当前位置:   article > 正文

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

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>
  • 1
  • 2
  • 3
  • 4
  • 5

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系统日志记录表';
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

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;
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132

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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

到这里,就配置完成了,重启系统,访问日志,会将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;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

修改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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/312769
推荐阅读
相关标签
  

闽ICP备14008679号