当前位置: 首页 > 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保存日志";}
}

相关文章:

Springboot3保存日志到数据库

保存日志到数据库 请求日志几乎是所有大型企业级项目的必要的模块&#xff0c;请求日志对于我们来说后期在项目运行上线一段时间用于排除异常、请求分流处理、限制流量等。请求日志一般都会记录请求参数、请求地址、请求状态&#xff08;Status Code&#xff09;、SessionId、…...

叉车高位显示器无线摄影,安装更加便捷!

叉车叉货&#xff0c;基本功能&#xff0c;但货叉升降高度确不一定&#xff0c;普通的3米左右&#xff0c;高的十几米&#xff0c;特别是仓储车&#xff0c;仓库叉货空间小&#xff0c;环境昏暗&#xff0c;视线受阻严重&#xff0c;司机叉货升的那么高怎么准确无误的插到货呢&…...

模板的特化

模板的特化 1.概念2.函数模板特化3.类模板的特化3.1 全特化3.2 偏特化3.2.1 部分特化3.2.2 参数更进一步的限制 4.总结 1.概念 在原模板类的基础上&#xff0c;针对特殊类型所进行特殊化的实现方式 2.函数模板特化 步骤 1.必须要先有一个基础的函数模板 2.关键字 template后面接…...

PCIE总线架构

1 概述 PCIe总线(Peripheral Component Interconnect Express)是一种高速串行计算机扩展总线标准,它是基于PCI总线的一种升级版,现在已经被广泛应用于各种高性能的计算机和服务器系统中。 PCIe总线提供更高的数据传输速度和更先进的特性,它主要特点如下: 高带宽:提供比…...

Adobe PR与AE的区别与联系(附网盘地址)

从事视频后期制作的小伙伴&#xff0c;对于PR&#xff08;Premiere&#xff09;和AE&#xff08;After Effects&#xff09;应该不会陌生。随着短视频的兴起&#xff0c;就连我们普通用户&#xff0c;拍摄完视频&#xff0c;都会去糟取精的剪辑一下&#xff0c;而PR正是一款功能…...

【QT 5 调试软件+Linux下调用脚本shell-无法调度+目录拼写+无法找目录+sudo权限(2)+问题解决方式+后续补充】

【QT 5 调试软件Linux下调用脚本shell-无法调度目录拼写无法找目录sudo权限&#xff08;2&#xff09;问题解决方式后续补充】 1、前言2、问题综述&#xff1a;自研qt上位机无法调度脚本&#xff08;1&#xff09;可能原因1&#xff1a;无法找到目录情况说明&#xff1a;解决思…...

企业防泄密妙招有哪些?请记住这8招!超实用,学起来!

在古代&#xff0c;有云&#xff1a;“密者&#xff0c;德之高也&#xff1b;事以密成&#xff0c;语以泄败。” 这些谚语不仅是对忠诚守密的高度赞扬&#xff0c;更是对保密工作重要性的深刻阐述。 在现代企业中&#xff0c;数据泄露已成为不容忽视的严峻挑战。 如何有效防止…...

pytorch千问模型源码分析

# 规范化技术&#xff0c;旨在替代传统的 Layer Normalization&#xff08;LN&#xff09; # 核心思想是对输入张量的每个样本的每个特征进行规范化&#xff0c;使其均值为 0&#xff0c;方差为 1 class Qwen2RMSNorm(nn.Module): def __init__(self, hidden_size, eps1e-6…...

滚雪球学SpringCloud[1.3]:SpringCloud环境搭建

全文目录&#xff1a; 前言1.3.1 环境要求1. JDK2. Maven3. IDE4. 其他工具 1.3.2 初始化Spring Boot项目方法一&#xff1a;使用Spring Initializr方法二&#xff1a;使用IDE项目结构 1.3.3 引入Spring Cloud依赖1. 更新pom.xml2. 添加Spring Cloud Starter依赖3. 示例完整的p…...

9.28今日错题解析(软考)

目录 前言面向对象技术——UML软件工程——软件能力成熟度模型&#xff08;CMM&#xff09;程序设计语言——编译 前言 这是用来记录我备考软考设计师的错题的&#xff0c;今天知识点为UML、软件能力成熟度模型&#xff08;CMM&#xff09;和编译&#xff0c;大部分错题摘自希…...

【Vue】以RuoYi框架前端为例,ElementUI封装图片上传组件——将图片信息转成base64后提交到后端保存

RuoYi 框架本身对于图片上传功能&#xff0c;在ElementUI的 <el-upload> 组件的基础装封装了 /components/ImageUpload/index.vue 组件。本组件就是在 RuoYi 自定义的 <ImageUpload> 组件的基础上进行改造&#xff0c;将图片的信息在上传之前处理成 base64 格式&am…...

【Linux】驱动的基本架构和编译

