Spring Boot如何实现错误日志统一存储至数据库?

2026-05-05 23:202阅读0评论SEO基础
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计888个文字,预计阅读时间需要4分钟。

Spring Boot如何实现错误日志统一存储至数据库?

首先,应用日志直接写入数据库(关系型、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接口,自定义一个错误日志处理类即可对错误日志进行统一存储。

错误日志数据库表设计

添加错误日志实体类

Spring Boot如何实现错误日志统一存储至数据库?

@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 />&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); } }

添加到数据库暂不展示

本文共计888个文字,预计阅读时间需要4分钟。

Spring Boot如何实现错误日志统一存储至数据库?

首先,应用日志直接写入数据库(关系型、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接口,自定义一个错误日志处理类即可对错误日志进行统一存储。

错误日志数据库表设计

添加错误日志实体类

Spring Boot如何实现错误日志统一存储至数据库?

@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 />&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); } }

添加到数据库暂不展示