编程配置

Log4j 2 为应用程序提供了一些方法来创建自己的编程配置

  • 指定一个自定义的 ConfigurationFactory 来启动 Log4j,并使用编程配置
  • 使用 Configurator 在 Log4j 启动后替换配置
  • 使用配置文件和编程配置的组合来初始化 Log4j
  • 在初始化后修改当前配置

ConfigurationBuilder API

从 2.4 版本开始,Log4j 提供了一个 ConfigurationBuilder 和一组组件构建器,允许以相当容易的方式创建 Configuration。实际的配置对象,如 LoggerConfig 或 Appender,可能很笨拙;它们需要大量关于 Log4j 内部机制的知识,如果只是想创建一个 Configuration,则很难使用它们。

新的 ConfigurationBuilder API(在 org.apache.logging.log4j.core.config.builder.api 包中)允许用户通过构建组件定义来在代码中创建 Configuration。无需直接使用实际的配置对象。组件定义被添加到 ConfigurationBuilder 中,一旦所有定义都被收集,所有实际的配置对象(如记录器和附加器)就会被构建。

ConfigurationBuilder 具有针对可以配置的基本组件的便捷方法,例如记录器、附加器、过滤器、属性等。但是,Log4j 2 的插件机制意味着用户可以创建任意数量的自定义组件。作为权衡,ConfigurationBuilder API 只提供有限数量的“强类型”便捷方法,如 newLogger()newLayout() 等。如果不存在要配置的组件的便捷方法,可以使用通用的 builder.newComponent() 方法。

例如,构建器不知道可以在特定组件上配置哪些子组件,例如 RollingFileAppender 与 RoutingAppender。要为 RollingFileAppender 指定触发策略,可以使用 builder.newComponent()。

以下部分提供了使用 ConfigurationBuilder API 的示例。

了解 ConfigurationFactory

在初始化期间,Log4j 2 将搜索可用的 ConfigurationFactories,然后选择要使用的那个。选定的 ConfigurationFactory 创建 Log4j 将使用的 Configuration。以下是 Log4j 如何找到可用的 ConfigurationFactories

  1. 可以设置名为 log4j2.configurationFactory 的系统属性,其中包含要使用的 ConfigurationFactory 的名称。
  2. ConfigurationFactory.setConfigurationFactory(ConfigurationFactory) 可以使用要使用的 ConfigurationFactory 的实例调用。这必须在对 Log4j 的任何其他调用之前调用。
  3. ConfigurationFactory 实现可以添加到类路径中,并作为“ConfigurationFactory”类别中的插件进行配置。Order 注解可用于指定在找到多个适用的 ConfigurationFactories 时,它们的相对优先级。

ConfigurationFactories 具有“支持类型”的概念,它基本上映射到 ConfigurationFactory 可以处理的配置文件的文件扩展名。如果指定了配置文件位置,则支持类型不包含“*”或匹配的文件扩展名的 ConfigurationFactories 不会被使用。

使用 ConfigurationBuilder 和自定义 ConfigurationFactory 初始化 Log4j

以编程方式配置 Log4j 2 的一种方法是创建一个自定义的 ConfigurationFactory,它使用 ConfigurationBuilder 来创建 Configuration。以下示例覆盖了 getConfiguration() 方法,以返回由 ConfigurationBuilder 创建的 Configuration。这将导致 Configuration 在创建 LoggerContext 时自动挂钩到 Log4j。在以下示例中,因为它指定了“*”的支持类型,所以它将覆盖提供的任何配置文件。

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

    static Configuration createConfiguration(final String name, ConfigurationBuilder<BuiltConfiguration> builder) {
        builder.setConfigurationName(name);
        builder.setStatusLevel(Level.ERROR);
        builder.add(builder.newFilter("ThresholdFilter", Filter.Result.ACCEPT, Filter.Result.NEUTRAL).
            addAttribute("level", Level.DEBUG));
        AppenderComponentBuilder appenderBuilder = builder.newAppender("Stdout", "CONSOLE").
            addAttribute("target", ConsoleAppender.Target.SYSTEM_OUT);
        appenderBuilder.add(builder.newLayout("PatternLayout").
            addAttribute("pattern", "%d [%t] %-5level: %msg%n%throwable"));
        appenderBuilder.add(builder.newFilter("MarkerFilter", Filter.Result.DENY,
            Filter.Result.NEUTRAL).addAttribute("marker", "FLOW"));
        builder.add(appenderBuilder);
        builder.add(builder.newLogger("org.apache.logging.log4j", Level.DEBUG).
            add(builder.newAppenderRef("Stdout")).
            addAttribute("additivity", false));
        builder.add(builder.newRootLogger(Level.ERROR).add(builder.newAppenderRef("Stdout")));
        return builder.build();
    }

    @Override
    public Configuration getConfiguration(final LoggerContext loggerContext, final ConfigurationSource source) {
        return getConfiguration(loggerContext, source.toString(), null);
    }

    @Override
    public Configuration getConfiguration(final LoggerContext loggerContext, final String name, final URI configLocation) {
        ConfigurationBuilder<BuiltConfiguration> builder = newConfigurationBuilder();
        return createConfiguration(name, builder);
    }

    @Override
    protected String[] getSupportedTypes() {
        return new String[] {"*"};
    }
}