驱动源码 /** Silicon Integrated Co., Ltd haptic sih688x haptic driver file** Copyright (c) 2021 kugua <daokuan.zhusi-in.com>** This program is free software; you can redistribute it and/or modify it* under the terms of the GNU General Public Licen…...

1013. 将数组分成和相等的三个部分 数组切分

1013. 将数组分成和相等的三个部分 已解答 简单 相关标签 相关企业 提示 给你一个整数数组 arr&#xff0c;只有可以将其划分为三个和相等的 非空 部分时才返回 true&#xff0c;否则返回 false。 形式上&#xff0c;如果可以找出索引 i 1 < j 且满足 (arr[0] arr[…...

【深度学习】—— 自动微分、非标量变量的反向传播、 分离计算、 Python控制流的梯度计算

【深度学习】—— 自动微分 自动微分一个简单的例子 非标量变量的反向传播分离计算Python控制流的梯度计算 自动微分 求导是⼏乎所有深度学习优化算法的关键步骤。虽然求导的计算很简单&#xff0c;只需要⼀些基本的微积分。但对于复杂的模型&#xff0c;⼿⼯进⾏更新是⼀件很…...

Java项目实战II基于Java+Spring Boot+MySQL的大学城水电管理系统(源码+数据库+文档)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者 一、前言 随着大学城规模的不断扩大和学生数量的急剧增加&#xff0c;大学城内的水电管理面临着前所未有的挑战…...

Vue 组件的三大组成部分详解

文章目录 模板&#xff08;template&#xff09;脚本&#xff08;script&#xff09;样式&#xff08;style&#xff09;总结 在 Vue.js 中&#xff0c;组件是构建用户界面的重要基石。一个 Vue 组件通常由三个主要部分组成&#xff1a;模板&#xff08;template&#xff09;、…...

深入理解Java内部类

一、什么是内部类 内部类是定义在另一个类内部的类。内部类与外部类&#xff08;Enclosing Class&#xff09;之间存在着紧密的联系&#xff0c;可以访问外部类的成员变量和方法&#xff0c;这使得它们在某些场景下非常有用。 1.1 内部类的分类 Java中的内部类主要有以下几种…...

fiddler抓包12_篡改请求(请求前断点)

课程大纲 原理 正常“客户端-服务器”通信&#xff0c;即发送请求&#xff0c;接收返回。 Fiddler抓包是「客户端-浏览器」进行交互时&#xff0c;请求和响应都会从Fiddler通过&#xff0c;Fiddler可以捕获并展示。 请求前断点&#xff08;BreakPoint Before Request&#xff0…...

Webpack和GuIp打包原理以及不同

Webpack打包原理 Webpack的打包原理主要基于模块化的概念&#xff0c;它将应用程序中的所有资源&#xff08;如JS、CSS、图片等&#xff09;视为模块&#xff0c;并根据模块间的依赖关系进行静态分析。Webpack会递归地构建一个依赖关系图&#xff08;dependency graph&#xf…...

c++与Python用笛卡尔的心形函数输出爱心

我突然想到输出爱心是否可以用笛卡尔的心形函数 在IDLE里用Python输出下面这个图形 在小熊猫c里用c输出下面这个图形 如果当你要输出这些的时候会怎么办 低级:纯输出 print( ********* ********* ***************** ***************** …...

RestClient

什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端&#xff0c;它允许HTTP与Elasticsearch 集群通信&#xff0c;而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级&#xff…...

TDengine 快速体验(Docker 镜像方式)

简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能&#xff0c;本节首先介绍如何通过 Docker 快速体验 TDengine&#xff0c;然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker&#xff0c;请使用 安装包的方式快…...

渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止

<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet&#xff1a; https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...

测试markdown--肇兴

day1&#xff1a; 1、去程&#xff1a;7:04 --11:32高铁 高铁右转上售票大厅2楼&#xff0c;穿过候车厅下一楼&#xff0c;上大巴车 &#xffe5;10/人 **2、到达&#xff1a;**12点多到达寨子&#xff0c;买门票&#xff0c;美团/抖音&#xff1a;&#xffe5;78人 3、中饭&a…...

DBAPI如何优雅的获取单条数据

API如何优雅的获取单条数据 案例一 对于查询类API&#xff0c;查询的是单条数据&#xff0c;比如根据主键ID查询用户信息&#xff0c;sql如下&#xff1a; select id, name, age from user where id #{id}API默认返回的数据格式是多条的&#xff0c;如下&#xff1a; {&qu…...

HTML前端开发:JavaScript 常用事件详解

作为前端开发的核心&#xff0c;JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例&#xff1a; 1. onclick - 点击事件 当元素被单击时触发&#xff08;左键点击&#xff09; button.onclick function() {alert("按钮被点击了&#xff01;&…...

学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2

每日一言 今天的每一份坚持&#xff0c;都是在为未来积攒底气。 案例&#xff1a;OLED显示一个A 这边观察到一个点&#xff0c;怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 &#xff1a; 如果代码里信号切换太快&#xff08;比如 SDA 刚变&#xff0c;SCL 立刻变&#…...

Java多线程实现之Thread类深度解析

Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

MySQL JOIN 表过多的优化思路

当 MySQL 查询涉及大量表 JOIN 时&#xff0c;性能会显著下降。以下是优化思路和简易实现方法&#xff1a; 一、核心优化思路 减少 JOIN 数量 数据冗余&#xff1a;添加必要的冗余字段&#xff08;如订单表直接存储用户名&#xff09;合并表&#xff1a;将频繁关联的小表合并成…...