跳到主要内容
JackSparrow414
返回

Log4j2的使用(三)不同的环境使用不同的配置

本文导航

展开本文导航

背景

假设现在需要在不同的环境中使用不同的日志格式,不使用Spring Profile

使用SystemPropertyArbiter和JVM启动参数实现

官方文档里给的解决方案是使用Arbiter

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="debug">
<!-- 官方文档   https://logging.apache.org/log4j/2.x/manual/configuration.html#Arbiters-->
    <Appenders>
        <SystemPropertyArbiter propertyName="env" propertyValue="test">
            <Console name="Out" target="SYSTEM_OUT">
                <PatternLayout pattern="%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX} [%t] %level %logger - %msg%n"/>
            </Console>
        </SystemPropertyArbiter>
        <SystemPropertyArbiter propertyName="env" propertyValue="prod">
            <Console name="Out" target="SYSTEM_OUT">
                <JsonTemplateLayout eventTemplateUri="classpath:EcsLayout.json"/>
            </Console>
        </SystemPropertyArbiter>
    </Appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="Out"/>
        </Root>
    </Loggers>
</Configuration>

启动时,设置SystemProperty,例如

java -jar Log4jApplication.jar -Denv=prod

使用Property Substitution和JVM启动参数实现

通过官方提供的Property Substitution也可以实现此目的

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="debug">
    <Properties>
        <Property name="Console">Console_${sys:log4jLayOut:-test}</Property>
    </Properties>
    <Appenders>
        <Console name="Console_prod" target="SYSTEM_OUT">
            <JsonTemplateLayout eventTemplateUri="classpath:EcsLayout.json"/>
        </Console>
        <Console name="Console_test" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX} [%t] %level %logger - %msg%n"/>
        </Console>
    </Appenders>
    <Loggers>
        <Root level="info">
<!-- ${name}语法,官方文档 https://logging.apache.org/log4j/2.x/manual/configuration.html#property-substitution-->
            <AppenderRef ref="${Console}"/>
        </Root>
    </Loggers>
</Configuration>

关于上述Properties的写法,默认值是test Log4j2 属性替换文档中使用 :- 指定默认值的说明 通过提前声明好两个Appender,根据每个Appender的名字匹配。

启动时,设置SystemProperty,例如

java -jar Log4jApplication.jar -Dlog4jLayOut=prod

使用自定义的LookUp Plugin来实现

上面两种方式适合新项目适和部署周期较短的项目。

由于上面两种方式需要在JVM启动的时候添加额外的参数,那这一部分工作是由运维来负责,虽然只是添加一个参数,假设服务器数量较多,那么代码需要等到JVM参数添加到所有服务器之后才可以,也就相当于部署了两次。

在这种背景下,我在想,能不能把设置查找SystemProperty这一部分工作也在代码里完成呢?

好在Log4j2提供了Plugins机制,允许我们可以自己拓展

@Plugin(
    name="sys",
    category = StrLookup.CATEGORY
)
public class CustomerSystemPropertiesLookUp extends SystemPropertiesLookup {

    private static final String LOOK_UP_KEY = "log4jLayOut";

    @Override
    public String lookup(final LogEvent event, String key) {
        if (LOOK_UP_KEY.equals(key)) {
           return getValueBasedOnEnv();
        }else {
            return super.lookup(key);
        }
    }

    private String getValueBasedOnEnv() {
        String isTest = System.getProperty("isTest");
        return "true".equals(isTest) ? "test" : "prod";
    }
}

这里重写了SystemPropertiesLookup,当查找特定key时,在我们自定义的方法里查找;其他key时则使用父类的查找方法。

看到代码可能有人会有疑问,你的getValueBasedOnEnv方法还不是在SystemProperties拿的吗?不还是一样要添加JVM参数?

这是因为,我们当前工程里的System.properties文件中已经包含里可以区分测试环境和生产环境的属性了,所以直接使用,这里这是为了演示好理解。

Log4j2.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="debug">
    <Properties>
        <Property name="Console">Console_${sys:log4jLayOut:-test}</Property>
    </Properties>
    <Appenders>
        <Console name="Console_prod" target="SYSTEM_OUT">
            <JsonTemplateLayout eventTemplateUri="classpath:EcsLayout.json"/>
        </Console>
        <Console name="Console_test" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX} [%t] %level %logger - %msg%n"/>
        </Console>
    </Appenders>
    <Loggers>
        <Root level="info">
            <!-- ${name}语法,官方文档 https://logging.apache.org/log4j/2.x/manual/configuration.html#property-substitution-->
            <AppenderRef ref="${Console}"/>
        </Root>
    </Loggers>
</Configuration>

使用ScriptAppenderSelector实现

ScriptAppenderSelector文档

When the configuration is built, the ScriptAppenderSelector appender calls a Script to compute an appender name. Log4j then creates one of the appender named listed under AppenderSet using the name of the ScriptAppenderSelector

Log4j only builds the one selected appender from the configuration tree, and ignores other AppenderSet child nodes

  1. 这里使用groovy,引入groovy-jsr223依赖
  2. log42.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <Configuration status="debug">
    <Appenders>
        <ScriptAppenderSelector name="SelectIt">
            <Script language="groovy" name="selectScripts"><![CDATA[
                    var result;
                    if (System.getProperty("os.name").equalsIgnoreCase("Windows")) {
                        result = "Null";
                    } else {
                        result = "Console";
                    }
                    return result;
            ]]>
            </Script>
            <AppenderSet>
                <Console name="Console" target="SYSTEM_OUT">
                    <PatternLayout pattern="%d{yyyy-MM-dd'T'HH:mm:ss.SSS'Z'} [%t] %level %logger - %msg%n"/>
                </Console>
                <Null name="Null" />
            </AppenderSet>
        </ScriptAppenderSelector>
    </Appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="SelectIt"/>
        </Root>
    </Loggers>
    </Configuration>
  3. 设置JVM启动参数,-Dlog4j2.Script.enableLanguages=groovy

备注


分享本文:

继续阅读本系列

Log4j2 的理解与使用

  1. Log4j2的理解与使用(一)
  2. Log4j2的使用(二)动态修改日志级别
  3. Log4j2的使用(三)不同的环境使用不同的配置正在阅读

评论

欢迎提问、纠错和分享经验。发表评论需要登录 GitHub;中英文版本共享评论。

评论仅在正式网站开放。