首先洲脂,應(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項目中广凸,默認(rèn)使用的是slf4j + logback日志框架阅茶。只需實現(xiàn)logback的Appender接口,自定義一個錯誤日志處理類即可對錯誤日志進行統(tǒng)一存儲谅海。
先上效果圖:
錯誤日志數(shù)據(jù)庫表設(shè)計
添加錯誤日志實體類
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 /> ");
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);
}
}