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保存日志";}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1549027.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

(最新已验证)stm32 + 新版 onenet +dht11+esp8266/01s + mqtt物联网上报温湿度和控制单片机(保姆级教程)

物联网实践教程&#xff1a;微信小程序结合OneNET平台MQTT实现STM32单片机远程智能控制 远程上报和接收数据——汇总 前言 之前在学校获得了一个新玩意&#xff1a;ESP-01sWIFI模块&#xff0c;去搜了一下这个小东西很有玩点&#xff0c;远程控制LED啥的&#xff0c;然后我就想…

[大语言模型-论文精读] Diffusion Model技术-通过时间和空间组合扩散模型生成复杂的3D人物动作

​​​​​​Generation of Complex 3D Human Motion by Temporal and Spatial Composition of Diffusion Models L Mandelli, S Berretti - arXiv preprint arXiv:2409.11920, 2024 通过时间和空间组合扩散模型生成复杂的3D人物动作 摘要 本文提出了一种新的方法&#xff0…

UCS512DHN DMX512差分并联协议LED驱动IC 舞动灯光的魔法芯片

UCS512DHN产品概述&#xff1a; UCS512DHN是DMX512差分并联协议LED驱动芯片&#xff0c;可选择1/2/3/4通道高精度恒流输出&#xff0c;灰度达65536 级。UCS512DHN为带散热片封装的大电流输出版本。UCS512DHN有PWM反极性输出功能&#xff0c;此功能适合外挂三极 管&#xff0c;…

极品飞车14热力追踪原始版高清重制版MOD分享

《极品飞车14&#xff1a;热力追击》&#xff08;Need for Speed:Hot Pursuit&#xff09;是由Criterion Games工作室负责开发&#xff0c;EA公司2010年底发行的一款竞速类游戏&#xff0c;也是新一代的热力追踪系列作品&#xff0c;游戏平台为Xbox 360、PS3。 《极品飞车14&a…

11. LCEL:LangChain Expression Language

这篇文章覆盖了LCEL的理解和他是如何工作的。 LCEL(LangChain Expression Language)&#xff1a;是把一些有趣python概念抽象成一种格式&#xff0c;从而为构建LangChain组件链提供一种“简约”代码层。 LCEL在下面方面有着强大的支撑&#xff1a; 链的快速开发流式输出、异…

线性方程组的迭代方法

目录 直接方法与迭代方法 常规迭代算法 选择迭代求解器 预条件子 预条件子示例 均衡和重新排序 使用线性运算函数取代矩阵 数值线性代数最重要也是最常见的应用之一是可求解以 A*x b 形式表示的线性方程组。当 A 为大型稀疏矩阵时&#xff0c;您可以使用迭代方法求解线…

【路径规划】基于球向量的粒子群优化(SPSO)算法在无人机路径规划中的实现

摘要 本文介绍了基于球形矢量的粒子群优化&#xff08;Spherical Particle Swarm Optimization, SPSO&#xff09;算法&#xff0c;用于无人机&#xff08;UAV&#xff09;路径规划。SPSO算法通过引入球形矢量的概念&#xff0c;增强了粒子群在多维空间中的探索和利用能力&…

excel统计分析(1):列联表分析与卡方检验

列联表&#xff1a;用于展示两个或多个分类变量之间频数关系的表格。——常用于描述性分析卡方检验&#xff1a;通过实际频数和期望频数&#xff08;零假设为真情况下的频数&#xff09;&#xff0c;反映了观察频数与期望频数之间的差异程度&#xff0c;来评估两个变量是否独立…

Android实现图片滚动和页签控件功能的实现代码

首先题外话&#xff0c;今天早上起床的时候&#xff0c;手滑一下把我的手机甩了出去&#xff0c;结果陪伴我两年半的摩托罗拉里程碑一代就这么安息了&#xff0c;于是我今天决定怒更一记&#xff0c;纪念我死去的爱机。 如果你是网购达人&#xff0c;你的手机上一定少不了淘宝…

