自定义log4j2的Appender来获取日志内容

springboot版本:2.6.15 log4j版本:2.17.2

Log4j中的Appender 是什么

本文讲述的是通过 log4j 中自定义的 Appender 来获取需要打印的日志信息,那么 Appender 是什么东西呢?先简单了解一下

在 Log4j2 中,Appender 是负责将日志事件输出到目标地点的组件。Appender 可以将日志事件输出到控制台、文件、网络等不同的目的地。 Log4j2 提供了多种内置的 Appender,例如 ConsoleAppender、FileAppender、AsyncAppender 等,同时支持自定义 Appender。 Appender 的主要职责是将日志事件按照指定的格式和目的地输出。每个 Appender 都有自己的名称和类型,可以根据需要配置不同的属性。例如,FileAppender 用于将日志事件输出到文件中,其属性包括文件名、追加模式(是否覆盖已存在的文件或追加到文件末尾)等。

需求背景

有两个系统,系统 A 和系统 B, 系统 A 会发送指令给系统 B, 系统 B 会响应结果给系统 A,这两个系统之间的通信方式是 MQ, 即 A 通过 MQ 发送数据给 B(数据中有 UUID 字段值),系统 B处理完成后将结果通过 MQ 发送给 A,请求和响应的对应关系则可以通过 UUID 来进行匹配

现在有一个需求是监控在某个时间段内发送了多少指令,以及多少指令失败(在给定时间内没有收到响应或者响应中给了错误结果认为失败)

对于这种需求一般都是通过 AOP 来解决,但是在部分场景下 AOP 也比较难处理,比如上面场景中其实这个监控处理是有两部分的,第一部分是发送请求,第二部分是获取请求对应的结果,而且不是 http 调用,是通过 MQ 调用,即业务逻辑实现是分布在两个方法中的 (可以抽取一层将发送请求和响应结果逻辑放在一起,并且返回原始结果即可)

下面基于已有实现提供另外一种更简单的解决方式,已有实现的特点:

  • 指令发送和结果接收都被抽取成独立的方法,只要是两个系统间的 MQ 通信最终都会调用这两个方法(一个发送,一个接收结果)
  • 在这两个方法中都将原始的参数以及最终的响应结果给打印出来了 基于以上两点,如果可以拿到打印的日志信息就可以比较简单汇总处理

修改前测试代码

log4j2 配置文件

下面 log4j2-dev.xml 配置文件很简单,就是将所有日志打印到控制台,日志级别为 info

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>  
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->  
<configuration monitorInterval="60">  
    <Properties>  
        <property name="LOG_PATTERN_CONSOLE"  
                  value="%clr{%d{yyyy-MM-dd HH:mm:ss.SSS}}{faint} %clr{%5p} %clr{${sys:PID}}{magenta} %clr{---}{faint} %clr{[%15.15t]}{faint} %clr{%-40.40c{1.}}{cyan} %clr{:}{faint} %m%n%xwEx"/>  
    </Properties>  
  
    <appenders>  
        <!-- Console 是将日志信息打印打控制台-->  
        <console name="Console" target="SYSTEM_OUT">  
            <PatternLayout pattern="${LOG_PATTERN_CONSOLE}"/>  
            <ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY"/>  
        </console>  
    </appenders>  
  
    <loggers>    
        <root level="info">  
            <AppenderRef ref="Console"/>  
        </root>  
    </loggers>  
  
</configuration>

两个测试类代码

给出两个测试类是为了对比,后续自定义的 Appender 只会作用在一个测试类中,对于其他类的日志打印不会获取到对应的日志信息

java 复制代码
@Component  
public class DemoService {  
    private static final Logger logger = LoggerFactory.getLogger(DemoService.class);  
    public void doSomething(String param) {  
        logger.info("test demo");  
    }  
}

@Component  
public class DemoService2 {  
    private static final Logger logger = LoggerFactory.getLogger(DemoService2.class);  
    public void doSomething(String param) {  
        logger.info("test demo 2");  
    }  
}

自定义Appender

自定义的 Appender 不需要被 Spring 管理,所以不需要 @Component 等注解

java 复制代码
package com.example;  
  
import org.apache.logging.log4j.core.Appender;  
import org.apache.logging.log4j.core.Filter;  
import org.apache.logging.log4j.core.Layout;  
import org.apache.logging.log4j.core.LogEvent;  
import org.apache.logging.log4j.core.appender.AbstractAppender;  
import org.apache.logging.log4j.core.config.plugins.Plugin;  
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;  
import org.apache.logging.log4j.core.config.plugins.PluginElement;  
import org.apache.logging.log4j.core.config.plugins.PluginFactory;  
import org.apache.logging.log4j.core.layout.PatternLayout;  
  
import java.io.Serializable;  
  
@Plugin(name = "CusAppender", category = "Core", elementType = Appender.ELEMENT_TYPE)  
public class CustomAppender extends AbstractAppender {  
  
    protected CustomAppender(String name, Filter filter, Layout<? extends Serializable> layout) {  
        super(name, filter, layout);  
    }  
  
    @Override  
    public void append(LogEvent event) {  
        // 在这里获取日志信息  
        String message = event.getMessage().getFormattedMessage();  
        // 打印日志信息  
        System.out.println("拦截到的消息" + message);  
    }  
  
