spring boot錯誤日志統(tǒng)一寫數(shù)據(jù)庫處理

首先,應(yīng)用日志直接寫入數(shù)據(jù)庫(關(guān)系型、NoSQL)的話,會極大地影響應(yīng)用的性能和并發(fā)能力。本人做過壓測實驗,并發(fā)數(shù)到達一定量后,業(yè)務(wù)接口沒受到什么影響,反倒是應(yīng)用日志由于生產(chǎn)速度過快,導(dǎo)致日志數(shù)據(jù)大量堆積,無法寫入數(shù)據(jù)庫,成為應(yīng)用的瓶頸?;ヂ?lián)網(wǎng)軟件行業(yè)對性能、并發(fā)要求比較高,通常使用的日志收集系統(tǒng)架構(gòu)有如下幾種: ElasticSearch + Logstash + Kibana(ELK)、ElasticSearch + Filebeat + Kibana(EFK)、Kafka + ELK、 Kafka + EFK。每個應(yīng)用服務(wù)器都要安裝agent客戶端從日志文件中收集日志,ElasticSearch做存儲,Kibana做展示。

但是,傳統(tǒng)軟件行業(yè)很多對性能、并發(fā)性要求并不高,很多軟件項目可能只有一個管理后臺,如果硬上互聯(lián)網(wǎng)那一套日志收集系統(tǒng),無疑會增加項目的部署和維護難度。這種情況下,應(yīng)用info級別的日志可以在項目中定義一個AOP切面異步寫入數(shù)據(jù)庫。本文主要介紹錯誤日志的統(tǒng)一存儲。

在spring boot項目中,默認使用的是slf4j + logback日志框架。只需實現(xiàn)logback的Appender接口,自定義一個錯誤日志處理類即可對錯誤日志進行統(tǒng)一存儲。

先上效果圖:


錯誤日志列表
錯誤日志詳情頁

錯誤日志數(shù)據(jù)庫表設(shè)計


錯誤日志數(shù)據(jù)庫表結(jié)構(gòu)

添加錯誤日志實體類

public class ErrorLogPO {

    private Integer logId;

    private String className;

    private String methodName;

    private String exceptionName;

    private String errMsg;

    private String stackTrace;

    private Date createTime;

    public Integer getLogId() {
        return logId;
    }

    public void setLogId(Integer logId) {
        this.logId = logId;
    }

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public String getExceptionName() {
        return exceptionName;
    }

    public void setExceptionName(String exceptionName) {
        this.exceptionName = exceptionName;
    }

    public String getErrMsg() {
        return errMsg;
    }

    public void setErrMsg(String errMsg) {
        this.errMsg = errMsg;
    }

    public String getStackTrace() {
        return stackTrace;
    }

    public void setStackTrace(String stackTrace) {
        this.stackTrace = stackTrace;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }
}

添加錯誤日志寫數(shù)據(jù)庫自定義Appender類

@Component
public class DbErrorLogAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {

    /**
     * 錯誤日志數(shù)據(jù)庫增刪改查服務(wù)
     */
    @Autowired
    private ILogService logService;

    /**
     * DbErrorLogAppender初始化
     */
    @PostConstruct
    public void init() {
        LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();

        ThresholdFilter filter = new ThresholdFilter();
        filter.setLevel("ERROR");
        filter.setContext(context);
        filter.start();
        this.addFilter(filter);
        this.setContext(context);

        context.getLogger("ROOT").addAppender(DbErrorLogAppender.this);

        super.start();
    }

    /**
     * 錯誤日志拼裝成實體類,寫入數(shù)據(jù)庫
     */
    @Override
    protected void append(ILoggingEvent loggingEvent) {
        IThrowableProxy tp = loggingEvent.getThrowableProxy();

        // ErrorLogPO數(shù)據(jù)表實體類
        ErrorLogPO errorLog = new ErrorLogPO();
        errorLog.setErrMsg(loggingEvent.getMessage());
        errorLog.setCreateTime(new Date(loggingEvent.getTimeStamp()));

        if (loggingEvent.getCallerData() != null && loggingEvent.getCallerData().length > 0) {
            StackTraceElement element = loggingEvent.getCallerData()[0];
            errorLog.setClassName(element.getClassName());
            errorLog.setMethodName(element.getMethodName());
        }

        if (tp != null) {
            errorLog.setExceptionName(tp.getClassName());
            errorLog.setStackTrace(getStackTraceMsg(tp));
        }

        try {
            // 錯誤日志實體類寫入數(shù)據(jù)庫
            logService.addErrorLog(errorLog);
        } catch (Exception ex) {
            this.addError("上報錯誤日志失?。? + ex.getMessage());
        }
    }

    /**
     * 拼裝堆棧跟蹤信息
     */
    private String getStackTraceMsg(IThrowableProxy tp) {
        StringBuilder buf = new StringBuilder();

        if (tp != null) {
            while (tp != null) {
                this.renderStackTrace(buf, tp);
                tp = tp.getCause();
            }
        }

        return buf.toString();
    }

    /**
     * 堆棧跟蹤信息拼裝成html字符串
     */
    private void renderStackTrace(StringBuilder sbuf, IThrowableProxy tp) {
        this.printFirstLine(sbuf, tp);
        int commonFrames = tp.getCommonFrames();
        StackTraceElementProxy[] stepArray = tp.getStackTraceElementProxyArray();

        for (int i = 0; i < stepArray.length - commonFrames; ++i) {
            StackTraceElementProxy step = stepArray[i];
            sbuf.append("<br />&nbsp;&nbsp;&nbsp;&nbsp;");
            sbuf.append(Transform.escapeTags(step.toString()));
            sbuf.append(CoreConstants.LINE_SEPARATOR);
        }

        if (commonFrames > 0) {
            sbuf.append("<br />&nbsp;&nbsp;&nbsp;&nbsp;");
            sbuf.append("\t... ").append(commonFrames).append(" common frames omitted").append(CoreConstants.LINE_SEPARATOR);
        }

    }

    /**
     * 拼裝堆棧跟蹤信息第一行
     */
    public void printFirstLine(StringBuilder sb, IThrowableProxy tp) {
        int commonFrames = tp.getCommonFrames();
        if (commonFrames > 0) {
            sb.append("<br />").append("Caused by: ");
        }

        sb.append(tp.getClassName()).append(": ").append(Transform.escapeTags(tp.getMessage()));
        sb.append(CoreConstants.LINE_SEPARATOR);
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容