vlambda博客
学习文章列表

转转架构统一日志配置文件log4j2.xml的实践

一、背景

  • 目前转转虽然统一使用log4j2日志框架,但业务线上的log4j2配置文件各不相同,RD对其掌握程度也各不相同,配置容易错乱。另外存在相互拷贝,导致错误的方式进一步放大;
  • 公司的日志规范也会迭代变更,每次推动业务线升级的成本也比较高。

基于此,架构部根据公司日志规范统一封装了log4j2.xml配置文件,RD只须简单修改即可使用。

当然,这个封装不可能覆盖所有场景,仅做为推荐,并非强制。如果满足不了业务场景,仍可以使用自定义的配置方式。

二、解决方案

大致思路如下:

  1. 架构部将log4j2.xml配置文件封装到一个公共的jar包上,并且这个配置文件支持一些业务自定义参数(如服务名称、日志路径、级别等);
  2. 业务同学在使用时只须引入jar包,并在 application.yml中做一些简单的配置即可。

下面进行详细描述。

2.1 封装log4j2.xml配置文件到公共jar包

我们将log4j2.xml配置文件封装到架构部的日志相关jar包zzarch-log-common,结构如下:

log4j2.xml内容如下(已简化):

<Properties>
    <property name="app_name">${sys:logging.zz.app-name:-unknown}</property>
    <property name="log_pattern">%d{MM-dd HH:mm:ss.SSS} %X{TRACE_ID}-%X{SPAN_ID}-%X{SAMPLED} %t %p %C{1.}:%L - %m%n</property>
    <property name="log_home">${sys:logging.zz.directory:-/opt/log/unknown}/${app_name}</property>
</Properties>
 
<AsyncRoot level="${sys:logging.zz.root.level:-INFO}" includeLocation="true">
    <AppenderRef ref="CONSOLE" level="${sys:logging.zz.console.level:-OFF}"/>
    <AppenderRef ref="DEBUG" level="${sys:logging.zz.debug.level:-OFF}"/>
    <AppenderRef ref="INFO" />
    <AppenderRef ref="WARN" />
    <AppenderRef ref="ERROR" />
</AsyncRoot>

在这里,我们通过sys系统变量读入自定义配置项(如服务名称、日志路径、级别等)。log4j2配置文件支持多种类型的变量注入,详见:Lookups[1]

虽然log4j2配置文件也支持直接读取SpringBoot环境变量,但基于以下两个原因我们还是采用了sys系统环境:

  • 读取SpringBoot环境变量,需要额外依赖 log4j-spring-cloud-config-client,有点重,详见: Spring Boot Lookup [2];
  • 公司仍有小部分业务并没有使用SpringBoot,sys系统变量要更通用。

2.2 SpringBoot环境变量到系统变量的映射

在log4j2.xml中我们使用的是sys系统变量,但用户配置application.yml时实际将变量写入到SpringBoot环境变量空间里,这时我们就需要将SpringBoot环境变量映射到sys系统变量中。(关于各变量空间详见:Externalized Configuration[3]

我们通过实现EnvironmentPostProcessor接口,读取application.yml的SpringBoot环境变量,并映射到sys系统变量中。

public class LoggingEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
    /**
     * 顺序比{@link org.springframework.boot.context.config.ConfigFileApplicationListener}低就行。
     */
    private int order = 0;

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        String configKey = "logging.config";
        String config = environment.getProperty(configKey);
        // 使用架构封装的log4j配置,需要在environment将logging.config设置成"classpath:zzarch/log4j2.xml"
        if (!Objects.equals(config, "classpath:zzarch/log4j2.xml")) { // 如果没有使用架构封装的log4j2.xml
            return;
        }
        System.setProperty(configKey, config); // 设置进系统变量

        String appName = environment.getProperty("logging.zz.app-name");
        if (Objects.nonNull(appName)) {
            System.setProperty(appNameKey, appName);
        }

        String directory = environment.getProperty("logging.zz.directory");
        if (Objects.nonNull(directory)) {
            System.setProperty(directoryKey, directory);
        }

        //......

    }

    @Override
    public int getOrder() {
        return order;
    }
}

注:我们实现类的order一定要比ConfigFileApplicationListener大(即比它晚),因为ConfigFileApplicationListener才是实际读取application.yml的类,ConfigFileApplicationListener会读进environment中,只有比它晚我们才能读取到里边的配置。

至此,我们的jar包就可以读取application.yml中的SpringBoot环境变量,并通过LoggingEnvironmentPostProcessor将其映射到sys系统变量,以便log4j2.xml能够识别。

封装好此jar包,接下来就是业务如何使用了。

三、如何使用

业务同学只须配置application.yml,加入如下代码,然后删除原有的log4j2配置文件即可。

logging:
  config: classpath:zzarch/log4j2.xml
  zz: #转转扩展的配置
    app-name: myapp #TODO:默认为unknown
    directory: /opt/log/zzweb #TODO:默认为/opt/log/unknown

四、总结

总体思想如下:

  1. 架构部将log4j2.xml配置文件封装到一个公共的jar包上,这个配置文件通过使用sys系统变量支持一些业务自定义参数(如服务名称、日志路径、级别等);
  2. 通过 LoggingEnvironmentPostProcessorapplication.yml中的SpringBoot环境变量映射到sys系统变量;
  3. 业务同学在使用时只须引入jar包,并在 application.yml中做一些简单的配置即可。

将log4j2.xml配置文件统一封装起来,实现比较简单,但收益巨大:

  1. 极大降低了业务配置log4j2.xml的成本;
  2. 格式统一,便于问题排查;
  3. 日志规范迭代更新后,也方便推动统一升级。

关于作者

道阻且长,拥抱变化;而困而知,且勉且行。

参考资料

[1]

log4j2 Lookups: https://logging.apache.org/log4j/2.x/manual/lookups.html#

[2]

log4j2 Spring Boot Lookup: https://logging.apache.org/log4j/2.x/manual/lookups.html#SpringLookup

[3]

SpringBoot Externalized Configuration: https://docs.spring.io/spring-boot/docs/2.1.9.RELEASE/reference/html/boot-features-external-config.html