Logback自定义DBAppender输出日志

Logback自定义DBAppender输出日志

虽然解决的办法不难,但是说实在我解决的过程挺曲折的。先说背景吧!我有一个需求:需要输出日志到数据库,但是我觉得logback官方给的那三张表太详细了,我想自定义方法去输出。于是便有了下面的折腾。

我在网络上找到的配置方法是通过继承DBAppenderBase<ILoggingEvent> 的方式去解决的,但是需要引入的包是

<!-- logback -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.2.3</version>
        </dependency>

因为包的版本是1.2.3,而logback最近一次爆出漏洞修复是在1.2.9。所以我把包引入的版本升级为1.2.9

<!-- logback -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.9</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.2.9</version>
        </dependency>

但是却发现DBAppenderBase<ILoggingEvent>爆红,我进入jar反编译的源码里面查看也没有对应的类。然后我就去搜索了一下,CSDN和掘金似乎都没有相应的解答。于是我很奇怪,就去官方网站找
logback官网api文档
解果就是没找到
开源软件应该在github上有人提过issue吧?
于是上到github找
logback的github地址
依然是没在issue中找到。最后是怎么解决的呢?
我带着问题去stackoverflow里面找,终于发现了问题的答案: 原来有关JDBC的依赖Logback已经在1.2.8就移除了,并且需要额外导入。
答案链接:
解决办法
他是怎么知道的呢?答案在Logback官网的news中。
logback官网的news
其中我截取三段有关的

2021-12-14 Release of version 1.2.8
? In response to CVE-2021-42550 and LOGBACK-1591 we have decided to make the following steps.

1) we have disabled all JNDI lookup code in logback until further notice. This impacts ContextJNDISelector and <insertFromJNDI> element in configuration files.

2) we have removed all database (JDBC) related code in the project with no replacement.
  • 可以看到,第一段logback先是出于安全性考虑,移除了所有JDBC代码在上面的依赖中
2021-12-16 Release of version 1.2.9
We note that the vulnerability mentioned in CVE-2021-42550 requires write access to logback's configuration file as a prerequisite. Please understand that log4Shell and CVE-2021-42550 are of different severity levels.
In response to CVE-2021-42550 (aka LOGBACK-1591) we have decided to make the following steps.

1) Hardened logback's JNDI lookup mechanism to only honor requests in the java: namespace. All other types of requests are ignored. Many thanks to Michael Osipov for suggesting this change and providing the relevant PR.

2) SMTPAppender was hardened.

3) Temporarily removed DB support for security reasons.

4) Removed Groovy configuration support. As logging is so pervasive and configuration with Groovy is probably too powerful, this feature is unlikely to be reinstated for security reasons.

We note that the aforementioned vulnerability requires write access to logback's configuration file as a prerequisite. Please understand that log4Shell/CVE-2021-44228 and CVE-2021-42550 are of different severity levels. A successful RCE attack with CVE-2021-42550 requires all of the following conditions to be met:

write access to logback.xml
use of versions < 1.2.9
reloading of poisoned configuration data, which implies application restart or scan="true" set prior to attack
As an additional extra precaution, in addition to upgrading to logback version 1.2.9, we also recommend users to set their logback configuration files as read-only.
  • 然后对于漏洞,logback指出只需要把依赖升级到1.2.9或者是把logback.xml更换成只读权限就可以避免了。
2022-04-20 Release of logback.db version 1.2.11.1
As of logback version 1.2.8 DBAppender no longer ships with logback. However, DBAppender for logback-classic is available under the following Maven coordinates:
   ch.qos.logback.db:logback-classic-db:1.2.11.1
and for logback-access under
   ch.qos.logback.db:logback-access-db:1.2.11.1
Both of these artifacts require ch.qos.logback.db:logback-core-db:1.2.11.1 which will be pulled in automatically by Maven's transitivity rules.
This release corrects the artifact name of logback-classic-db fixing LOGBACK-1631 as reported by Juan Pablo Santos Rodriguez.
  • 最后,logback重新给出了db的单独的依赖包,至此问题解决。
    最后,单独给出解决办法
解决办法
<!-- Slf4j -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.32</version>
        </dependency>

        <!-- logback -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.9</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback.db</groupId>
            <artifactId>logback-core-db</artifactId>
            <version>1.2.11</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.2.9</version>
        </dependency>

自定义DBAppender代码部分

自定义的DBAppender需要继承DBAppenderBase<ILoggingEvent>,具体代码如下:

public class MyDbAppender extends DBAppenderBase<ILoggingEvent> {
    /**
     * 插入语句
     */
    protected static final String INSERT_SQL;
    /**
     * 用于在执行插入语句后获取生成的主键
     */
    protected static final Method GET_GENERATED_KEYS_METHOD;

    /**
     * 日志表中的字段,这里只是举个例子
     */
    static final int LOG_TYPE = 1;

    static {
        Method getGeneratedKeysMethod;
        try {
            getGeneratedKeysMethod = PreparedStatement.class.getMethod("getGeneratedKeys", (Class[]) null);
        } catch (Exception ex) {
            getGeneratedKeysMethod = null;
        }
        GET_GENERATED_KEYS_METHOD = getGeneratedKeysMethod;
        INSERT_SQL = "INSERT INTO t_log (log_type) VALUES (?)";
    }

