springboot 统一错误日志收集器
- springboot 统一错误日志收集器
- 序言
- 收集流程
springboot 统一错误日志收集器
序言
如果单个项目做错误日志收集,可使用拦截器,过滤器,或者重写slf4j的log方法,等思路做日志收集,但随着微服务的逐步扩大,很多项目中都有这种需求,将其中的共性抽出来,个性的代码作为配置项,这种设计可以满足很多场景的需求
收集流程
- 自定义一个启动器starter,封装成jar文件,可以供其他项目依赖使用,对其余项目来说,是代码业务无侵入
- 定义一个基础PO,包含logId,出错的方法,入参,事件等基础参数
import java.util.Date;
@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;
}
- 处理ERROR日志的核心类
代码:
package com.wode.converter;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.filter.ThresholdFilter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.IThrowableProxy;
import ch.qos.logback.classic.spi.StackTraceElementProxy;
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.UnsynchronizedAppenderBase;
import ch.qos.logback.core.helpers.Transform;
import com.alibaba.fastjson.JSON;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.Date;
@Component
public class DbErrorLogAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {
/**
* 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 {
System.out.println("55555555555556666666666" + JSON.toJSONString(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);
}
}
说明:该类主要处理error的日志,器定义的init方法,就是项目启动时,过滤得到ERROR日志的内容,后面append方法就是从ILoggingEvent 日志对象中拿到具体参数进行拼接封装,得到自己的日志对象进行处理,可以落库,或者发MQ,或者mail,通知群下发通知
后续,这里面可以加上自定义配置,多少时间通知一次,通知给哪些人等
- 上面的启动器,该bean需要被发现,从而引入到其余项目的IOC容器中,正常工作
备注:通过资源文件夹下的spring.factories,让主项目发现该bean,并注册到IOC容器中
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.wode.config.HelloServiceAutoConfiguration,\
com.wode.converter.DbErrorLogAppender
- 将项目打成jar包,供其他项目依赖
- 依赖该启动器
<dependency>
<groupId>cn.itcast</groupId>
<artifactId>hello-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
- 测试
控制台打印出jar中的日志了。
package com.tsf.demo.redis.controller;
import com.tsf.demo.redis.param.Param;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @author 75690
*/
@RestController
@RequestMapping("redis")
@Slf4j
public class Controller {
@RequestMapping("test")
public String test(@RequestBody Param param) {
log.info("1111111111111111,{}", param);
log.error("00000000000000000,{}", param);
return "hhhh" + param;
}
}