转转架构统一日志配置文件log4j2.xml的实践
一、背景
-
目前转转虽然统一使用log4j2日志框架,但业务线上的log4j2配置文件各不相同,RD对其掌握程度也各不相同,配置容易错乱。另外存在相互拷贝,导致错误的方式进一步放大; -
公司的日志规范也会迭代变更,每次推动业务线升级的成本也比较高。
基于此,架构部根据公司日志规范统一封装了log4j2.xml配置文件,RD只须简单修改即可使用。
当然,这个封装不可能覆盖所有场景,仅做为推荐,并非强制。如果满足不了业务场景,仍可以使用自定义的配置方式。
二、解决方案
大致思路如下:
-
架构部将log4j2.xml配置文件封装到一个公共的jar包上,并且这个配置文件支持一些业务自定义参数(如服务名称、日志路径、级别等); -
业务同学在使用时只须引入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
四、总结
总体思想如下:
-
架构部将log4j2.xml配置文件封装到一个公共的jar包上,这个配置文件通过使用sys系统变量支持一些业务自定义参数(如服务名称、日志路径、级别等); -
通过 LoggingEnvironmentPostProcessor
将application.yml
中的SpringBoot环境变量映射到sys系统变量; -
业务同学在使用时只须引入jar包,并在 application.yml
中做一些简单的配置即可。
将log4j2.xml配置文件统一封装起来,实现比较简单,但收益巨大:
-
极大降低了业务配置log4j2.xml的成本; -
格式统一,便于问题排查; -
日志规范迭代更新后,也方便推动统一升级。
关于作者
道阻且长,拥抱变化;而困而知,且勉且行。
参考资料
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