    /**
     * subAppend 方法用于实际执行日志的插入操作。它从传入的 ILoggingEvent 中提取信息,创建 Log 对象,并使用预编译的语句将日志信息插入数据库
     * @param event
     * @param connection
     * @param insertStatement
     * @throws Throwable
     */
    @Override
    protected void subAppend(ILoggingEvent event, Connection connection, PreparedStatement insertStatement) throws Throwable {

        // 预编译语句装载
        insertStatement.setInt(LOG_TYPE, log.getLogType());
        // 执行插入日志语句
        int updateCount = insertStatement.executeUpdate();
        if (updateCount != 1) {
        // 执行处理失败的逻辑
        }
    }

    /**
     * secondarySubAppend 方法用于在插入日志后执行其他操作,比如获取生成的主键
     * @param event
     * @param connection
     * @param eventId
     */
    @Override
    protected void secondarySubAppend(ILoggingEvent event, Connection connection, long eventId) {

    }

    /**
     * getGeneratedKeysMethod 方法用于获取生成的主键
     * @return
     */
    @Override
    protected Method getGeneratedKeysMethod() {
        return GET_GENERATED_KEYS_METHOD;
    }

    /**
     * getInsertSQL 方法用于获取插入语句
     * @return
     */
    @Override
    protected String getInsertSQL() {
        return INSERT_SQL;
    }
}
同时,要配置好自己的logback.xml
<?xml version = "1.0" encoding = "UTF-8" ?>
<configuration debug="false">
    <!--定义日志文件的存储地址-->
    <!-- TODO: 这里需要根据自己的存储地址更改 -->
    <property name="LOG_HOME" value="A:\" />
    <property name="CONSOLE_LOG_PATTERN"
              value="%highlight(%d{yyyy-MM-dd HH:mm:ss}) | %-5level | %blue(%thread) | %blue(%file:%line) | %green(%logger) >> %cyan(%msg%n)"/>
    <property name="FILE_LOG_PATTERN"
              value="%d{yyyy-MM-dd HH:mm:ss} | %-5level | %thread | %file:%line | %logger : %msg%n"/>
    //这里我用的是本机的环境变量,建议拿自己的环境变量来替换。当然也可以用自己的用户名密码替换""中的值
    <property name="mysqlUsername" value="${MYSQL_USERNAME}" />
    <property name="mysqlPassword" value="${PASSWORD}" />

    <!-- 控制台输出 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <!-- 输出日志到数据库 -->
    <!-- 使用自己重写后的 Appender -->

    <appender name="DB" class="com.qgstudio.newer.log.MyDbAppender">
        <connectionSource class="ch.qos.logback.core.db.DataSourceConnectionSource">
            <!-- 连接池 -->
            <dataSource class="com.alibaba.druid.pool.DruidDataSource">
                <driverClassName>com.mysql.cj.jdbc.Driver</driverClassName>
                <!-- 改成自己的url -->
                <url>jdbc:mysql://自己的url?serverTimezone=UTC</url>
                <username>${mysqlUsername}</username>
                <password>${mysqlPassword}</password>
            </dataSource>
        </connectionSource>
    </appender>

    <!-- 输出到文件 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--设置策略-->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件路径:这里%d{yyyyMMdd}表示按天分类日志-->
            <FileNamePattern>${LOG_HOME}/%d{yyyyMMdd}.log</FileNamePattern>
        </rollingPolicy>
        <!--设置格式-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${FILE_LOG_PATTERN}</pattern>
            <charset>utf8</charset>
        </encoder>
    </appender>

    <!-- 提高日志等级,过滤数据库无用信息 -->
    <logger name="org.springframework" level="ERROR" ref="DB"/>
    <logger name="com.alibaba.druid" level="ERROR" ref="DB"/>
    <logger name="springfox.documentation" level="ERROR" ref="DB"/>
    <logger name="org.apache" level="ERROR" ref="DB"/>

    <!-- 日志输出级别 -->
    <root level="INFO">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="DB"/>
        <appender-ref ref="FILE"/>
    </root>
</configuration>

问题集锦

其中自己第一次做肯定会遇到很多问题,下面我就把我遇到的坑做了一个整理,如果配置有问题了可以回来看看。

  • 问题一:一直运行就是没有出结果
    在这里插入图片描述

回答: 这种情况可以有多种原因导致
1.logback.xml配置错误,尤其是输出到数据库的那条配置,看看有没有把数据库的链接写错,或者用户名和密码填错。填错就会导致控制台在等待DB连接上才能输出日志,所以才会一直加载。
2.检查过了没检查出来,应该怎么办? 可以注释掉一部分你觉得可能有问题的xml配置,然后看看控制台有没有输出日志。

  • 问题二:控制台有日志,但是表里面就是没有插入的日志信息
    回答:可能的原因有以下:
    1.数据库中的日志表的主键是不是没设置自动递增,没有设置会造成无法插入日志。
    2.检查一下装填进入sql的值,是不是有为空值的,如果有就会无法插入,空值最好用其他替代比如null。

如果有遇到其他问题的可以在评论区补充哦!!