首先,应用日志直接写入数据库(关系型、NoSQL)的话,会极大地影响应用的性能和并发能力。本人做过压测实验,并发数到达一定量后,业务接口没受到什么影响,反倒是应用日志由
首先,应用日志直接写入数据库(关系型、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);
}
}
添加到数据库暂不展示