1. 简介

本文将深入探讨 Apache Log4j 2 的编程式配置(Programmatic Configuration)方式。相比传统的 XML 或 properties 文件配置,通过 Java 代码动态构建日志配置,能更灵活地应对运行时环境变化、多租户场景或需要动态调整日志策略的复杂系统。

对于有经验的开发者来说,硬编码日志配置可能显得“不够优雅”,但在某些关键场景下——比如容器化部署、配置中心驱动的日志策略——编程式配置反而是更可控、更可靠的选择。本文带你避开这些“坑”,用简单粗暴的方式搞定 Log4j 2 的 API。


2. 环境准备

要使用 Log4j 2,首先确保你的 pom.xml 中引入了核心依赖:

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

说明

  • log4j-core:Log4j 2 的核心实现
  • log4j-slf4j-impl:SLF4J 的 Log4j 2 绑定,便于与现有日志门面兼容

⚠️ 踩坑提示:不要混用 log4j-api 和其他日志实现(如 logback-classic),会导致类加载冲突。


3. 使用 ConfigurationBuilder 构建配置

Log4j 2 提供了 ConfigurationBuilder API,允许我们用 Java 代码一步步构建完整的日志配置。核心思路是:先创建 builder,然后依次添加 Appender、Filter、Layout、Logger 等组件

获取 builder 实例:

ConfigurationBuilder<BuiltConfiguration> builder = 
    ConfigurationBuilderFactory.newConfigurationBuilder();

Builder 提供了 newAppendernewLayout 等方法用于创建各类组件。注意:Appender、Filter 等组件在 Log4j 2 中被称为“插件”(Plugin),这是其扩展机制的核心。

3.1 配置 Appender(输出目标)

Appender 决定日志输出到哪里。常见类型包括 Console(控制台)、File(文件)、RollingFile(滚动文件)等。

示例:配置控制台和文件 Appender

// 控制台输出
AppenderComponentBuilder console = builder.newAppender("stdout", "Console");
builder.add(console);

// 普通文件输出
AppenderComponentBuilder file = builder.newAppender("log", "File");
file.addAttribute("fileName", "target/logging.log");
builder.add(file);

// 滚动文件输出
AppenderComponentBuilder rollingFile = builder.newAppender("rolling", "RollingFile");
rollingFile.addAttribute("fileName", "target/rolling.log");
rollingFile.addAttribute("filePattern", "target/archive/rolling-%d{MM-dd-yy}.log.gz");
builder.add(rollingFile);

⚠️ 重点注意

  • newAppender(name, plugin) 中的 name 是后续引用的关键,必须唯一
  • 所有组件必须通过 builder.add() 添加到主配置中,否则不会生效 ❌
  • AppenderComponentBuilder 是泛型类,不提供 setFileName() 这类强类型方法,只能用 addAttribute() 设置属性

3.2 配置 Filter(过滤器)

Filter 用于决定哪些日志事件可以被输出。可以绑定到 Appender 或 Logger 上。

示例:为控制台添加 MarkerFilter,只输出标记为 "FLOW" 的日志

FilterComponentBuilder flow = builder.newFilter(
    "MarkerFilter", 
    Filter.Result.ACCEPT,
    Filter.Result.DENY
);  
flow.addAttribute("marker", "FLOW");

console.add(flow); // ❗不是 builder.add,而是添加到具体 Appender

说明

  • onMatch:匹配时的行为(ACCEPT/DENY/NEUTRAL)
  • onMisMatch:不匹配时的行为
  • Filter 是附加在 Appender 或 Logger 上的,所以调用 appender.add(filter),而不是 builder.add(filter)

3.3 配置 Layout(输出格式)

Layout 定义日志的输出格式。最常用的是 PatternLayout

LayoutComponentBuilder standard = builder.newLayout("PatternLayout");
standard.addAttribute("pattern", "%d [%t] %-5level: %msg%n%throwable");

// 分别绑定到各个 Appender
console.add(standard);
file.add(standard);
rollingFile.add(standard);

⚠️ 踩坑提示:Layout 也是添加到 Appender 上的,不是直接加到 builder。


3.4 配置 Root Logger(根日志器)

Root Logger 是所有 Logger 的默认父级,相当于 Java 中的 Object 类。

RootLoggerComponentBuilder rootLogger = builder.newRootLogger(Level.ERROR);
rootLogger.add(builder.newAppenderRef("stdout")); // 引用名为 "stdout" 的 Appender
builder.add(rootLogger);

关键点

  • 通过 newAppenderRef("name") 引用之前定义的 Appender,不是传对象实例
  • 根 Logger 默认继承父配置(additivity=true),子 Logger 可覆盖

3.5 配置子 Logger

用于为特定包或类定制日志级别和输出目标。

LoggerComponentBuilder logger = builder.newLogger("com", Level.DEBUG);
logger.add(builder.newAppenderRef("log"));        // 输出到文件
logger.addAttribute("additivity", false);         // 关闭继承,避免重复输出
builder.add(logger);

additivity 说明

  • true:日志事件会同时传递给父 Logger(可能造成重复输出)
  • false:仅由当前 Logger 处理,不向上传递 ✅ 推荐在特定场景下关闭

3.6 配置其他组件(如触发策略)

并非所有组件都有专用的 newXxx 方法。对于复杂嵌套结构(如 RollingFile 的触发策略),需使用 newComponent

示例:为 RollingFile 配置基于时间和大小的滚动策略

ComponentBuilder triggeringPolicies = builder.newComponent("Policies")
    .addComponent(builder.newComponent("CronTriggeringPolicy")
        .addAttribute("schedule", "0 0 0 * * ?"))  // 每天凌晨滚动
    .addComponent(builder.newComponent("SizeBasedTriggeringPolicy")
        .addAttribute("size", "100M"));            // 文件超过 100M 滚动

