1. 简介

本文将介绍如何为 Log4j2 创建一个自定义的 Appender。如果你还不熟悉 Log4j2 的基本概念,可以先阅读我们之前的这篇文章

Log4j2 内置了大量 Appender,支持日志输出到文件、数据库、Socket 甚至 NoSQL 存储,覆盖绝大多数常见场景。

但实际开发中,业务需求千奇百怪,比如你想把日志推送到消息队列、写入 Redis,或者做轻量级内存缓存用于调试——这时候就得自己动手写一个自定义 Appender 了。

⚠️ 注意:Log4j2 是 Log4j 的升级版,性能和扩展性都有显著提升。本文基于 Log4j2 演示,别再用老掉牙的 Log4j 了。


2. Maven 依赖配置

要使用 Log4j2 的核心功能,首先在 pom.xml 中引入 log4j-core

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.19.0</version>
</dependency>

📌 最新版本可前往 Maven Central 查看。


3. 自定义 Appender 实现

实现自定义 Appender 有两种方式:

  • ✅ 实现 Appender 接口
  • ✅ 继承 AbstractAppender 类(推荐)

我们推荐第二种,AbstractAppender 已经帮你处理了线程安全、过滤器、名称管理等通用逻辑,简单粗暴又省事。

示例:MapAppender

我们来实现一个 MapAppender,它的功能是把日志事件(LogEvent)存进一个 ConcurrentHashMap,以时间戳为 key,便于后续程序读取或调试。

@Plugin(
  name = "MapAppender", 
  category = Core.CATEGORY_NAME, 
  elementType = Appender.ELEMENT_TYPE)
public class MapAppender extends AbstractAppender {

    private ConcurrentMap<String, LogEvent> eventMap = new ConcurrentHashMap<>();

    protected MapAppender(String name, Filter filter) {
        super(name, filter, null);
    }

    @PluginFactory
    public static MapAppender createAppender(
      @PluginAttribute("name") String name, 
      @PluginElement("Filter") Filter filter) {
        return new MapAppender(name, filter);
    }

    @Override
    public void append(LogEvent event) {
        eventMap.put(Instant.now().toString(), event);
    }
}

关键点解析:

  • @Plugin 注解:告诉 Log4j2 这是个插件,必须加上。
    • name:配置文件中使用的 Appender 名称
    • category:固定为 Core.CATEGORY_NAME
    • elementType:设为 Appender.ELEMENT_TYPE
  • @PluginFactory:工厂方法,用于创建 Appender 实例。Log4j2 在解析配置时会调用它。
  • ✅ 构造函数:调用父类构造器,传入 name 和 filter,layout 设为 null 表示使用默认布局。
  • append(LogEvent):核心逻辑,每次日志输出都会调用这个方法。

⚠️ 踩坑提醒:@PluginFactory 方法必须是 public static,参数用 @PluginAttribute@PluginElement 标注,否则 Log4j2 找不到你的 Appender!


4. 配置文件使用

有了自定义 Appender,接下来在 log4j2.xml 中配置它。

1. 启用插件扫描

<Configuration xmlns:xi="http://www.w3.org/2001/XInclude" 
               packages="com.baeldung" 
               status="WARN">

📌 packages="com.baeldung" 是关键!Log4j2 会扫描这个包下的所有 @Plugin 类。确保你的 MapAppender 在这个包路径下,否则插件不会被加载。

2. 定义 Appender

<MapAppender name="MapAppender" />

名字要和 @Plugin(name = "MapAppender") 保持一致。

3. 在 Logger 中引用

<Root level="DEBUG">
    <AppenderRef ref="MapAppender" />
</Root>

这样所有日志都会经过 MapAppender,并被存入内存 Map。


5. 错误处理

自定义 Appender 也要考虑异常情况。AbstractAppender 提供了 error() 方法,可用于记录内部错误。

比如我们想限制只记录 WARN 及以上级别的日志,低于的直接丢弃并报错:

@Override
public void append(LogEvent event) {
    if (event.getLevel().isLessSpecificThan(Level.WARN)) {
        error("Unable to log less than WARN level.");
        return;
    }
    eventMap.put(Instant.now().toString(), event);
}

error() 方法会把错误信息输出到 Log4j2 内部日志(由 status 控制),方便排查问题。


6. 总结

本文演示了如何创建一个简单的自定义 Log4j2 Appender:

  • ✅ 使用 @Plugin@PluginFactory 注解注册插件
  • ✅ 继承 AbstractAppender 简化实现
  • ✅ 在 log4j2.xml 中通过 packages 扫描并使用
  • ✅ 合理使用 error() 处理异常

虽然 Log4j2 提供了丰富的内置 Appender,但自定义能力让你可以灵活应对特殊场景,比如监控、调试、集成内部系统等。

📌 完整代码示例已上传至 GitHub:https://github.com/eugenp/tutorials/tree/master/logging-modules/log4j2


原始标题:Creating a Custom Log4j2 Appender

« 上一篇: Java 连接池详解
» 下一篇: Java Weekly, 第240期