Logback自定义DBAppender保存系统日志到数据库

news2024/11/27 1:38:13

在系统中采用了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.4
        Method getGeneratedKeysMethod;
        try {
            // the
            getGeneratedKeysMethod = PreparedStatement.class.getMethod("getGeneratedKeys", (Class[]) null);
        } catch (Exception ex) {
            getGeneratedKeysMethod = null;
        }
        GET_GENERATED_KEYS_METHOD = getGeneratedKeysMethod;
    }

    @Override
    public 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()));
    }

	// 核心方法,插入具体日志数据
    @Override
    protected 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;
    }

    @Override
    protected Method getGeneratedKeysMethod() {
        return GET_GENERATED_KEYS_METHOD;
    }

    @Override
    protected String getInsertSQL() {
        return insertSQL;
    }

    protected void secondarySubAppend(ILoggingEvent event, Connection connection, long eventId) throws Throwable {
    }

    @Override
    protected 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>

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

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

相关文章

【新版】系统架构设计师 - 系统配置与性能评价

个人总结&#xff0c;仅供参考&#xff0c;欢迎加好友一起讨论 文章目录 架构 - 系统配置与性能评价考点摘要系统性能概述性能指标性能调整阿姆达尔解决方案性能评价方法 架构 - 系统配置与性能评价 考点摘要 性能指标&#xff08;★★&#xff09;阿姆达尔解决方案&#xff…

java SSM 教师管理系统myeclipse开发mysql数据库springMVC模式java编程计算机网页设计

一、源码特点 java SSM 教师管理系统是一套完善的web设计系统&#xff08;系统采用SSM框架进行设计开发&#xff0c;springspringMVCmybatis&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和 数据库&#xff0c;系统主要采用B…

工业深度学习软件 从标注 训练 到测试 再到现场部署

工业深度学习软件 从标注 训练 到测试 再到现场部署 M7000技术规格表 Producer Specification 影像系统 Imaging Sys 适配相机 supported cameras 支持海康&#xff0c;迈德威视&#xff0c;度申2D相机&#xff08;可根据需求增加适配其他厂家相机&#xff09; Support for Hi…

22AP30 H.265 编解码处理器

22AP30 H.265 编解码处理器 主要特点 SVP&#xff08;Smart Vision Processing&#xff09;  图像分析工具推理引擎&#xff08;NNIE&#xff09; − 支持多种图像分析工具 − 1.2Tops运算性能 处理器内核  ARM Cortex A53 四核1.15GHz − 32KB L1 I-Cache&#xff0c;32KB…

小程序页面事件与wxs脚本

文章和代码已经归档至【Github仓库&#xff1a;https://github.com/timerring/front-end-tutorial 】或者公众号【AIShareLab】回复 小程序 也可获取。 文章目录 小程序视图与逻辑页面导航声明式导航编程式导航导航传参 页面事件下拉刷新事件上拉触底事件上拉触底案例 自定义编…

一级建造师执业资格考试--工程管理--速学36记--联想法

第一记&#xff1a;项目管理 第二记&#xff1a;项目管理的核心 第三记&#xff1a;项目总承包方的工作程序 第四记&#xff1a;项目质量控制体系建立 第五记&#xff1a;项目质量控制体系运行 第六记&#xff1a;施工过程质量验收不合格的处理方法 第七记&#xff1a;装配式混…

33:避免遮掩继承而来的名称

我们都知道在下面的代码中&#xff1a; int x;//global变量 void someFunc() {double x;//local变量cin >> x;//读一个新值赋予local变量x } 上述读取数据的语句指涉的是local变量x&#xff0c;而不是global变量x&#xff0c;因为内层作用域的名称会遮掩外围作用域的名…

提示词工程让儿童编程轻而易举

编写长长的代码时常令人头疼。尤其是小朋友&#xff0c;打字不快&#xff0c;但想象力丰富。 现在借助chatgpt。 一切变得超级简单。 1. https://github.com/roocell/gptCozmo 2. https://github.com/Bhood23/CozmoGPT while True:from pyChatGPT import ChatGPTimport speec…

1427205-93-3,Fmoc-Ser(Ac4Manα1-2Ac3Manα1-2Ac3Manα)-OH:一种糖基化蛋白

Fmoc-Ser(Ac4Manα1-2Ac3Manα1-2Ac3Manα)-OH&#xff0c;其CAS号为1427205-93-3&#xff0c;英文名为Fmoc-Ser(Ac4Manα1-2Ac3Manα1-2Ac3Manα)-OH。分子式为C56H67NO30&#xff0c;分子量为1234.13&#xff0c;纯度标准为95%。外形颜色为固体或粉末&#xff0c;包装规格有1…