rollingFile.addComponent(triggeringPolicies); // 添加到 RollingFile

⚠️ 注意PoliciesRollingFile 的子组件,因此调用 rollingFile.addComponent()


3.7 生成等效 XML 配置

ConfigurationBuilder 提供了一个超实用的方法:writeXmlConfiguration(),可用于调试或持久化配置。

builder.writeXmlConfiguration(System.out);

输出结果:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
   <Appenders>
      <Console name="stdout">
         <PatternLayout pattern="%d [%t] %-5level: %msg%n%throwable" />
         <MarkerFilter onMatch="ACCEPT" onMisMatch="DENY" marker="FLOW" />
      </Console>
      <RollingFile name="rolling" 
        fileName="target/rolling.log" 
        filePattern="target/archive/rolling-%d{MM-dd-yy}.log.gz">
         <PatternLayout pattern="%d [%t] %-5level: %msg%n%throwable" />
         <Policies>
            <CronTriggeringPolicy schedule="0 0 0 * * ?" />
            <SizeBasedTriggeringPolicy size="100M" />
         </Policies>
      </RollingFile>
      <File name="FileSystem" fileName="target/logging.log">
         <PatternLayout pattern="%d [%t] %-5level: %msg%n%throwable" />
      </File>
   </Appenders>
   <Loggers>
      <Logger name="com" level="DEBUG" additivity="false">
         <AppenderRef ref="log" />
      </Logger>
      <Root level="ERROR" additivity="true">
         <AppenderRef ref="stdout" />
      </Root>
   </Loggers>
</Configuration>

用途

  • 验证代码配置是否正确
  • 导出为文件供静态加载
  • 便于团队协作和版本控制

3.8 应用配置

最后一步,将构建好的配置应用到 Log4j 2 环境:

Configurator.initialize(builder.build());

⚠️ 致命踩坑点

  • 必须在首次调用 LogManager.getLogger() 之前执行此方法
  • 否则 Log4j 2 会使用默认配置(通常是 log4j2.xml 或默认配置),后续无法更改

4. 使用 ConfigurationFactory 实现高级定制

ConfigurationFactory 是 Log4j 2 的 SPI 机制,允许我们在框架启动时自动注入自定义配置逻辑,比手动调用 Configurator.initialize 更优雅。

定义一个自定义工厂:

public class CustomConfigFactory extends ConfigurationFactory {
    
    @Override
    public Configuration createConfiguration(
        LoggerContext context, 
        ConfigurationSource src) {
        
        ConfigurationBuilder<BuiltConfiguration> builder = 
            newConfigurationBuilder();

        // ✅ 在这里调用 3.x 节中的配置逻辑
        configureAppenders(builder);
        configureLoggers(builder);

        return builder.build();
    }

    @Override
    public String[] getSupportedTypes() { 
        return new String[] { "*" }; // 支持所有配置源
    }
}

有三种方式让 Log4j 2 加载你的 ConfigurationFactory

4.1 静态初始化(代码侵入性强)

static {
    ConfigurationFactory custom = new CustomConfigFactory();
    ConfigurationFactory.setConfigurationFactory(custom);
}

⚠️ 限制:仍需保证在 LogManager.getLogger 前执行,通常放在 main 方法最开始。


4.2 使用运行时参数(推荐)

启动时指定:

-Dlog4j2.configurationFactory=com.example.CustomConfigFactory

优势

  • 无代码侵入
  • 不受初始化顺序影响
  • 可通过配置中心动态调整

4.3 使用 @Plugin 注解(自动发现)

@Plugin(
  name = "CustomConfigFactory", 
  category = ConfigurationFactory.CATEGORY)
@Order(50)
public class CustomConfigFactory extends ConfigurationFactory {
    // ...
}

Log4j 2 会在类路径扫描 @Plugin 注解,并自动加载 ConfigurationFactory 类别的实现。

优势:完全无侵入,适合封装为公共库。

⚠️ 注意:需确保 log4j-core 的插件扫描机制启用(默认开启)。


4.4 结合静态配置(XML/JSON)

ConfigurationFactory 的强大之处在于可以合并静态配置与动态逻辑

@Override
public Configuration createConfiguration(
    LoggerContext context, 
    ConfigurationSource src) {
    
    if (src != null) {
        // 先加载 XML 配置
        XmlConfiguration xmlConfig = new XmlConfiguration(context, src);
        xmlConfig.initialize();
        
        // 再用 ConfigurationBuilder 扩展或覆盖
        ConfigurationBuilder<BuiltConfiguration> builder = 
            ConfigurationBuilderFactory.newConfigurationBuilder();
        
        // 添加自定义 Appender 或覆盖 Root Logger
        // ...
        
        return builder.build();
    }
    
    // 无静态配置时,完全由代码构建
    return buildFromScratch();
}

典型场景

  • 基于 XML 配置,动态添加云存储 Appender
  • 多环境差异化配置(开发/生产)
  • 运行时从配置中心拉取日志策略

5. 总结

本文系统介绍了 Log4j 2 的两种编程式配置方式:

  • ConfigurationBuilder:适合在应用启动初期手动构建配置,灵活可控
  • ConfigurationFactory:更适合生产环境,支持自动加载、合并静态配置,初始化顺序更安全

📌 关键建议

  • 开发阶段用 writeXmlConfiguration() 验证配置
  • 生产环境优先使用 -Dlog4j2.configurationFactory 方式
  • 避免在 Logger 使用后再初始化配置

完整示例代码已托管至 GitHub:https://github.com/yourname/log4j2-programmatic-config


原始标题:Programmatic Configuration with Log4j 2

» 下一篇: AutoFactory 介绍