从 2.7 版本开始,ConfigurationFactory.getConfiguration() 方法接受一个额外的 LoggerContext 参数。

使用 ConfigurationBuilder 和 Configurator 重新配置 Log4j

除了自定义 ConfigurationFactory 之外,还可以使用 Configurator 进行配置。一旦 Configuration 对象被构建,它就可以被传递给 Configurator.initialize 方法之一,以设置 Log4j 配置。

以这种方式使用 Configurator 允许应用程序控制 Log4j 何时初始化。但是,如果在调用 Configurator.initialize() 之前尝试进行任何日志记录,则默认配置将用于这些日志事件。

ConfigurationBuilder<BuiltConfiguration> builder = ConfigurationBuilderFactory.newConfigurationBuilder();
builder.setStatusLevel(Level.ERROR);
builder.setConfigurationName("BuilderTest");
builder.add(builder.newFilter("ThresholdFilter", Filter.Result.ACCEPT, Filter.Result.NEUTRAL)
    .addAttribute("level", Level.DEBUG));
AppenderComponentBuilder appenderBuilder = builder.newAppender("Stdout", "CONSOLE").addAttribute("target",
    ConsoleAppender.Target.SYSTEM_OUT);
appenderBuilder.add(builder.newLayout("PatternLayout")
    .addAttribute("pattern", "%d [%t] %-5level: %msg%n%throwable"));
appenderBuilder.add(builder.newFilter("MarkerFilter", Filter.Result.DENY, Filter.Result.NEUTRAL)
    .addAttribute("marker", "FLOW"));
builder.add(appenderBuilder);
builder.add(builder.newLogger("org.apache.logging.log4j", Level.DEBUG)
    .add(builder.newAppenderRef("Stdout")).addAttribute("additivity", false));
builder.add(builder.newRootLogger(Level.ERROR).add(builder.newAppenderRef("Stdout")));
ctx = Configurator.initialize(builder.build());

此示例展示了如何创建一个包含 RollingFileAppender 的配置。

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

builder.setStatusLevel( Level.ERROR);
builder.setConfigurationName("RollingBuilder");
// create a console appender
AppenderComponentBuilder appenderBuilder = builder.newAppender("Stdout", "CONSOLE").addAttribute("target",
    ConsoleAppender.Target.SYSTEM_OUT);
appenderBuilder.add(builder.newLayout("PatternLayout")
    .addAttribute("pattern", "%d [%t] %-5level: %msg%n%throwable"));
builder.add( appenderBuilder );
// create a rolling file appender
LayoutComponentBuilder layoutBuilder = builder.newLayout("PatternLayout")
    .addAttribute("pattern", "%d [%t] %-5level: %msg%n");
ComponentBuilder triggeringPolicy = builder.newComponent("Policies")
    .addComponent(builder.newComponent("CronTriggeringPolicy").addAttribute("schedule", "0 0 0 * * ?"))
    .addComponent(builder.newComponent("SizeBasedTriggeringPolicy").addAttribute("size", "100M"));
appenderBuilder = builder.newAppender("rolling", "RollingFile")
    .addAttribute("fileName", "target/rolling.log")
    .addAttribute("filePattern", "target/archive/rolling-%d{MM-dd-yy}.log.gz")
    .add(layoutBuilder)
    .addComponent(triggeringPolicy);
builder.add(appenderBuilder);

// create the new logger
builder.add( builder.newLogger( "TestLogger", Level.DEBUG )
    .add( builder.newAppenderRef( "rolling" ) )
    .addAttribute( "additivity", false ) );

builder.add( builder.newRootLogger( Level.DEBUG )
    .add( builder.newAppenderRef( "rolling" ) ) );
LoggerContext ctx = Configurator.initialize(builder.build());

通过组合配置文件和编程配置来初始化 Log4j

有时你希望使用配置文件进行配置,但也要进行一些额外的编程配置。一个可能的用例可能是,你希望使用 XML 来实现灵活的配置,但同时也要确保始终存在一些无法删除的配置元素。

实现此目的最简单的方法是扩展一个标准的 Configuration 类(XmlConfiguration、JSONConfiguration),然后为扩展的类创建一个新的 ConfigurationFactory。在标准配置完成后,可以向其添加自定义配置。

以下示例展示了如何扩展 XmlConfiguration,以手动将 Appender 和 LoggerConfig 添加到配置中。

@Plugin(name = "MyXmlConfigurationFactory", category = "ConfigurationFactory")
@Order(10)
public class MyXmlConfigurationFactory extends ConfigurationFactory {

    /**
     * Valid file extensions for XML files.
     */
    public static final String[] SUFFIXES = new String[]{".xml", "*"};

