Log4j2分析与实践-编程实现日志配置

    Log4j2提供了几种方式来实现编程配置:

(1)指定一个使用编程配置的自定义ConfigurationFactory来启动Log4j。

(2)Log4j启动之后,使用Configurator来代替配置。

(3)使用配置文件和编程配置的组合来初始化Log4j。

(4)初始化之后修改当前的Configuration。

ConfigurationBuilder API

    从2.4开始,Log4j提供了一个ConfigurationBuilder和一系列组件构建器,使得创建Configuration变得非常简单。而实际的配置对象(比如LoggerConfig或Appender)都比较笨重,它们需要开发者理解很多Log4j的内部机制,如果仅仅想创建一个Configuration,将会异常困难。
    新的ConfigurationBuilder API(在org.apache.logging.log4j.core.config.builder.api包中)允许用户通过构造组件的定义在代码中创建Configuration。没有必要直接操作实际的配置对象。组件定义被添加到了ConfigurationBuilder,一旦所有的组件都定义好了,那么所有的实际对象(比如Logger和Appender)也都创建好了。
    ConfigurationBuilder拥有便于配置基本组件(比如Logger、Appender、Filter和Properties等)的方法。然而,Log4j2的插件机制意味着用户可以创建任何数量的自定义组件。作为一种权衡,ConfigurationBuilder API仅提供了有限数量的“强类型”方法,比如newLogger()和newLayout()。如果没有找到合适的组件创建方法,也可以使用通用的builder.newComponent()。
    举例,构建器并不知道在一个指定的组件上可以配置哪些子组件,要想在RollingFileAppender指定一个触发政策(Triggering Policy),使用builder.newComponent()就可以了。

理解ConfigurationFactory

    在Log4j2初始化期间,Log4j2会查找并使用合适的ConfigurationFactory,ConfigurationFactory会创建Configuration对象,下面就是查找ConfigurationFactory的方式:
(1)可以设置系统属性log4j.configurationFactory来指定一个ConfigurationFactory。

(2)ConfigurationFactory.setConfigurationFactory(ConfigurationFactory),需要在Log4j的其它调用前执行。

(3)ConfigurationFactory的实现可以添加到classpath下,配置为一个插件,category设置为"ConfigurationFactory"。@Order注解可以用来设置多个ConfigurationFactory的相对优先级。
    ConfigurationFactory有"可支持文件后缀类型"的概念,因此如果指定了配置文件的路径,如果可支持文件后缀类型不包含"*"或者文件后缀不匹配,那么这个配置文件不会被使用。

使用自定义的ConfigurationFactory

    编程配置Log4j2的一种方式就是创建一个自定义的ConfigurationFactory,并且使用ConfigurationBuilder来创建一个Configuration。下面的示例就是覆盖了ConfigurationFactory接口的getConfiguration()方法,返回一个ConfigurationBuilder创建的的Configuration。这就会使得在LoggerContext创建时,   Configuration会自动地被加入Log4j。下面的例子中,因为指定了可支持的类型为"*",它将会加载任何配置文件。
    从2.7开始,ConfigurationFactory.getConfiguration()添加了一个LoggerContext参数。
@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[] {"*"};
    }
}

使用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());

配置文件和编程配置相结合

    有时需要在使用配置文件的同时,还要进行编程配置。最简单的方式就是继承一个标准的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(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);
    }
}

初始化后修改当前Configuration

    有时需要在已有的Configuration基础上自定义日志输出,Log4j是允许这样做的,但有限制:
(1)如果配置文件更新了,那么配置就会重新加载,那么原来的的配置就会丢失。
(2)对正在运行的配置进行修改,需要所有被调用的方法(addAppender和addLogger)都是同步的。
就如前文所述,自定义一个配置最简单的方式就是继承一个标准的Configuration类,覆盖setup方法,先调用super.setup,然后添加自定义的Appender、Filter和LoggerConfig。
    下面的例子创建了一个Appender和一个LoggerConfig,并在当前配置中添加这个Appender。
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();

编程将日志事件Append到Writes和OutputStream

    Log4j2.5开始提供了将日志事件添加到Writers和OutputSteam的机制。给定任何Writer,比如PrintWriter,可以创建一个WriteAppender来使得Log4j将日志事件Append到这个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来达到目的:

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);
}