Apache Log4j2 详解 (二)

一、 尝试用编程的方式去配置Log4j2

上一篇我们说了 log4j2 的配置方式,官方给我们说了很多种配置方式,我们回忆一下:

markdown复制代码1. 通过XML、JSON、YAML或者properties格式的配置文件;
2. 通过创建一个 ConfigurationFactory 和 Configuration 接口的实现
3. 调用 Configuration 接口暴露的方法来在默认配置的基础上添加其他组件
4. 通过在内部 Logger 类上调用方法

官方文档说了,除了用配置文件之外,还能用编程的方式实现配置,关于这点呢,我们来看看更详细的官方文档

Log4j 2 provides a few ways for applications to create their own programmatic configuration:

  • Specify a custom ConfigurationFactory to start Log4j with a programmatic configuration
  • Use the Configurator to replace the configuration after Log4j started
  • Initialize Log4j with a combination of a configuration file and programmatic configuration
  • Modify the current Configuration after initialization

翻译一哈:

Log4j2 提供一些编程配置的方式

  • 指定一个自定义的 ConfigurationFactory 去启动 log4j 完成编程配置
  • 在Log4j 启动之后 用 Configurator 去替换 Configuration
  • 使用配置文件和 编程配置的方式去初始化Log4j (原来还能混着用啊)
  • 初始化之后修改当前配置 (嗯哼,配置原来还能修改啊)

方式也是非常之多,如果大家真的感兴趣,可以去官网瞅瞅

logging.apache.org/log4j/2.x/m…

今天我们就用上面说的第一种方法 ,尝试去编程配置下 Log4j2, 主要是配置的方式太多了,都写出来累死了,但是如果大家真的想看,评论区留言,反馈多的话我给补全。

二、案例实战

将那么多理论没啥用,直接上demo, 他说可以用自定义的ConfigurationFactory去作为配置启动Log4j, OK 我们看看官网上给的案例是啥样子。

scala复制代码@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[] {"*"};
    }
}

嗯哼,看起来使用方法就是我们继承 ConfigurationFactory 然后在里面填入自己的配置嘛~

我们直接运行一下官方提供的配置

yaml复制代码2023-02-22 13:43:51,413 [main] DEBUG:  debug level 
2023-02-22 13:43:51,417 [main] INFO :  info level 
2023-02-22 13:43:51,417 [main] WARN :  warn level 
2023-02-22 13:43:51,417 [main] ERROR:  error level 
2023-02-22 13:43:51,417 [main] FATAL:  fatal level 

嗯哼,我们发现是可以直接运行的, 但是上面的代码实在是太抽象了,我们来重新整理下代码

ini复制代码@Plugin(name = "CustomConfigurationFactory", category = ConfigurationFactory.CATEGORY)
@Order(50)
public class CustomConfigurationFactory extends ConfigurationFactory {

    /**
     * 创建Configuration 在这里面干的活我们可以理解为我们之前配置文件中干的活
     * @param name
     * @param builder
     * @return
     */
    static Configuration createConfiguration(final String name, ConfigurationBuilder<BuiltConfiguration> builder) {

        /*
         * 对比xml 配置文件中的配置,这个是不是就是
         * <Configuration name="ConfigTest" status="ERROR" monitorInterval="5">
         * 里面的name 字段和 status 字段嘛
         * 当然里面的 monitorInterval 我们也可以在这里配置
         */
        builder.setConfigurationName(name);
        builder.setStatusLevel(Level.ERROR);

        /*
         * 下面这三句是不是 其实就是 xml 配置文件中的
         * <ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
         * 为了确认,我们点进去看一下 newFilter的构造方法
         * FilterComponentBuilder newFilter(String pluginName, Result onMatch, Result onMismatch);
         * 发现确实是这样的
         */
        FilterComponentBuilder filter = builder.newFilter("ThresholdFilter", Filter.Result.ACCEPT, Filter.Result.NEUTRAL);
        filter.addAttribute("level", Level.DEBUG);
        builder.add(filter);


        /*
         * 这两句是不是就相当于
         * <PatternLayout pattern="%d [%t] %-5level: %msg%n%throwable"/>
         * 当然这个layout 要在appender中才对嘛,所以后面还有个 add(layout)
         */
        LayoutComponentBuilder layout = builder.newLayout("PatternLayout");
        layout.addAttribute("pattern", "%d [%t] %-5level: %msg%n%throwable");


        /*
         * 下面这些是不是相当于
         * <Console name="Stdout" target="SYSTEM_OUT">
         *      <MarkerFilter marker="FLOW" onMatch="DENY" onMismatch="NEUTRAL"/>
         *      <PatternLayout pattern="%d [%t] %-5level: %msg%n%throwable"/>
         * </Console>
         */
        AppenderComponentBuilder console = builder.newAppender("Stdout", "CONSOLE").addAttribute("target", ConsoleAppender.Target.SYSTEM_OUT);
        console.add(layout);
        // 这里自定义了另一个filter 跟上面的filter 差不多我就不展开到xml中了
        console.add(builder.newFilter("MarkerFilter", Filter.Result.DENY, Filter.Result.NEUTRAL).addAttribute("marker", "FLOW"));
        builder.add(console);


        /*
         * 最后这些当然就是RootLogger了
         * <Loggers>
                <Root level="ERROR">
                    <AppenderRef ref="Stdout"/>
                </Root>
            </Loggers>
         *
         */
        RootLoggerComponentBuilder rootLogger = builder.newRootLogger(Level.ERROR);
        rootLogger.add(builder.newAppenderRef("Stdout"));
        builder.add(rootLogger);
        return builder.build();
    }

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

