当前位置: 首页 > news >正文

Springboot3保存日志到数据库

保存日志到数据库

请求日志几乎是所有大型企业级项目的必要的模块,请求日志对于我们来说后期在项目运行上线一段时间用于排除异常、请求分流处理、限制流量等。请求日志一般都会记录请求参数、请求地址、请求状态(Status Code)、SessionId、请求方法方式(Method)、请求时间、客户端IP地址、请求返回内容、耗时等等。如果你得系统还有其他个性化的配置,也可以完成记录。

在实际的项目中,特别是管理系统中,对于那些重要的操作我们通常都会记录操作日志。比如对数据库的CRUD操作,我们都会对每一次重要的操作进行记录,通常的做法是向数据库指定的日志表中插入一条记录。这里就产生了一个问题,难道要我们每次在 CRUD的时候都手动的插入日志记录吗?这肯定是不合适的,这样的操作无疑是加大了开发量,而且不易维护,所以实际项目中总是利用AOP(Aspect Oriented Programming)即面向切面编程这一技术来记录系统中的操作日志。Logback也提供了保存日志到数据库的功能。

1、添加依赖

 <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--mysql数据源--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version></dependency><!-- lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.34</version><scope>provided</scope></dependency><!--日志相关--><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-core</artifactId><version>1.4.5</version></dependency><!-- 自动依赖 slf4j-api --><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.4.5</version></dependency><!-- logback操作数据库的包 --><dependency><groupId>ch.qos.logback.db</groupId><artifactId>logback-classic-db</artifactId><version>1.2.11.1</version></dependency><!--这个依赖必须存在,否则会报java.lang.ClassNotFoundException.org.apache.commons.dbcp.BasicDataSource--><dependency><groupId>commons-dbcp</groupId><artifactId>commons-dbcp</artifactId><version>1.4</version></dependency></dependencies>

2、创建日志数据库

创建一个数据库logs_db,该库中创建如下表

