log4j2 + 以编程方式覆盖 Web 应用程序中的父自定义 ConfigurationFactory

log4j2 + programmatically override a parent custom ConfigurationFactory in a web app

我们正在尝试一些新的东西,并在此处发布问题以查看是否可行。

TL;DR 覆盖 Web 应用中的父自定义 ConfigurationFactory。

场景:我们有一堆网络应用程序。其中一些 Web 应用程序将日志写入同一个日志文件,而其他 Web 应用程序将日志写入与应用程序相关的单个日志文件。所以我写了一个 CustomLoggerConfigFactory(为了便于讨论,我们称其为父配置)并将其捆绑在 custom-logger-util.jar 中并将此 jar 用作Web 应用程序中的依赖项和记录器在 Web 应用程序启动期间被初始化。此配置在初始化后有一个 RollingFileAppender 将日志写入名为 common-logs.log 的文件,该文件工作正常。下面是 CustomLoggerConfigFactory 的代码。

问题: 现在我的问题是,比如在 Web 应用程序 B 中,common-logger-util.jar 也用作依赖项 - 我如何扩展CustomLoggerConfigFactory 覆盖 RollingFileAppender 以编程方式将日志写入 appB.log 文件而不是 common-logs.log 并且不使用配置文件并保持其余配置相同即使用父配置工厂定义的控制台附加程序、记录器(包括 RootLogger)? 谁能解释一下如何实现这一点?

提前致谢。

package org.custom.logger;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.ConsoleAppender;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.ConfigurationFactory;
import org.apache.logging.log4j.core.config.ConfigurationSource;
import org.apache.logging.log4j.core.config.Order;
import org.apache.logging.log4j.core.config.builder.api.*;
import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration;
import org.apache.logging.log4j.core.config.plugins.Plugin;

import java.net.URI;

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

    static Configuration createConfiguration(final String name, ConfigurationBuilder<BuiltConfiguration> builder) {
        //Set the Logger configuration
        builder.setConfigurationName(name);
        builder.setStatusLevel(Level.TRACE);

        System.out.println("Testing Logger programmatic configuration");
        String region = System.getProperty(CustomLoggerConstants.LOG4J2_ENV);
        System.out.println("ENV|" + region);

        //Create a pattern layout (common for console and rolling file appenders)
        LayoutComponentBuilder layoutBuilder = builder.newLayout("PatternLayout").addAttribute("pattern", CustomLoggerConstants.KP_STD_CONVERSIONPATTERN);

        FilterComponentBuilder thresholdFilter = builder.newFilter("ThresholdFilter", Filter.Result.ACCEPT, Filter.Result.NEUTRAL)
                .addAttribute("level", getLogLevel(region));

        //Create a Console appender
        AppenderComponentBuilder appenderBuilder = builder.newAppender(CustomLoggerConstants.LOG_TO_CONSOLE, "CONSOLE")
                                                .addAttribute("target", ConsoleAppender.Target.SYSTEM_OUT);
        appenderBuilder.add(layoutBuilder).addAttribute("pattern", CustomLoggerConstants.KP_STD_CONVERSIONPATTERN);
        appenderBuilder.add(thresholdFilter);
        builder.add(appenderBuilder);

        //Create a policy for adding it to rolling file appender
        ComponentBuilder triggeringPolicy = builder.newComponent("Policies")
                .addComponent(builder.newComponent("SizeBasedTriggeringPolicy").addAttribute("size", "5M"));

        //Create a rolling file appender
        AppenderComponentBuilder rollingFileAppender = builder.newAppender(CustomLoggerConstants.LOG_TO_ROLLING_FILE, "RollingFile")
                .addAttribute("fileName", CustomLoggerConstants.FILENAME + CustomLoggerConstants.FILE_EXTN)
                .addAttribute("filePattern", CustomLoggerConstants.FILENAME + "-%d{MM-dd-yy-HH-mm-ss}-%i.log.gz")
                .add(layoutBuilder)
                .addComponent(triggeringPolicy);
        builder.add(rollingFileAppender);

        //Create a logger
        builder.add(builder.newLogger("org.kp", getLogLevel(region))
                .add(builder.newAppenderRef(CustomLoggerConstants.LOG_TO_ROLLING_FILE))
                .add(builder.newAppenderRef(CustomLoggerConstants.LOG_TO_CONSOLE))
                .addAttribute("additivity", false)
        );

        //Create root logger
        builder.add(builder.newRootLogger(Level.INFO)
                .add(builder.newAppenderRef(CustomLoggerConstants.LOG_TO_CONSOLE))
        );

        System.out.println("Logger programmatic configuration is almost done and ready to be initialized.");
        return builder.build();
        //LoggerContext context = Configurator.initialize(builder.build());
    }

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

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

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

    private static Level getLogLevel(String region) {
        switch (region) {
            case CustomLoggerConstants.DEV:
                return Level.DEBUG;
            case CustomLoggerConstants.QA:
            case CustomLoggerConstants.LOAD:
            case CustomLoggerConstants.PREV:
                return Level.INFO;
            
        }
        return null;
    }
}

