【springboot 统一错误日志收集器,各子项目可按需配置集成】

springboot 统一错误日志收集器

springboot 统一错误日志收集器

序言

如果单个项目做错误日志收集,可使用拦截器,过滤器,或者重写slf4j的log方法,等思路做日志收集,但随着微服务的逐步扩大,很多项目中都有这种需求,将其中的共性抽出来,个性的代码作为配置项,这种设计可以满足很多场景的需求

收集流程

  1. 自定义一个启动器starter,封装成jar文件,可以供其他项目依赖使用,对其余项目来说,是代码业务无侵入
  2. 定义一个基础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;
}
  1. 处理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 />&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);
    }
}

说明:该类主要处理error的日志,器定义的init方法,就是项目启动时,过滤得到ERROR日志的内容,后面append方法就是从ILoggingEvent 日志对象中拿到具体参数进行拼接封装,得到自己的日志对象进行处理,可以落库,或者发MQ,或者mail,通知群下发通知
后续,这里面可以加上自定义配置,多少时间通知一次,通知给哪些人等

  1. 上面的启动器,该bean需要被发现,从而引入到其余项目的IOC容器中,正常工作
    在这里插入图片描述
    备注:通过资源文件夹下的spring.factories,让主项目发现该bean,并注册到IOC容器中
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.wode.config.HelloServiceAutoConfiguration,\
com.wode.converter.DbErrorLogAppender
  1. 将项目打成jar包,供其他项目依赖
    6.
  2. 依赖该启动器
    在这里插入图片描述
        <dependency>
            <groupId>cn.itcast</groupId>
            <artifactId>hello-spring-boot-starter</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
  1. 测试
    在这里插入图片描述
    控制台打印出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;
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值