BEGIN;
DROP TABLE IF EXISTS `system_log`;
COMMIT;BEGIN;
CREATE TABLE `system_log` (`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键id',`create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',`update_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '更新时间',`ip_addr` varchar(154) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '客户端ip地址',`message` text NOT NULL COMMENT '详情',`level_string` varchar(254) NOT NULL COMMENT '等级',`logger_name` varchar(254) NOT NULL COMMENT '名称',`caller_filename` varchar(254) NOT NULL COMMENT '文件名',`caller_class` varchar(254) NOT NULL COMMENT '类',`caller_method` varchar(254) NOT NULL COMMENT '方法',`caller_line` char(4) NOT NULL COMMENT '行数',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB DEFAULT CHARSET = utf8 COMMENT '系统日志';
COMMIT;

3、创建LogDBAppender类

该类是用来操作日志数据库的类

/*** 自定义日志保存类*/
@Configuration
@Slf4j
public class LogDBAppender extends DBAppenderBase<ILoggingEvent> {private static final int CREATE_TIME_INDEX = 1;private static final int UPDATE_TIME_INDEX = 2;private static final int IP_ADDR=3;private static final int MESSAGE_INDEX = 4;private static final int LEVEL_STRING_INDEX = 5;private static final int LOGGER_NAME_INDEX = 6;private static final int CALLER_FILENAME_INDEX = 7;private static final int CALLER_CLASS_INDEX = 8;private static final int CALLER_METHOD_INDEX = 9;private static final int CALLER_LINE_INDEX = 10;protected String insertSQL;protected static final Method GET_GENERATED_KEYS_METHOD;protected static final StackTraceElement EMPTY_CALLER_DATA = CallerData.naInstance();private static String buildInsertSQL() {StringBuilder sqlBuilder = new StringBuilder("INSERT INTO system_log ");sqlBuilder.append("(create_time, update_time,ip_addr, message, level_string, logger_name, caller_filename, caller_class, caller_method, caller_line) ");sqlBuilder.append("VALUES (?, ?,?, ? ,?, ?, ?, ?, ?, ?)");return sqlBuilder.toString();}@Overridepublic void start() {this.insertSQL = buildInsertSQL();super.start();}@Overrideprotected Method getGeneratedKeysMethod() {return GET_GENERATED_KEYS_METHOD;}@Overrideprotected String getInsertSQL() {return this.insertSQL;}@Overrideprotected void subAppend(ILoggingEvent iLoggingEvent, Connection connection, PreparedStatement preparedStatement) throws Throwable {this.bindLoggingEventWithInsertStatement(preparedStatement, iLoggingEvent);this.bindCallerDataWithPreparedStatement(preparedStatement, iLoggingEvent.getCallerData());int updateCount = preparedStatement.executeUpdate();if (updateCount != 1) {this.addWarn("Failed to insert loggingEvent");}}@Overrideprotected void secondarySubAppend(ILoggingEvent iLoggingEvent, Connection connection, long l) throws Throwable {}private void bindCallerDataWithPreparedStatement(PreparedStatement preparedStatement, StackTraceElement[] callerDataArray) throws SQLException {StackTraceElement caller = this.extractFirstCaller(callerDataArray);preparedStatement.setString(CALLER_FILENAME_INDEX, caller.getFileName());preparedStatement.setString(CALLER_CLASS_INDEX, caller.getClassName());preparedStatement.setString(CALLER_METHOD_INDEX, caller.getMethodName());preparedStatement.setString(CALLER_LINE_INDEX, Integer.toString(caller.getLineNumber()));}private StackTraceElement extractFirstCaller(StackTraceElement[] callerDataArray) {StackTraceElement caller = EMPTY_CALLER_DATA;if (this.hasAtLeastOneNonNullElement(callerDataArray)) {caller = callerDataArray[0];}return caller;}private boolean hasAtLeastOneNonNullElement(StackTraceElement[] callerDataArray) {return callerDataArray != null && callerDataArray.length > 0 && callerDataArray[0] != null;}private void bindLoggingEventWithInsertStatement(PreparedStatement preparedStatement, ILoggingEvent iLoggingEvent) throws SQLException {Date date = new Date(iLoggingEvent.getTimeStamp());preparedStatement.setDate(CREATE_TIME_INDEX, date);preparedStatement.setDate(UPDATE_TIME_INDEX, date);preparedStatement.setString(IP_ADDR,getUserIP());preparedStatement.setString(MESSAGE_INDEX, iLoggingEvent.getFormattedMessage());preparedStatement.setString(LEVEL_STRING_INDEX, iLoggingEvent.getLevel().toString());preparedStatement.setString(LOGGER_NAME_INDEX, iLoggingEvent.getLoggerName());}public String getUserIP() {ServletRequestAttributes requestAttributes = ServletRequestAttributes.class.cast(RequestContextHolder.getRequestAttributes());HttpServletRequest contextRequest = requestAttributes.getRequest();String remoteAddr = "";if (contextRequest != null) {remoteAddr = contextRequest.getHeader("X-FORWARDED-FOR");if (remoteAddr == null || "".equals(remoteAddr)) {remoteAddr = contextRequest.getRemoteAddr();}}return remoteAddr;}static {Method getGeneratedKeysMethod;try {getGeneratedKeysMethod = PreparedStatement.class.getMethod("getGeneratedKeys", (Class[])null);} catch (Exception var2) {getGeneratedKeysMethod = null;}GET_GENERATED_KEYS_METHOD = getGeneratedKeysMethod;}
}

4、创建配置文件logback-spring.xml

新版的logback中去除了DBAppender类,如查要保存到数据库需要重写该类,参考步骤6

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false"><!--控制台日志格式:彩色日志--><!-- magenta:洋红 --><!-- boldMagenta:粗红--><!-- green:绿色--><!-- boldGreen:深绿色--><!-- cyan:青色 --><!-- white:白色 --><conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" /><conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" /><conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" /><!-- 彩色日志格式 --><property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/><!--编码--><property name="ENCODING" value="UTF-8"/><!--输出到控制台--><appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"><filter class="ch.qos.logback.classic.filter.ThresholdFilter"><!--日志级别--><level>DEBUG</level></filter><encoder><!--日志格式--><Pattern>${CONSOLE_LOG_PATTERN}</Pattern><!--日志字符集--><charset>${ENCODING}</charset></encoder></appender><!--连接数据库配置--><appender name="db_classic_mysql_pool" class="com.woniu.logs.LogDBAppender"><connectionSource class="ch.qos.logback.core.db.DataSourceConnectionSource"><dataSource class="org.apache.commons.dbcp.BasicDataSource"><driverClassName>com.mysql.cj.jdbc.Driver</driverClassName><url>jdbc:mysql://127.0.0.1:3306/logs_db?serverTimezone=Asia/Shanghai</url><username>root</username><password>123456</password></dataSource></connectionSource></appender><!--myibatis log configure--><logger name="com.apache.ibatis" level="TRACE"/><logger name="java.sql.Connection" level="DEBUG"/><logger name="java.sql.Statement" level="DEBUG"/><logger name="java.sql.PreparedStatement" level="DEBUG"/><!-- 日志输出级别 --><root level="INFO"><appender-ref ref="CONSOLE"/><appender-ref ref="db_classic_mysql_pool"/></root>
</configuration>

5、修改yml

server:port: 8080
spring:application:name: log-demologging:level:com:woniu:dao: DEBUGroot: INFOconfig: classpath:logback-spring.xml

6、测试

编写测试代码

@RestController
@Slf4j
public class UserController {@GetMapping("login")public String getUser(String account,String password){if(account.equals("tom") && password.equals("123")){log.info("用户"+account+"登录成功");}else{log.warn("用户名或密码错误");}return "测试lomback保存日志";}
}

相关文章:

  • 移动会议:气膜馆的理想选择—轻空间
  • 设计模式 策略模式(Strategy Pattern)
  • SWAP、AquaCrop、FVCOM、Delft3D、SWAT、R+VIC、HSPF、HEC-HMS......
  • 【JAVA高级】 redis分布式双重加锁(业务校验:防止接口并发调用时数据重复)
  • excel-VBA知识点记录
  • 享元(轻量级)模式
  • B样条(B-spline)函数
  • 如何更改 Ubuntu/Linux 终端中命令行提示符的颜色
  • (11)(2.1.2) DShot ESCs(四)
  • C++文件内容加解密
  • Unity3D Compute Shader同步详解
  • HBase 性能优化的高频面试题及答案
  • Adobe Photoshop 2024 v25.12 (macOS, Windows) 发布下载 - 照片和设计软件
  • C++基础知识9 模版进阶
  • 【Springboot入门- RESTful服务的支持】
  • 「译」Node.js Streams 基础
  • 07.Android之多媒体问题
  • Angular数据绑定机制
  • C# 免费离线人脸识别 2.0 Demo
  • chrome扩展demo1-小时钟
  • CSS 提示工具(Tooltip)
  • ECMAScript6(0):ES6简明参考手册
  • es的写入过程
  • js ES6 求数组的交集,并集,还有差集
  • leetcode-27. Remove Element
  • php ci框架整合银盛支付
  • 闭包--闭包之tab栏切换(四)
  • 第十八天-企业应用架构模式-基本模式
  • 短视频宝贝=慢?阿里巴巴工程师这样秒开短视频
  • 工作踩坑系列——https访问遇到“已阻止载入混合活动内容”
  • 基于遗传算法的优化问题求解
  • 精彩代码 vue.js
  • 来,膜拜下android roadmap,强大的执行力
  • 聊一聊前端的监控
  • 前端技术周刊 2018-12-10:前端自动化测试
  • 嵌入式文件系统
  • 如何用vue打造一个移动端音乐播放器
  • 思维导图—你不知道的JavaScript中卷
  • 一个普通的 5 年iOS开发者的自我总结,以及5年开发经历和感想!
  • 走向全栈之MongoDB的使用
  • ​ 无限可能性的探索:Amazon Lightsail轻量应用服务器引领数字化时代创新发展
  • ​VRRP 虚拟路由冗余协议(华为)
  • ​必胜客礼品卡回收多少钱,回收平台哪家好
  • #1015 : KMP算法
  • (4)事件处理——(2)在页面加载的时候执行任务(Performing tasks on page load)...
  • (C#)一个最简单的链表类
  • (done) 两个矩阵 “相似” 是什么意思?
  • (Java企业 / 公司项目)点赞业务系统设计-批量查询点赞状态(二)
  • (四)opengl函数加载和错误处理
  • (四)进入MySQL 【事务】
  • (贪心 + 双指针) LeetCode 455. 分发饼干
  • (图文详解)小程序AppID申请以及在Hbuilderx中运行
  • (一)项目实践-利用Appdesigner制作目标跟踪仿真软件
  • (幽默漫画)有个程序员老公,是怎样的体验?
  • .bat批处理(一):@echo off