我设计了我的自定义配置工厂,并将其视为实现此目的的一种方式。

下面是解决方法。

该解决方案基于 Java 8。Java 8 中的接口现在允许您定义方法的默认实现并使用它们。提供的 log4j2 ConfigurationFactory 已经是一个抽象 class 但我想将配置的创建与具有默认实现的各个组件分开。

下图中的 LoggerConfigFactory 是自定义默认实现,只需包含它就可以在任何其他应用程序中使用(但是,在我的例子中,我将 CustomConfigBuilder、LoggerConfigFactory 和 LoggerConstants 打包在一个jar 文件)。

如果在应用程序中需要定义任何或所有单个组件,则只需创建扩展默认 LoggerConfigFactory 的配置工厂 class (AppSpecificLoggerConfigFactory)。

这成功了!!下面是代码,供参考。

package org.custom.logger;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.appender.ConsoleAppender;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.builder.api.*;
import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration;


public interface CustomConfigBuilder {

    default Configuration createConfiguration(final String name, ConfigurationBuilder<BuiltConfiguration> builder, String filename) {
        //Set the Logger configuration
        setLogConfig(name, builder);

        System.out.println("Testing Logger programmatic configuration for " + name);
        String region = System.getProperty(LoggerConstants.LOG4J2_ENV, "PROD");
        System.out.println("ENV from properties file|" + region);
        /*String region = System.getProperty(LoggerConstants.LOG4J2_ENV);
        if(null == region || region.equals("")){
            region = LoggerConstants.PROD;
        }
        System.out.println("ENV from sys props|" + region);*/
        //Create a pattern layout (common for console and rolling file appenders)
        LayoutComponentBuilder layoutBuilder = createLayoutBuilder(builder);
        FilterComponentBuilder thresholdFilter = createThresholdFilter(builder, region);

        //Create a Console appender
        AppenderComponentBuilder appenderBuilder = builder.newAppender(LoggerConstants.LOG_TO_CONSOLE, "CONSOLE")
                .addAttribute("target", ConsoleAppender.Target.SYSTEM_OUT);
        appenderBuilder.add(layoutBuilder).addAttribute("pattern", LoggerConstants.CONVERSIONPATTERN);
        appenderBuilder.add(thresholdFilter);
        builder.add(appenderBuilder);

        //Create a policy for adding it to rolling file appender
        ComponentBuilder triggeringPolicy = createTriggeringPolicy(builder);

        //Create a rolling file appender
        AppenderComponentBuilder rollingFileAppender = createRollingFileAppender(builder, layoutBuilder, triggeringPolicy, filename);
        builder.add(rollingFileAppender);

        LoggerComponentBuilder appLogger = createAppLogger(builder, region);
        //Create a logger
        builder.add(appLogger);

        //Create root logger
        RootLoggerComponentBuilder rootLogger = createRootLogger(builder);
        builder.add(rootLogger);

        System.out.println("Logger programmatic configuration for " + name + " is almost done and ready to be initialized.");
        return builder.build();
        //LoggerContext context = Configurator.initialize(builder.build());
    }

    default void setLogConfig(String name, ConfigurationBuilder<BuiltConfiguration> builder) {
        builder.setConfigurationName(name);
        builder.setStatusLevel(Level.TRACE);
    }

    default LayoutComponentBuilder createLayoutBuilder(ConfigurationBuilder<BuiltConfiguration> builder) {
        LayoutComponentBuilder layoutBuilder = builder.newLayout("PatternLayout").addAttribute("pattern", LoggerConstants.CONVERSIONPATTERN);
        return layoutBuilder;
    }