protobuff中的required有什么用?

大家在proto2 应该经常看到如下msg表达: message MsgType3 { required int32 value1 1; required int32 value2 2; } 在protobuff中的required 有什么作用&#xff1f;在 Protocol Buffers&#xff08;protobuf&#xff09;中&#xff0c;required 关键字用于指定某个字段是…

经济不好,但是遍地都是赚钱的机会

现在职场越来越内卷&#xff0c;裁员风波也是不断&#xff0c;前些天看到一个帖子&#xff0c;裁员都裁到应届生头上了。 都说00后整治职场&#xff0c;在如今环境下也要掂量一下了。 大家都在抱怨环境&#xff0c;可是你有没有想过&#xff0c;有些人在闷声发着小财。 下面…

信息安全工程师(24)网络安全体系建设原则与安全策略

一、网络安全体系建设原则 网络空间主权原则&#xff1a;维护网络空间主权是网络安全的首要原则。这要求国家在网络空间的管理、运营、建设和使用等方面具有完全自主的权利和地位&#xff0c;不受任何外部势力的干涉和侵犯。网络安全与信息化发展并重原则&#xff1a;网络安全与…

异常断链吐血经历!拯救Air780EP模块紧急项目

我最近被老板驱使&#xff0c;要用合宙Air780EP模块做几个紧急项目。由于时间紧任务重&#xff0c;遇到了一些棘手问题&#xff0c;可把我给折腾死了…… 这里把遇到的问题&#xff0c;排查记录下来&#xff0c;看能不能帮到因遇到类似的问题&#xff0c;并且一直没找到原因&a…

深度学习------------------------RNN(循环神经网络)

目录 潜变量自回归模型循环神经网络困惑度梯度剪裁循环神经网络的从零开始实现初始化循环神经网络模型的模型参数初始化隐藏状态创建一个类来包装这些函数该部分总代码 定义预测函数该部分总代码 梯度裁剪定义一个函数在一个迭代周期内训练模型训练函数 循环神经网络的简洁实现…

Redis高级特性及应用

一、Redis慢查询 1.1 Redis命令流程 1.2 慢查询配置&#xff1a; 可以通过以下命令配置慢查询时间阈值&#xff08;慢查询值得是上图中执行命令的时间&#xff0c;不包含其他时间&#xff09; config set slowlog-log-slower-than 10000 //单位微秒 config rewrite //写入…

大模型与智能体的市场调研分析

2024年&#xff0c;很多人都在谈论智能体&#xff0c;我老婆这样的美术老师&#xff0c;也让我给她科普一下&#xff0c;于是我花了几天时间&#xff0c;系统学习和深入调研了一下&#xff0c;在此分享给大家。 时代背景 人工智能就像电力一样&#xff0c;如果你的竞争对手正…

木材检测系统源码分享

木材检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vision …

物联网智能项目全面解析

目录 引言 一、物联网概述 1.1 什么是物联网 1.2 物联网的历史与发展 二、物联网智能项目分类 三、关键组件与技术 3.1 传感器和执行器 3.2 连接技术 3.3 数据处理与分析 3.4 用户界面 四、物联网智能项目案例分析 4.1 智能家居 4.2 智慧城市 4.3 工业物联网 4.4…

C# 字符串(String)的应用说明一

一.字符串&#xff08;String&#xff09;的应用说明&#xff1a; 在 C# 中&#xff0c;更常见的做法是使用 string 关键字来声明一个字符串变量&#xff0c;也可以使用字符数组来表示字符串。string 关键字是 System.String 类的别名。 二.创建 String 对象的方法说明&#x…

$attrs 和 $listeners

通常情况下&#xff0c;父子组件之间的数据是通过 props 由父向子传递的&#xff0c;当子组件想要修改数据时&#xff0c;则需要通过 $emit 以事件形式交由父组件完成&#xff0c;而这种交互方式只存在于父子组件之间&#xff0c;多层嵌套的时候&#xff0c;处于内层的组件想要…