spring boot錯誤日誌統一寫資料庫處理
阿新 • • 發佈:2022-03-15
首先,應用日誌直接寫入資料庫(關係型、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 /> "); sbuf.append(Transform.escapeTags(step.toString())); sbuf.append(CoreConstants.LINE_SEPARATOR); } if (commonFrames > 0) { sbuf.append("<br /> "); 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); } }
新增到資料庫暫不展示