Logback自定义DBAppender保存系统日志到数据库
在系统中采用了spring boot + logback+slf4j的日志框架,将系统日志记录到数据库。

相关参考来源:
官方文档-DBAppender
Logback输出日志到自定义MySQL数据库(重写DBAppender)
logback日志框架中filter的使用
1. 添加依赖:
从 logback 版本 1.2.8 开始,DBAppender 不再随 logback-classic 一起提供,所以需要单独引入
<dependency><groupId>ch.qos.logback.db</groupId><artifactId>logback-classic-db</artifactId><version>1.2.11.1</version>
</dependency>
2. 自定义表结构:
可以根据实际情况增加或减少和修改字段
CREATE TABLE t_log_logback (`id` VARCHAR ( 64 ) NOT NULL COMMENT '主键',`create_time` VARCHAR ( 32 ) NOT NULL COMMENT '创建时间',`message` TEXT NOT NULL COMMENT '内容',`level_string` VARCHAR ( 254 ) NOT NULL COMMENT '日志等级:TRACE,DEBUG,INFO,WARNING,ERROR,FATAL',`logger_name` VARCHAR ( 254 ) NOT NULL COMMENT '发出日志记录请求的记录器的名称',`thread_name` VARCHAR ( 254 ) COMMENT '线程名称',`reference_flag` INT ( 11 ) COMMENT 'MDC属性',`caller_filename` VARCHAR ( 254 ) NOT NULL COMMENT '发出日志记录请求的文件名',`caller_class` VARCHAR ( 254 ) NOT NULL COMMENT '发出日志记录请求的类',`caller_method` VARCHAR ( 254 ) NOT NULL COMMENT '发出日志记录请求的方法的名称',`caller_line` VARCHAR ( 4 ) NOT NULL COMMENT '发出日志记录请求的行号',
PRIMARY KEY ( `id` ) USING BTREE
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = DYNAMIC COMMENT = 'logback系统日志记录表';
3. 自定义追加器DbLogbackAppender
import ch.qos.logback.classic.spi.CallerData;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.db.DBAppenderBase;import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;/*** <p>* 自定义DB日志追加器,参考实现类 {@link ch.qos.logback.classic.db.DBAppender}* 参考来源:https://blog.csdn.net/qq_20914913/article/details/92830914* </p>*/
public class DbLogbackAppender extends DBAppenderBase<ILoggingEvent> {private String insertSQL;private static final Method GET_GENERATED_KEYS_METHOD;// 对应于数据库字段的插入数据序号private static final int ID_INDEX = 1;private static final int CREATE_TIME_INDEX = 2;private static final int MESSAGE_INDEX = 3;private static final int LEVEL_STRING_INDEX = 4;private static final int LOGGER_NAME_INDEX = 5;private static final int THREAD_NAME_INDEX = 6;private static final int REFERENCE_FLAG_INDEX = 7;private static final int CALLER_FILENAME_INDEX = 8;private static final int CALLER_CLASS_INDEX = 9;private static final int CALLER_METHOD_INDEX = 10;private static final int CALLER_LINE_INDEX = 11;private static final StackTraceElement EMPTY_CALLER_DATA = CallerData.naInstance();// 处理主键的自动生成,这里我们使用手工生成,因此下面代码可忽略static {// PreparedStatement.getGeneratedKeys() method was added in JDK 1.4Method getGeneratedKeysMethod;try {// thegetGeneratedKeysMethod = PreparedStatement.class.getMethod("getGeneratedKeys", (Class[]) null);} catch (Exception ex) {getGeneratedKeysMethod = null;}GET_GENERATED_KEYS_METHOD = getGeneratedKeysMethod;}@Overridepublic void start() {insertSQL = buildInsertSQL();cnxSupportsBatchUpdates = connectionSource.supportsBatchUpdates();// super.start();super.started = true;}// 核心代码,构建插入语句,并对应数据库字段private static String buildInsertSQL() {return "INSERT INTO t_log_logback " +"(id, create_time, message, level_string, logger_name, thread_name, " +"reference_flag, caller_filename, caller_class, caller_method, caller_line) "+"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";}// 管理每个字段插入的数据private void bindLoggingEventWithInsertStatement(PreparedStatement stmt, ILoggingEvent event) throws SQLException {// TODO 手工处理ID的生成stmt.setString(ID_INDEX, IdUtils.simpleUUID());stmt.setString(CREATE_TIME_INDEX, LocalDateTime.ofInstant(Instant.ofEpochMilli(event.getTimeStamp()),ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));stmt.setString(MESSAGE_INDEX, event.getFormattedMessage());stmt.setString(LEVEL_STRING_INDEX, event.getLevel().toString());stmt.setString(LOGGER_NAME_INDEX, event.getLoggerName());stmt.setString(THREAD_NAME_INDEX, event.getThreadName());}// 管理每个字段插入的数据private void bindCallerDataWithPreparedStatement(PreparedStatement stmt, StackTraceElement[] callerDataArray) throws SQLException {StackTraceElement caller = extractFirstCaller(callerDataArray);stmt.setInt(REFERENCE_FLAG_INDEX, 0);stmt.setString(CALLER_FILENAME_INDEX, caller.getFileName());stmt.setString(CALLER_CLASS_INDEX, caller.getClassName());stmt.setString(CALLER_METHOD_INDEX, caller.getMethodName());stmt.setString(CALLER_LINE_INDEX, Integer.toString(caller.getLineNumber()));}// 核心方法,插入具体日志数据@Overrideprotected void subAppend(ILoggingEvent event, Connection connection, PreparedStatement insertStatement) throws Throwable {bindLoggingEventWithInsertStatement(insertStatement, event);// This is expensive... should we do it every time?bindCallerDataWithPreparedStatement(insertStatement, event.getCallerData());int updateCount = insertStatement.executeUpdate();if (updateCount != 1) {addWarn("Failed to insert loggingEvent");}}private StackTraceElement extractFirstCaller(StackTraceElement[] callerDataArray) {StackTraceElement caller = EMPTY_CALLER_DATA;if (hasAtLeastOneNonNullElement(callerDataArray))caller = callerDataArray[0];return caller;}private boolean hasAtLeastOneNonNullElement(StackTraceElement[] callerDataArray) {return callerDataArray != null && callerDataArray.length > 0 && callerDataArray[0] != null;}@Overrideprotected Method getGeneratedKeysMethod() {return GET_GENERATED_KEYS_METHOD;}@Overrideprotected String getInsertSQL() {return insertSQL;}protected void secondarySubAppend(ILoggingEvent event, Connection connection, long eventId) throws Throwable {}@Overrideprotected long selectEventId(PreparedStatement insertStatement, Connection connection) throws SQLException, InvocationTargetException {return 0;}
}
4. logback-spring.xml配置关联
添加自定义的追加器DbLogbackAppender
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false"><!-- 获取springboot中的数据库配置 --><springProperty scope="context" name="driverClassName" source="spring.datasource.master.driverClassName" defaultValue="driverClassName"/><springProperty scope="context" name="url" source="spring.datasource.master.url" defaultValue="url"/><springProperty scope="context" name="username" source="spring.datasource.master.username" defaultValue="username"/><springProperty scope="context" name="encryptPassword" source="spring.datasource.master.encryptPassword" defaultValue="encryptPassword"/><!-- 添加自定义DbLogbackAppender --><appender name="DB" class="com.xxx.DbLogbackAppender"><connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource"><driverClass>${driverClassName}</driverClass><url>${url}</url><user>${username}</user><password>${password}</password></connectionSource><!--临界值过滤。只记录指定级别以及高于该级别的日志--><filter class="ch.qos.logback.classic.filter.ThresholdFilter"><level>WARN</level></filter></appender><root level="DEBUGE"><appender-ref ref="DB" /></root>
</configuration>
到这里,就配置完成了,重启系统,访问日志,会将warn级别以上的日志记录到数据库表。
由于数据源是使用logback的LogbackConnectionSource,如果要自定义数据源,请往下看
5.自定义数据源
创建类LogbackConnectionSource.java,继承ConnectionSourceBase
import ch.qos.logback.core.db.ConnectionSourceBase;import java.sql.Connection;
import java.sql.SQLException;/*** <p>* 获取数据库连接用于logback,并提供DbLogbackAppender使用* 参考类:ch.qos.logback.core.db.DriverManagerConnectionSource* </p>*/
public class LogbackConnectionSource extends ConnectionSourceBase {public Connection getConnection() {获取数据库连接Connection connection = null;// 自行构建连接// ...return connection;}
}
修改logback-spring.xml配置为如下
<!-- 添加自定义DbLogbackAppender --><appender name="DB" class="com.xxx.DbLogbackAppender"><!-- 添加自定义的数据源 --><connectionSource class="com.xxx.LogbackConnectionSource"><!-- <driverClass>${driverClassName}</driverClass><url>${url}</url><user>${username}</user><password>${password}</password>--></connectionSource><!--临界值过滤。只记录指定级别以及高于该级别的日志--><filter class="ch.qos.logback.classic.filter.ThresholdFilter"><level>WARN</level></filter></appender>
相关文章:
Logback自定义DBAppender保存系统日志到数据库
在系统中采用了spring boot logback+slf4j的日志框架,将系统日志记录到数据库。 相关参考来源: 官方文档-DBAppender Logback输出日志到自定义MySQL数据库(重写DBAppender) logback日志框架中filter的使用 1. 添加依…...
云原生之使用Docker部署LimeSurvey在线调查工具
云原生之使用Docker部署LimeSurvey在线调查工具 一、LimeSurvey介绍1.1 LimeSurvey简介1.2 LimeSurvey特点1.3 LimeSurvey使用场景1.4 LimeSurvey支持版本二、本地环境介绍2.1 本地环境规划2.2 本次实践介绍三、本地环境检查3.1 检查Docker服务状态3.2 检查Docker版本3.检查doc…...
sdbusplus:添加ObjectManager interface
ObjectManager接口可以一次性拿到对象及子对象的所有property,在交互中经常会用到。 sdbusplus提供了add_manager完成该接口的添加: //server_obj.cpp #include <sdbusplus/asio/connection.hpp> #include <sdbusplus/asio/object_server.hpp> #include <sd…...
“RAID0 vs RAID1 vs RAID5 vs RAID6 vs RAID10:哪种RAID级别最适合你的需求?“
概要: RAID(Redundant Array of Independent Disks)是一种数据存储技术,可以将多个硬盘组合起来以提高性能、可靠性和容错能力。下面是几种常见的RAID级别,以及它们的用途和特点。 目录 RAID 0RAID 1RAID 5RAID 6RAID…...
【MySQL】Mycat
文章目录 什么是Mycat为什么要用Mycatmycat能干什么各数据库中间件对比Mycat原理数据库中间件逻辑库逻辑表分片表分片规则全局表ER表非分片表分片节点节点主机mycat安装mycat核心配置schema.xmlserver.xmlrule.xml加密明文密码(可选) MyCat读写分离垂直拆…...
Netty中ServerBootstrap类介绍
一、Netty基本介绍 Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。Netty 在保证易于开发的同时还保证了其应用的性能,稳定性和伸缩性。 Netty 是一…...
数字图像处理实验报告
目录 实验二、图像在空间域上的处理方法 实验三、图像在频率域上的处理方法 实验二、图像在空间域上的处理方法 一、实验目的 了解图像亮(灰)度变换与空间滤波的意义和手段;熟悉图像亮(灰)度变换与空间滤波的MATLA…...
【C51】10-基础51单片机的小车项目(51完结)
10.1小车的安装 10.2电机模块的开发(L9110S) 接通 VCC , GND 模块电源指示灯亮, 以下资料来源官方,但是不对,根据下节课实际调试 IA1 输入高电平, IA1 输入低电平,【 OA1 OB1 】电…...
进程、线程、锁阶段总结汇总
目录 进程 线程 锁 由于进程线程和锁的方面比较陌生,并且繁杂,所以简单总结一下学习到的函数API 进程 子进程创建 fork(); 进程结束 exit(); 进程回收 wait(); 进程回收 waitpad(); //函数可以指定进程组中的任意子进程,可以设置特殊…...
Filters.jar图片转素描
链接:https://pan.baidu.com/s/1ATlC2l1I83TPYFomHiWuFg?pwd2vm5 提取码:2vm5...
将MSYS2 MinGW集成到Windows终端
微软开发了一款Windows终端的开源软件,非常好用。安装后在Win7及以上系统会在右键菜单中添加一条“在终端中打开”的命令,非常方便。它默认配置了Windows命令行以及PowerShell,如果安装了Visual Studio 2022还会配置Visual Studio 2022的命令…...
SpringBoot项目使用slf4j的MDC日志打点功能
SpringBoot项目使用slf4j的MDC日志打点功能 物料准备: 1.自定义1个线程MDC打点工具类 2.配置logback打印MDC打点的traceId 3.配置webMVC使用MDC打点 4.配置ThreadPoolTaskExecutor使用MDC打点 5.配置HttpClient使用MDC打点 6.测试MDC日志打点效果 线程mdc打…...
宝塔修改默认端口后面板打不开
1、查看防火墙开启的端口,发现没有开启8888 [rootVM-12-12-centos ~]# firewall-cmd --list-ports 20/tcp 21/tcp 22/tcp 80/tcp 888/tcp 8081/tcp 39000-40000/tcp 8081/udp 2、防火墙开启8888端口 [rootVM-12-12-centos ~]# firewall-cmd --zonepublic --add-por…...
tinkerCAD案例:3.基本按钮
基本按钮 在本课中,您将学习制作具有圆柱形状的基本按钮。 说明 将圆柱体拖动到工作平面。 将其缩小到 2 毫米的高度。 提示: 您可以使用圆柱形状顶部的白点缩小圆柱体。 将其缩小到直径 16 毫米。 这将是按钮的主要形状。 现在我们可以创建允许将纽…...
客户线上反馈:从信息搜集到疑难 bug 排查全流程经验分享
写在前面:本文是我在前端团队的第三次分享,应该很少会有开发者写客户反馈处理流程以及 bug 排查的心得技巧,全文比较长,写了一个多星期大概1W多字(也是我曾经2年工作的总结),如果你有耐心阅读&a…...
悲观锁、乐观锁、自旋锁
悲观锁、乐观锁、自旋锁 (1)乐观锁 乐观锁是一种乐观的思想,即认为读多写少,遇到并发的可能性低,每次拿数据时都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有…...
七、进程地址空间
一、环境变量 (一)概念 环境变量(environment variables):系统当中用做特殊用途的系统变量。 如:我们在编写C/C代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可…...
浅谈智能微电网供电系统的谐波治理
摘要:智能微电网供电系统的特性容易引发谐波,而谐波导致电力损耗加大,降低供电质量。本文从谐波的产 生原因和危害做出详细阐述,并结合智能微电网提出了治 理谐波的方法和措施。 关键词:智能微电网;谐波危害…...
springboot项目的社区/博客系统
课前导读: 你学完一篇,你就多会一项技能,多多少少对你还是有点帮助的不是吗?~~~ 这是博主网页的url:优文共享社区 开发环境:JDK1.8,IDEA2021,MySQL5.7,Windows11 开发技术…...
go语言基础——函数、结构体、接口
由于go不是一门面向对象的语言,因此在有一些特性上和java是有一些区别的,比如go中就没有类这样的概念。下面来介绍一下go的一些特性。 结构体 结构体类似与java中的类,但又不完全一样。在类中,可以定义字段和方法,但…...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...
日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻
在如今就业市场竞争日益激烈的背景下,越来越多的求职者将目光投向了日本及中日双语岗位。但是,一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧?面对生疏的日语交流环境,即便提前恶补了…...
SkyWalking 10.2.0 SWCK 配置过程
SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外,K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案,全安装在K8S群集中。 具体可参…...
超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...
.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
python/java环境配置
环境变量放一起 python: 1.首先下载Python Python下载地址:Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个,然后自定义,全选 可以把前4个选上 3.环境配置 1)搜高级系统设置 2…...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...
使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装
以下是基于 vant-ui(适配 Vue2 版本 )实现截图中照片上传预览、删除功能,并封装成可复用组件的完整代码,包含样式和逻辑实现,可直接在 Vue2 项目中使用: 1. 封装的图片上传组件 ImageUploader.vue <te…...
跨链模式:多链互操作架构与性能扩展方案
跨链模式:多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈:模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展(H2Cross架构): 适配层…...
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...