重命名文件名 | 一键导出表格,让您的文件快速整理归档

您是否因为大量文件命名混乱、找文件困难而感到苦恼&#xff1f;现在有一个好消息&#xff0c;文件批量改名高手帮你解决&#xff01;我们提供最简单、最快捷的方式来统一您的文件命名&#xff0c;无论是照片、文档或是音视频文件&#xff0c;只需一键即可完成重命名。而且我们…

华为OD机试真题 Java 实现【观看文艺汇演问题】【2023 B卷 100分】,附详细解题思路

一、题目描述 为庆祝中国共产党成立100周年&#xff0c;某公园将举行多场文艺汇演&#xff0c;很多演出都是同时进行。 一个人只能同时观看一场演出&#xff0c;且不能迟到早退。由于演出分散在不同的演出场地&#xff0c;所以连续观看的演出最少要有15分钟的时间间隔。 小明…

二、Drools WorkBench

一、WorkBench 简介 WorkBench 是 KIE 组件中的元素&#xff0c;也称为 KIE-WB&#xff0c;是 Drools-WB 与 JBPM-WB 的结合体。它是一个可视化的规则编辑器。WorkBench 其实就是一个 war 包&#xff0c;安装到 Tomcat 中就可以运行。使用 WorkBench 可以在浏览器中 创建数据对…

基于jsp+mysql+Spring+mybatis+Springboot的Springboot实现的就业信息管理平台

运行环境: 最好是java jdk 1.8&#xff0c;我在这个平台上运行的。其他版本理论上也可以。 IDE环境&#xff1a; Eclipse,Myeclipse,IDEA或者Spring Tool Suite都可以&#xff0c;如果编译器的版本太低&#xff0c;需要升级下编译器&#xff0c;不要弄太低的版本 tomcat服务器环…

教你如何使用自定义测试(Minium)进行微信小程序自动化测试

目录 前言&#xff1a; 一、 编写用例 基本操作 处理小程序API 处理小程序原生控件 数据驱动 二、 执行用例 本地执行 云测服务测试 三、 最佳实践 四、总结 前言&#xff1a; 微信小程序自动化测试是保障小程序质量的重要手段&#xff0c;Minium是一个可为微信小程…

kafka 对 java NIO 的封装

说明 本文基于 kafka 2.7 编写。author JellyfishMIX - github / blog.jellyfishmix.comLICENSE GPL-2.0 java NIO 组件 几个 java NIO 的组件。 Buffer: 缓冲区。这是一个接口&#xff0c;kafka 用它的 ByteBuffer 实现类&#xff0c;配合 SocketChannel 实现读写操作。读…

区块链产业快速发展 和数集团开启区块链应用新时代

UTONMOS区块链游戏要来了。 就在5月底&#xff0c;UTONMOS品牌所属公司上海和数集团在泰国发布了【神念无界】系列的多款国际版链游&#xff0c;包括【神念无界-源起山海】、【北荒传奇】、【神宠岛】、【神农园】等区块链游戏。 以【神念无界-源起山海】为例&#xff0c;其是…

Web、容器化 Native、小程序跨平台!三种跨平台方案对比

前端码农工作几年&#xff0c;从一家公司跳到另一家公司&#xff0c;永远逃不掉的是跨平台需求。除了本身应用在多平台上架的需求之外&#xff0c;资源有限恐怕是最大的原因&#xff0c;跨平台方案确实可以减少重复开发工作&#xff0c;降低成本和节省时间&#xff1b;而且掌握…

从开源到云原生,时序数据库 TDengine 六年回顾精彩纷呈

2023 年 6 月 6 日&#xff0c;涛思数据旗下时序数据库&#xff08;Time Series Database&#xff09; TDengine 迎来六周年庆典&#xff0c;并于北京保利国际广场T2举办了主题为“TDengine 6th Anniversary&#xff1a;Back to The Future”的庆典活动&#xff0c;设置了「TDe…

《Contrastive Learning for Unpaired Image-to-Image Translation》

Contrastive Learning for Unpaired Image-to-Image Translation 1. 摘要2. 介绍3. 相关工作3.1 图像转换、循环一致性3.2 关系保持3.3 深度网络嵌入中的感知相似性3.4 对比表示学习 4. 方法 原文及代码链接 https://github.com/taesungp/contrastive-unpaired-translation 1.…

API之Apifox和Postman工具该如何抉择?

目录 前言 一.功能列表对比 &#xff08;一&#xff09;接口设计与文档管理功能 &#xff08;二&#xff09;接口调试功能对比 &#xff08;三&#xff09;接口mock功能 &#xff08;四&#xff09;接口测试功能 二.团队协作功能 三.Apifox 没有的功能 四.产品价格 前…