Programmatic Configuration

Log4j 2 为应用程序提供了几种创建自己的程序化配置的方法:

  • 指定一个自定义 ConfigurationFactory 以使用编程配置启动 Log4j

  • Log4j 启动后,使用配置器替换配置

  • 结合使用配置文件和编程配置来初始化 Log4j

  • 初始化后修改当前配置

ConfigurationBuilder API

从版本 2.4 开始,Log4j 提供了 ConfigurationBuilder 和一组组件构建器,使构建配置变得相当容易。诸如 LoggerConfig 或 Appender 之类的实际配置对象可能很笨拙。他们需要有关 Log4j 内部的大量知识,这使得如果您想要创建配置就很难使用它们。

新的 ConfigurationBuilder API(在 org.apache.logging.log4j.core.config.builder.api 包中)允许用户通过构造组件定义以代码创建 Configurations。无需直接使用实际的配置对象。组件定义已添加到 ConfigurationBuilder,一旦收集了所有定义,便会构造所有实际的配置对象(如 Loggers 和 Appenders)。

ConfigurationBuilder 具有可配置的基本组件的便捷方法,例如 Loggers,Appenders,Filter,Properties 等。但是,Log4j 2 的插件机制意味着用户可以创建任意数量的自定义组件。作为权衡,ConfigurationBuilder API 仅提供有限数量的“强类型”便捷方法,例如 newLogger(),newLayout()等。如果组件不存在便捷方法,则可以使用通用 builder.newComponent()方法。您要配置。

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

以下各节中提供了使用 ConfigurationBuilder API 的示例。

Understanding ConfigurationFactory

在初始化期间,Log4j 2 将搜索可用的ConfigurationFactories,然后选择要使用的那个。所选的 ConfigurationFactory 创建 Log4j 将使用的配置。 Log4j 如何找到可用的 ConfigurationFactories:

  • 可以使用要使用的 ConfigurationFactory 的名称来设置名为 log4j2.configurationFactory 的系统属性。

  • 可以使用要使用的 ConfigurationFactory 实例调用 ConfigurationFactory.setConfigurationFactory(ConfigurationFactory)。必须在调用 Log4j 之前调用它。

  • 可以将 ConfigurationFactory 实现添加到 Classpath 中,并配置为“ ConfigurationFactory”类别中的插件。找到多个适用的 ConfigurationFactories 时,可以使用 Order 注解指定相对优先级。

ConfigurationFactories 具有“支持的类型”的概念,该概念基本上 Map 到 ConfigurationFactory 可以处理的配置文件的文件 extensions。如果指定了配置文件位置,则不会使用其支持的类型不包括“ *”或匹配文件 extensions 的 ConfigurationFactories。

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

以编程方式配置 Log4j 2 的一种方法是创建一个自定义 ConfigurationFactory,该工厂使用ConfigurationBuilder来创建 Configuration。下面的示例重写 getConfiguration()方法以返回由 ConfigurationBuilder 创建的 Configuration。创建 LoggerContext 时,这将导致 Configuration 自动挂接到 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 配置。

以这种方式使用配置器允许应用程序控制 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 进行灵活的配置,但同时请确保始终存在一些无法删除的配置元素。

实现此目的的最简单方法是扩展标准配置类之一(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(InputSource source) {
        return new MyXMLConfiguration(source, configFile);
    }

    /**
     * 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 ConfigurationFactory.ConfigurationSource configSource) {
      super(configSource);
    }

    @Override
    protected void doConfigure() {
        super.doConfigure();
        final LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
        final Layout layout = PatternLayout.createLayout(PatternLayout.SIMPLE_CONVERSION_PATTERN, config, null,
              null,null, null);
        final Appender appender = FileAppender.createAppender("target/test.log", "false", "false", "File", "true",
              "false", "false", "4000", layout, null, "false", null, config);
        appender.start();
        addAppender(appender);
        LoggerConfig loggerConfig = LoggerConfig.createLogger("false", "info", "org.apache.logging.log4j",
              "true", refs, null, config, null );
        loggerConfig.addAppender(appender, null, null);
        addLogger("org.apache.logging.log4j", loggerConfig);
    }
}

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

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

  • 如果更改了配置文件,则将重新加载配置,并且手动更改将丢失。

  • 修改运行中的配置需要同步所有被调用的方法(addAppender 和 addLogger)。

因此,建议的自定义配置方法是扩展标准配置类之一,覆盖设置方法以首先执行 super.setup(),然后在将配置注册到应用程序之前将自定义的 Appenders,Filters 和 LoggerConfigs 添加到配置中用。

以下示例将一个 Appender 和一个使用该 Appender 的新 LoggerConfig 添加到当前配置中。

final LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
        final Configuration config = ctx.getConfiguration();
        Layout layout = PatternLayout.createLayout(PatternLayout.SIMPLE_CONVERSION_PATTERN, config, null,
            null,null, null);
        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();
}

以编程方式将日志事件附加到编写器和 OutputStreams

Log4j 2.5 提供了将日志事件追加到 Writers 和 OutputStreams 的工具。例如,这为在内部使用 Log4j 且仍要支持 JDBC API 的 JDBC 驱动程序实现者提供了简单的集成。 PrintStream)。

给定任何 Writer(例如 PrintWriter),您可以通过创建 WriterAppender 并更新 Log4j 配置,告诉 Log4j 将事件追加到该 Writer:

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。