spring boot错误日志统一写数据库处理

首先,应用日志直接写入数据库(关系型、NoSQL)的话,会极大地影响应用的性能和并发能力。本人做过压测实验,并发数到达一定量后,业务接口没受到什么影响,反倒是应用日志由于生产速度过快,导致日志数据大量堆积,无法写入数据库,成为应用的瓶颈。互联网软件行业对性能、并发要求比较高,通常使用的日志收集系统架构有如下几种: ElasticSearch + Logstash + Kibana(ELK)、ElasticSearch + Filebeat + Kibana(EFK)、Kafka + ELK、 Kafka + EFK。每个应用服务器都要安装agent客户端从日志文件中收集日志,ElasticSearch做存储,Kibana做展示。

但是,传统软件行业很多对性能、并发性要求并不高,很多软件项目可能只有一个管理后台,如果硬上互联网那一套日志收集系统,无疑会增加项目的部署和维护难度。这种情况下,应用info级别的日志可以在项目中定义一个AOP切面异步写入数据库。本文主要介绍错误日志的统一存储。

在spring boot项目中,默认使用的是slf4j + logback日志框架。只需实现logback的Appender接口,自定义一个错误日志处理类即可对错误日志进行统一存储。

先上效果图:


错误日志列表
错误日志详情页

错误日志数据库表设计


错误日志数据库表结构

添加错误日志实体类

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;
    }
}

添加错误日志写数据库自定义Appender类

@Component
public class DbErrorLogAppender extends UnsynchronizedAppenderBase {

    /**
     * 错误日志数据库增删改查服务
     */
    @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("
    "); sbuf.append(Transform.escapeTags(step.toString())); sbuf.append(CoreConstants.LINE_SEPARATOR); } if (commonFrames > 0) { sbuf.append("
    "); 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("
").append("Caused by: "); } sb.append(tp.getClassName()).append(": ").append(Transform.escapeTags(tp.getMessage())); sb.append(CoreConstants.LINE_SEPARATOR); } }

你可能感兴趣的:(spring boot错误日志统一写数据库处理)