    /**
     * 这是Log4j获取我们自定的Configuration的入口方法
     * @param loggerContext
     * @param name
     * @param configLocation
     * @return
     */
    @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[] {"*"};
    }
}

强烈建议把代码配置对照着 xml 文件看,看完你就会发现,只是xml文件的写法变成了我代码中去 new set 和 add 罢了。

我们发现

markdown复制代码1. 新建一个标签 在代码中就是 new 对象
2. 标签中的属性 我们就用 setAttribute() 去做 放一个键值对
3. 标签嵌套呢,我们就用父对象 执行 .add() 把子对象加进来就行了

怎么样,是不是很简单?

在官方的案例基础上,我们把File类型的日志 也写一下吧,我展示下最终的成果

ini复制代码@Plugin(name = "CustomConfigurationFactory", category = ConfigurationFactory.CATEGORY)
@Order(50)
public class CustomConfigurationFactory extends ConfigurationFactory {

    private static final String LOG_PATH = "D:/logs/conf.log";
    
    static Configuration createConfiguration(final String name, ConfigurationBuilder<BuiltConfiguration> builder) {

        builder.setConfigurationName(name);
        builder.setStatusLevel(Level.DEBUG);
        builder.setMonitorInterval("30");

        FilterComponentBuilder filter = builder.newFilter("ThresholdFilter", Filter.Result.DENY, Filter.Result.ACCEPT);
        filter.addAttribute("level", Level.WARN);
        builder.add(filter);

        LayoutComponentBuilder layout = builder.newLayout("PatternLayout").addAttribute("pattern", "%d [%t] %-5level: --> %m%n");

        AppenderComponentBuilder consoleAppender = builder.newAppender("Stdout", "Console");
        consoleAppender.addAttribute("target", ConsoleAppender.Target.SYSTEM_OUT);
        consoleAppender.add(layout);
        consoleAppender.add(filter);
        builder.add(consoleAppender);

        AppenderComponentBuilder fileAppender = builder.newAppender("FileAppender", "File");
        fileAppender.addAttribute("fileName", LOG_PATH);
        fileAppender.addAttribute("immediateFlush", true);
        fileAppender.add(layout);
        builder.add(fileAppender);

        // builder.add(builder.newLogger("org.apache.logging.log4j", Level.DEBUG).add(builder.newAppenderRef("Stdout")).addAttribute("additivity", false));

        RootLoggerComponentBuilder rootLogger =  builder.newRootLogger(Level.ERROR);
        AppenderRefComponentBuilder stdoutRef =  builder.newAppenderRef("Stdout");
        AppenderRefComponentBuilder fileRef =  builder.newAppenderRef("FileAppender");
        rootLogger.add(stdoutRef);
        rootLogger.add(fileRef);

        builder.add(rootLogger);

        return builder.build();

    }

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

    @Override
    public Configuration getConfiguration(LoggerContext loggerContext, 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);
    }
}

运行结果:

yaml复制代码2023-02-22 14:19:59,108 [main] DEBUG: -->  debug level 
2023-02-22 14:19:59,112 [main] INFO : -->  info level 

对应的地方也有文件啦

image.png

是不是非常简单? 这次我们就到这里,使用编程化配置文件这种方式我们使用的不多,这次作为一个知识点讲一讲。

当然了 生产中 File 类型的 Appender 我们也是基本不用, 我们一般用 RollingFile ,下一节我们重点介绍下 RollingFile的 配置,我们从xml 文件开始,再到 编程化配置都来一遍。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值