1. 程式人生 > 其它 >spring boot錯誤日誌統一寫資料庫處理

spring boot錯誤日誌統一寫資料庫處理

首先,應用日誌直接寫入資料庫(關係型、NoSQL)的話,會極大地影響應用的效能和併發能力。本人做過壓測實驗,併發數到達一定量後,業務介面沒受到什麼影響,反倒是應用日誌由於生產速度過快,導致日誌資料大量堆積,無法寫入資料庫,成為應用的瓶頸。網際網路軟體行業對效能、併發要求比較高,通常使用的日誌收集系統架構有如下幾種: ElasticSearch + Logstash + Kibana(ELK)、ElasticSearch + Filebeat + Kibana(EFK)、Kafka + ELK、 Kafka + EFK。每個應用伺服器都要安裝agent客戶端從日誌檔案中收集日誌,ElasticSearch做儲存,Kibana做展示。

但是,傳統軟體行業很多對效能、併發性要求並不高,很多軟體專案可能只有一個管理後臺,如果硬上網際網路那一套日誌收集系統,無疑會增加專案的部署和維護難度。這種情況下,應用info級別的日誌可以在專案中定義一個AOP切面非同步寫入資料庫。本文主要介紹錯誤日誌的統一儲存。

在spring boot專案中,預設使用的是slf4j + logback日誌框架。只需實現logback的Appender介面,自定義一個錯誤日誌處理類即可對錯誤日誌進行統一儲存。

錯誤日誌資料庫表設計

新增錯誤日誌實體類

@Data
public class ErrorLogPO {

    private Integer logId;
    private String className;
    private String methodName;
    private String exceptionName;
    private String errMsg;
    private String stackTrace;
    private Date createTime;
}

新增錯誤日誌寫資料庫自定義Appender類

@Component
public class DbErrorLogAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {

    /**
     * 錯誤日誌資料庫增刪改查服務
     */
    @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();
    }

    /**
     * 錯誤日誌拼裝成實體類,寫入資料庫
     */
    @Override
    protected void append(ILoggingEvent loggingEvent) {
        IThrowableProxy tp = loggingEvent.getThrowableProxy();

        // ErrorLogPO資料表實體類
        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 {
            // 錯誤日誌實體類寫入資料庫
            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);
    }
}

新增到資料庫暫不展示