    @PluginFactory  
    public static CustomAppender createAppender(@PluginAttribute("name") String name,  
                                                @PluginElement("Layout") Layout<? extends Serializable> layout) {  
        if (name == null) {  
            throw new IllegalArgumentException("No name provided for CustomAppender");  
        }  
        if (layout == null) {  
            layout = PatternLayout.createDefaultLayout();  
        }  
        return new CustomAppender(name, null, layout);  
    }  
}

将自定义 Appender 配置到 Log4j 配置文件中

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>  
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->  
<configuration monitorInterval="60">  
    <Properties>  
        <property name="LOG_PATTERN_CONSOLE"  
                  value="%clr{%d{yyyy-MM-dd HH:mm:ss.SSS}}{faint} %clr{%5p} %clr{${sys:PID}}{magenta} %clr{---}{faint} %clr{[%15.15t]}{faint} %clr{%-40.40c{1.}}{cyan} %clr{:}{faint} %m%n%xwEx"/>  
    </Properties>  
  
    <appenders>  
	    <!-- 添加自定义的 Appender -->  
        <CusAppender name="CustomAppender" />  
        <!-- Console 是将日志信息打印打控制台-->  
        <console name="Console" target="SYSTEM_OUT">  
            <PatternLayout pattern="${LOG_PATTERN_CONSOLE}"/>  
            <ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY"/>  
        </console>  
    </appenders>  
  
    <loggers>  
        <!-- 对某个具体类进行单独的配置, 需要关注 additivity 参数-->  
        <Logger name="com.example.DemoService2" level="info" additivity="true">  
            <AppenderRef ref="CustomAppender"/>  
        </Logger>  
  
        <root level="info">  
            <AppenderRef ref="Console"/>  
        </root>  
    </loggers>  
  
</configuration>

上述配置文件针对原始配置文件主要有两个修改点

  1. <appenders> 标签下面添加了 <CusAppender name="CustomAppender" /> 标签, 这里使用 CusAppender 标签是因为 @Plugin(name = "CusAppender" 中配置的是这个名字
  2. <loggers> 标签下添加了如下内容
xml 复制代码
<!-- 对某个具体类进行单独的配置, 需要关注 additivity 参数-->  
<Logger name="com.example.DemoService2" level="info" additivity="true">  
	<AppenderRef ref="CustomAppender"/>  
</Logger>  

通过这种方式,当 DemoService2 类有日志打印并且级别在 info 及以上时就会调用到自定义 Appenderappend 方法中

java 复制代码
public void append(LogEvent event) {  
        // 在这里获取日志信息  
	String message = event.getMessage().getFormattedMessage();  
	// 打印日志信息  
	System.out.println("拦截到的消息" + message);  
}  

additivity="true" 配置的作用

先看下 loggers 配置

xml 复制代码
<loggers>  
	<!-- 对某个具体类进行单独的配置, 需要关注 additivity 参数-->  
	<Logger name="com.example.DemoService2" level="info" additivity="true">  
		<AppenderRef ref="CustomAppender"/>  
	</Logger>  

	<root level="info">  
		<AppenderRef ref="Console"/>  
	</root>  
</loggers>  

这里的 root 就是顶层的配置,对于 DemoService2 类来说,因为单独配置了 Logger, 所以会先走单独配置的 Logger 中的 Appender, 也就是 CustomAppender,但是 CustomAppender 中只是获取日志信息,并没有往控制台或者文件写日志,这样日志就会丢失了,additivity 参数就是用来控制是否继续使用父级的 Appender, additivity=true 时,日志会先经过 CustomAppender 处理,然后会让父级( root ) 的 Console 处理。

结果

先调用 DemoService2 方法,然后调用 DemoService 的方法,日志打印如下

yaml 复制代码
拦截到的消息test demo 2
2024-02-05 16:48:21.641  INFO 2760 --- [command-execute] c.e.DemoService2                         : test demo 2
2024-02-05 16:48:34.424  INFO 2760 --- [command-execute] c.e.DemoService                          : test demo

说明只有调用了 DemoService2 类中日志方法时才会调用到自定义的 Appender

相关推荐
星河梦瑾1 小时前
SpringBoot相关漏洞学习资料
java·经验分享·spring boot·安全
黄名富1 小时前
Redis 附加功能(二)— 自动过期、流水线与事务及Lua脚本
java·数据库·redis·lua
love静思冥想1 小时前
JMeter 使用详解
java·jmeter
言、雲1 小时前
从tryLock()源码来出发,解析Redisson的重试机制和看门狗机制
java·开发语言·数据库
TT哇1 小时前
【数据结构练习题】链表与LinkedList
java·数据结构·链表
Yvemil72 小时前
《开启微服务之旅:Spring Boot 从入门到实践》(三)
java
Anna。。2 小时前
Java入门2-idea 第五章:IO流(java.io包中)
java·开发语言·intellij-idea
.生产的驴2 小时前
SpringBoot 对接第三方登录 手机号登录 手机号验证 微信小程序登录 结合Redis SaToken
java·spring boot·redis·后端·缓存·微信小程序·maven
爱上语文2 小时前
宠物管理系统:Dao层
java·开发语言·宠物
王ASC2 小时前
SpringMVC的URL组成,以及URI中对/斜杠的处理,解决IllegalStateException: Ambiguous mapping
java·mvc·springboot·web