    /**
     * Return the Configuration.
     *
     * @param source The InputSource.
     * @return The Configuration.
     */
    public Configuration getConfiguration(LoggerContext loggerContext, ConfigurationSource source) {
        return new MyXmlConfiguration(loggerContext, source);
    }

    /**
     * Returns the file suffixes for XML files.
     *
     * @return An array of File extensions.
     */
    public String[] getSupportedTypes() {
        return SUFFIXES;
    }
}

public class MyXmlConfiguration extends XmlConfiguration {
    public MyXmlConfiguration(final LoggerContext loggerContext, final ConfigurationSource configSource) {
        super(loggerContext, configSource);
    }

    @Override
    protected void doConfigure() {
        super.doConfigure();
        final LoggerContext context = (LoggerContext) LogManager.getContext(false);
        final Configuration config = context.getConfiguration();
        final Layout layout = PatternLayout.createDefaultLayout(config);
        final Appender fileAppender = FileAppender.newBuilder().setName("target/test.log").withFileName("File")
                .withImmediateFlush(true).setIgnoreExceptions(false).withBufferedIo(false).withBufferSize(4000)
                .setLayout(layout).withAdvertise(false).setConfiguration(config)
                .build();
        fileAppender.start();
        addAppender(fileAppender);
        AppenderRef[] refs = new AppenderRef[]{AppenderRef.createAppenderRef(fileAppender.getName(), null, null)};
        LoggerConfig loggerConfig = LoggerConfig.createLogger(false, Level.INFO, "org.apache.logging.log4j",
                "true", refs, null, config, null);
        loggerConfig.addAppender(fileAppender, null, null);
        addLogger("org.apache.logging.log4j", loggerConfig);
    }
}

以编程方式修改初始化后的当前配置

应用程序有时需要对日志记录进行自定义,这与实际配置分开。Log4j 允许这样做,尽管它有一些限制

  1. 如果配置文件发生更改,配置将被重新加载,并且手动更改将丢失。
  2. 对正在运行的配置的修改要求所有被调用的方法(addAppender 和 addLogger)必须同步。

因此,自定义配置的推荐方法是扩展一个标准的 Configuration 类,覆盖 setup 方法,首先执行 super.setup(),然后在配置被注册以供使用之前,将自定义的附加器、过滤器和记录器配置添加到配置中。

以下示例使用该附加器将附加器和新的记录器配置添加到当前配置中。

        final LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
        final Configuration config = ctx.getConfiguration();
        final Layout layout = PatternLayout.createDefaultLayout(config);
        Appender appender = FileAppender.createAppender("target/test.log", "false", "false", "File", "true",
            "false", "false", "4000", layout, null, "false", null, config);
        appender.start();
        config.addAppender(appender);
        AppenderRef ref = AppenderRef.createAppenderRef("File", null, null);
        AppenderRef[] refs = new AppenderRef[] {ref};
        LoggerConfig loggerConfig = LoggerConfig.createLogger("false", "info", "org.apache.logging.log4j",
            "true", refs, null, config, null );
        loggerConfig.addAppender(appender, null, null);
        config.addLogger("org.apache.logging.log4j", loggerConfig);
        ctx.updateLoggers();
}

以编程方式将日志事件追加到写入器和输出流

Log4j 2.5 提供了将日志事件追加到写入器和输出流的功能。例如,这为内部使用 Log4j 的 JDBC 驱动程序实现者提供了简单的集成,同时仍然支持 JDBC API 的 CommonDataSource.setLogWriter(PrintWriter)java.sql.DriverManager.setLogWriter(PrintWriter)java.sql.DriverManager.setLogStream(PrintStream)

对于任何 Writer(如 PrintWriter),你可以通过创建一个 WriterAppender 并更新 Log4j 配置来告诉 Log4j 将事件追加到该写入器

void addAppender(final Writer writer, final String writerName) {
    final LoggerContext context = LoggerContext.getContext(false);
    final Configuration config = context.getConfiguration();
    final PatternLayout layout = PatternLayout.createDefaultLayout(config);
    final Appender appender = WriterAppender.createAppender(layout, null, writer, writerName, false, true);
    appender.start();
    config.addAppender(appender);
    updateLoggers(appender, config);
}

private void updateLoggers(final Appender appender, final Configuration config) {
    final Level level = null;
    final Filter filter = null;
    for (final LoggerConfig loggerConfig : config.getLoggers().values()) {
        loggerConfig.addAppender(appender, level, filter);
    }
    config.getRootLogger().addAppender(appender, level, filter);
}

你可以使用 OutputStream(如 PrintStream)来实现相同的效果

void addAppender(final OutputStream outputStream, final String outputStreamName) {
    final LoggerContext context = LoggerContext.getContext(false);
    final Configuration config = context.getConfiguration();
    final PatternLayout layout = PatternLayout.createDefaultLayout(config);
    final Appender appender = OutputStreamAppender.createAppender(layout, null, outputStream, outputStreamName, false, true);
    appender.start();
    config.addAppender(appender);
    updateLoggers(appender, config);
}

区别在于使用 OutputStreamAppender 而不是 WriterAppender