自定义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

相关推荐
腥臭腐朽的日子熠熠生辉37 分钟前
解决maven失效问题(现象:maven中只有jdk的工具包,没有springboot的包)
java·spring boot·maven
ejinxian39 分钟前
Spring AI Alibaba 快速开发生成式 Java AI 应用
java·人工智能·spring
杉之44 分钟前
SpringBlade 数据库字段的自动填充
java·笔记·学习·spring·tomcat
圈圈编码1 小时前
Spring Task 定时任务
java·前端·spring
俏布斯1 小时前
算法日常记录
java·算法·leetcode
27669582921 小时前
美团民宿 mtgsig 小程序 mtgsig1.2 分析
java·python·小程序·美团·mtgsig·mtgsig1.2·美团民宿
爱的叹息1 小时前
Java 连接 Redis 的驱动(Jedis、Lettuce、Redisson、Spring Data Redis)分类及对比
java·redis·spring
程序猿chen2 小时前
《JVM考古现场(十五):熵火燎原——从量子递归到热寂晶壁的代码涅槃》
java·jvm·git·后端·java-ee·区块链·量子计算
松韬2 小时前
Spring + Redisson:从 0 到 1 搭建高可用分布式缓存系统
java·redis·分布式·spring·缓存
绝顶少年2 小时前
Spring Boot 注解:深度解析与应用场景
java·spring boot·后端