    default FilterComponentBuilder createThresholdFilter(ConfigurationBuilder<BuiltConfiguration> builder, String region) {
        FilterComponentBuilder thresholdFilter = builder.newFilter("ThresholdFilter", Filter.Result.ACCEPT, Filter.Result.NEUTRAL)
                .addAttribute("level", getLogLevel(region));
        return thresholdFilter;
    }
    default ComponentBuilder createTriggeringPolicy(ConfigurationBuilder<BuiltConfiguration> builder) {
        ComponentBuilder triggeringPolicy = builder.newComponent("Policies")
                .addComponent(builder.newComponent("SizeBasedTriggeringPolicy").addAttribute("size", "5M"));
        return triggeringPolicy;
    }
    default AppenderComponentBuilder createRollingFileAppender(ConfigurationBuilder<BuiltConfiguration> builder, LayoutComponentBuilder layoutBuilder, ComponentBuilder triggeringPolicy, String filename) {
        System.out.println("Filename|" + filename);
        AppenderComponentBuilder rollingFileAppender = builder.newAppender(LoggerConstants.LOG_TO_ROLLING_FILE, "RollingFile")
                .addAttribute("fileName", filename + LoggerConstants.FILE_EXTN)
                .addAttribute("filePattern", filename + "-%d{MM-dd-yy-HH-mm-ss}-%i.log.gz")
                .add(layoutBuilder)
                .addComponent(triggeringPolicy);
        return rollingFileAppender;
    }

    default LoggerComponentBuilder createAppLogger(ConfigurationBuilder<BuiltConfiguration> builder, String region) {
        return builder.newLogger("org.app", getLogLevel(region))
                .add(builder.newAppenderRef(LoggerConstants.LOG_TO_ROLLING_FILE))
                .add(builder.newAppenderRef(LoggerConstants.LOG_TO_CONSOLE))
                .addAttribute("additivity", false);
    }

    default RootLoggerComponentBuilder createRootLogger(ConfigurationBuilder<BuiltConfiguration> builder) {
        return builder.newRootLogger(Level.INFO)
                .add(builder.newAppenderRef(LoggerConstants.LOG_TO_CONSOLE));
    }

    default Level getLogLevel(String region) {
        switch (region) {
            case LoggerConstants.DEV:
                return Level.DEBUG;
            case LoggerConstants.QA:
            case LoggerConstants.LOAD:
            case LoggerConstants.PREV:
                return Level.INFO;
            case LoggerConstants.PROD:
                return Level.ERROR;
        }
        return null;
    }
}

package org.custom.logger;

public class LoggerConstants {
    public static final String CONVERSIONPATTERN = "%d{ISO8601} [%t] %-5p %c{3} - %m%n";
    public static final String FILENAME = "/logs/logfile";
    public static final String FILE_EXTN = ".log";
    public static final String LOG_TO_ROLLING_FILE = "LogToRollingFile";
    public static final String LOG_TO_CONSOLE = "LogToConsole";
    //public static final String LOG4J2_ENV = "LOG4J2_ENV";
    public static final String LOG4J2_ENV = "log4j2.env";

    public static final String DEV = "DEV";
    public static final String QA = "QA";
    public static final String LOAD = "LOAD";
    public static final String PREV = "PREV";
    public static final String PROD = "PROD";

}

package org.custom.logger;

import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.ConfigurationFactory;
import org.apache.logging.log4j.core.config.ConfigurationSource;
import org.apache.logging.log4j.core.config.Order;
import org.apache.logging.log4j.core.config.builder.api.*;
import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration;
import org.apache.logging.log4j.core.config.plugins.Plugin;

import java.net.URI;

@Plugin(name = "CustomLogConfig", category = ConfigurationFactory.CATEGORY)
@Order(50)
public class LoggerConfigFactory extends ConfigurationFactory implements CustomConfigBuilder {

    public Configuration createConfiguration(String name, ConfigurationBuilder<BuiltConfiguration> builder) {
        System.out.println("Invoking  default config factory");
        createConfiguration(name, builder, LoggerConstants.FILENAME);
        return null;
    }

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

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

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

}

以上三个都打包成一个jar文件。如果应用程序想要使用 LoggerConfigFactory 中定义的默认配置,则需要将此 jar 文件包含在应用程序 class 路径中,或者如果它是 Web 应用程序,则在其 WEB-INF/lib.

例如如果应用程序想要拥有自己的具有不同文件名的日志文件,则您需要扩展它。请参阅下面的代码。注意,请确保 @Order 注释的值高于默认配置工厂中定义的值。

import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.ConfigurationFactory;
import org.apache.logging.log4j.core.config.Order;
import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder;
import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.custom.logger.LoggerConfigFactory;

@Plugin(name = "AppCustomLogConfig", category = ConfigurationFactory.CATEGORY)
@Order(55)
public class AppSpecificLoggerConfigFactory extends LoggerConfigFactory {

    String filename = "/logs/appSpecificLogFilename";
    @Override
    public Configuration createConfiguration(String name, ConfigurationBuilder<BuiltConfiguration> builder) {
        System.out.println("Invoking App Specific config factory");
        return createConfiguration(name, builder, filename);
    }
}