实战:如何优雅地扩展Log4j配置?

news2024/10/7 8:26:43

前言

Log4j 日志框架我们经常会使用到,最近,我就遇到了一个与日志配置相关的问题。简单来说,就是在原来日志配置的基础上,指定类的日志打印到指定的日志文件中。

这样讲述可能不是那么好理解,且听我从需求来源讲起。

一、扩展配置的需求来源

我们的项目中使用的是 Log4j2 日志框架,日志配置log4j.yml是这样的:

 Configuration:
   status: warn
   
   Appenders:
     Console:
       name: Console
       target: SYSTEM_OUT
       # 不重要
     RollingFile:
       - name: ROLLING_FILE
         # 不重要
   Loggers:
     Root:
       level: info
       AppenderRef:
         - ref: Console
         - ref: ROLLING_FILE
     Logger:
       - name: com.myproject
         level: info

配置很简单,只是一个滚动日志文件和控制台的输出。现在来了这么一个需求:要把项目的 HTTP 接口访问日志单独打印到一个日志文件logs/access.log中,这个功能由配置开关casslog.accessLogEnabled决定是否开启。

说做就做,我立马把原来的log4j.yml文件改成log4j_with_accesslog.yml,并添加了访问日志的AppenderACCESS_LOG,如下配置所示。

 Configuration:
   status: warn
   
   Appenders:
     Console:
       name: Console
       target: SYSTEM_OUT
       # 不重要
     RollingFile:
       - name: ROLLING_FILE
         # 不重要
         ### 新增的配置开始(1) ###
       - name: ACCESS_LOG
         fileName: logs/access.log
         ### 新增的配置结束(1) ###
   Loggers:
     Root:
       level: info
       AppenderRef:
         - ref: Console
         - ref: ROLLING_FILE
     Logger:
       - name: com.myproject
         level: info
       ### 新增的配置开始(2) ###
       - name: com.myproject.commons.AccessLog
         level: trace
         additivity: false
         AppenderRef:
           - ref: Console
           - ref: ACCESS_LOG
       ### 新增的配置结束(2) ###

上面配置注释中【新增的配置开始(1)】和【新增的配置开始(2)】就是添加的配置内容。功能开关是下面这样实现的,在项目启动时做判断。

 import org.springframework.boot.logging.log4j2.Log4J2LoggingSystem;
 ​
 public class MyProjectLoggingSystem extends Log4J2LoggingSystem {
 ​
     static final boolean accessLogEnabled =
             Boolean.parseBoolean(System.getProperty("casslog.accessLogEnabled", "true"));
 ​
     @Override
     protected String[] getStandardConfigLocations() {
         if (accessLogEnabled) {
             return new String[]{"casslog_with_accesslog.yml"};
         }
         return new String[]{"casslog.yml"};
     }
 }

这样功能就实现了,程序也确实可以运行。但是总感觉不够优雅,如果有上百个项目都要加上这个功能,这些项目的日志配置文件都要改,想想都崩溃。

二、看看开源项目 Nacos 的实现

使用过 Nacos 的朋友可能知道,Nacos 的配置模块与服务发现模块是两个功能,日志也是分开的。具体通过nacos-client.jar中的nacos-log4j2.xml就可以看出来。

image-20221118105141675

注意本文 Nacos 源码版本是nacos-client 1.4.1

nacos-log4j2.xml我做了精简,内容如下。

 <Configuration status="WARN">
     <Appenders>
         <RollingFile name="CONFIG_LOG_FILE" fileName="${sys:JM.LOG.PATH}/nacos/config.log"
             filePattern="${sys:JM.LOG.PATH}/nacos/config.log.%d{yyyy-MM-dd}.%i">
             <!-- 不重要 -->
         </RollingFile>
         <RollingFile name="NAMING_LOG_FILE" fileName="${sys:JM.LOG.PATH}/nacos/naming.log"
             filePattern="${sys:JM.LOG.PATH}/nacos/naming.log.%d{yyyy-MM-dd}.%i">
             <!-- 不重要 -->
         </RollingFile>
     </Appenders>
     
     <Loggers>
         <!-- 不重要 -->
         <Logger name="com.alibaba.nacos.client.config" level="${sys:com.alibaba.nacos.config.log.level:-info}"
             additivity="false">
             <AppenderRef ref="CONFIG_LOG_FILE"/>
         </Logger>
         <Logger name="com.alibaba.nacos.client.naming" level="${sys:com.alibaba.nacos.naming.log.level:-info}"
             additivity="false">
             <AppenderRef ref="NAMING_LOG_FILE"/>
         </Logger>
         <!-- 不重要 -->
     </Loggers>
 </Configuration>

通过以上日志配置可以看到,Nacos 将包名为com.alibaba.nacos.client.config的类的日志输出到${sys:JM.LOG.PATH}/nacos/config.log文件中,将包名为com.alibaba.nacos.client.naming的类的日志输出到${sys:JM.LOG.PATH}/nacos/naming.log文件中。${sys:JM.LOG.PATH}默认配置的路径就是用户目录。

接下来,我们看看 Nacos 是如何将日志配置加载进应用程序的。(实现代码请自行赏析)

 import static org.slf4j.LoggerFactory.getLogger;
 ​
 public class LogUtils {
     public static final Logger NAMING_LOGGER;
     static {
         NacosLogging.getInstance().loadConfiguration();
         NAMING_LOGGER = getLogger("com.alibaba.nacos.client.naming");
     }
 }
 public class NacosLogging {
     private AbstractNacosLogging nacosLogging;
     public void loadConfiguration() {
         try {
             nacosLogging.loadConfiguration();
         }
         // 省略...
     }
 }
 public abstract class AbstractNacosLogging {
     public abstract void loadConfiguration();
 }
 public class Log4J2NacosLogging extends AbstractNacosLogging {
     private final String location = getLocation("classpath:nacos-log4j2.xml");
     @Override
     public void loadConfiguration() {
         final LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false);
         final Configuration contextConfiguration = loggerContext.getConfiguration();
         
         // load and start nacos configuration
         Configuration configuration = loadConfiguration(loggerContext, location);
         configuration.start();
         
         // append loggers and appenders to contextConfiguration
         Map<String, Appender> appenders = configuration.getAppenders();
         for (Appender appender : appenders.values()) {
             contextConfiguration.addAppender(appender);
         }
         Map<String, LoggerConfig> loggers = configuration.getLoggers();
         for (String name : loggers.keySet()) {
             if (name.startsWith(NACOS_LOGGER_PREFIX)) {
                 contextConfiguration.addLogger(name, loggers.get(name));
             }
         }
         
         loggerContext.updateLoggers();
     }
 }

总结来说,就是先将扩展配置(即nacos-log4j2.xml)转化成LoggerConfig对象;然后将LoggerConfig实例添加到应用的日志配置上下文contextConfiguration中;最后更新应用的Loggers

三、即学即用

我们就把扩展日志当成一个对象,比如这里的「访问日志」,Nacos 中的「配置模块日志」都可以称为扩展日志。我们先来编写扩展日志的抽象AbstractLogExtend

 @Slf4j
 public abstract class AbstractLogExtend {
     public void loadConfiguration() {
         final LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false);
         final Configuration contextConfiguration = loggerContext.getConfiguration();
 ​
         // load and start casslog extend configuration
         Configuration configurationExtend = loadConfiguration(loggerContext);
         configurationExtend.start();
 ​
         // append loggers and appenders to contextConfiguration
         Map<String, Appender> appenders = configurationExtend.getAppenders();
         for (Appender appender : appenders.values()) {
             addAppender(contextConfiguration, appender);
         }
         Map<String, LoggerConfig> loggersExtend = configurationExtend.getLoggers();
         loggersExtend.forEach((loggerName, loggerConfig) ->
                 addLogger(contextConfiguration, loggerName, loggerConfig)
         );
 ​
         loggerContext.updateLoggers();
     }
     private Configuration loadConfiguration(LoggerContext loggerContext) {
         try {
             URL url = ResourceUtils.getResourceUrl(logConfig());
             ConfigurationSource source = getConfigurationSource(url);
             // since log4j 2.7 getConfiguration(LoggerContext loggerContext, ConfigurationSource source)
             return ConfigurationFactory.getInstance().getConfiguration(loggerContext, source);
         } catch (Exception e) {
             throw new IllegalStateException("Could not initialize Log4J2 logging from " + logConfig(), e);
         }
     }
     /**
      * 要扩展配置的文件名
      */
     public abstract String logConfig();
 }

AbstractLogExtend定义了两个方法,分别是:

  • loadConfiguration():加载扩展日志配置;
  • logConfig():扩展日志配置文件的路径;

然后我们把这些扩展日志加载进应用中。

 public class LogExtendInitializer {
     
     private final List<AbstractLogExtend> cassLogExtends;
     
     @PostConstruct
     public void init() {
         cassLogExtends.forEach(cassLogExtend -> {
             try {
                 cassLogExtend.loadConfiguration();
             }
             // 省略...
         });
     }
 }

到这里,基础类代码写好了。下面我们回到文章开头的需求,来看看如何实现。

首先配置访问日志accesslog-log4j.xml

 <Configuration status="WARN">
     <Appenders>
         <!-- 不重要 -->
         <RollingFile name="ACCESS_LOG" fileName="logs/access.log"
                      filePattern="logs/$${date:yyyy-MM}/access-%d{yyyy-MM-dd}-%i.log.gz">
             <!-- 不重要 -->
         </RollingFile>
     </Appenders>
 ​
     <Loggers>
         <Root level="INFO"/>
         <Logger name="com.myproject.commons.AccessLog" level="trace" additivity="false">
             <AppenderRef ref="Console"/>
             <AppenderRef ref="ACCESS_LOG"/>
         </Logger>
     </Loggers>
 </Configuration>

我这里将accesslog-log4j.xml放在了类包下。

image-20221118111846952

接着就是配置accesslog-log4j.xml的文件的路径,这里我把「访问日志」定义成了对象AccessLogConfigExtend

 public class AccessLogConfigExtend extends AbstractLogExtend {
 ​
     @Override
     public String logConfig() {
         return "classpath:com/github/open/casslog/accesslog/accesslog-log4j.xml";
     }
 ​
 }

这样访问日志就配置好了,也可以将访问日志封装成基础jar包供其他项目使用,这样其他项目就不需要重复配置了。

对于配置开关,可以使用@Conditional来实现,具体如下。

 @Configuration
 @ConditionalOnProperty(value = "casslog.accessLogEnabled")
 public class AccessLogAutoConfiguration {
 ​
     @Bean
     public AccessLogConfigExtend accessLogConfigExtend() {
         return new AccessLogConfigExtend();
     }
 ​
 }

这样实现,确实优雅了很多!

小结

本案例是我之前在做日志组件实现的一个功能,源码放在了我的 Github 上:https://github.com/studeyang/casslog。一开始实现访问日志,就是通过文章中所说的不优雅的方式实现的,后来在做消息消费的监控时,想把消费的日志单独放到新日志文件中,供 ELK 采集分析。于是提取【访问日志】与【消费监控】的功能共性,实现日志的扩展。

如果你想和我交流,欢迎关注我的微信公众号【杨同学technotes】。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/22986.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【10】leetcode note

题目&#xff1a; 799. 香槟塔 个人总结 799. 香槟 我们把玻璃杯摆成金字塔的形状&#xff0c;其中 第一层 有 1 个玻璃杯&#xff0c; 第二层 有 2 个&#xff0c;依次类推到第 100 层&#xff0c;每个玻璃杯 (250ml) 将盛有香槟。 从顶层的第一个玻璃杯开始倾倒一些香槟&…

基于铁犀牛ironrhino平台的税务档案管理系统

目录 摘要 2 引言 5 1.1 选题背景 6 1.2 国内外研究现状 7 1.3课题研究的重要性 8 2. 系统的需求与分析 8 2.1 系统的功能介绍 9 2.1.1 业务信息描述 9 2.1.2 系统功能说明 10 2.1.3 系统的开发目标 11 2.2 系统分析 12 2.2.1 铁犀牛的功能 12 2.2.2 铁犀牛工作原理 13 2.2.3 铁…

翻阅必备----Java窗口组件,容器,布局,监听,事件 API大全

---------------------------------------------------------------------------------------- &#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 秩沅 原创 收录于专栏 java ⭐Jav…

将存在课题的过程可视化,丰田的“自工程完结”是什么?

将存在课题的过程可视化&#xff0c;丰田的“自工程完结”是什么? “全日本的公司是不是都发生了奇怪的事情呢?人们常说日本人很勤劳。所以要拼命努力。但是&#xff0c;有很多人拼命努力却毫无成果。(中略)这样才不会有动力。明明很努力却没有成果&#xff0c;我认为这才是奇…

将SpringBOOT项目 打成 war 包 并 部署到 Tomcat

当前环境&#xff1a;Windows Tomcat版本&#xff1a;tomcat8.5 SpringBoot版本&#xff1a; 2.2.3 1. pom.xml 修改打包方式 <packaging>war</packaging> 2.加入SpringBoot打包插件&#xff08;pom.xml&#xff09; <build><plugins><plugin&g…

Jmeter的使用说明

一、安装Jmeter工具 链接&#xff1a;https://pan.baidu.com/s/1ZYc15eq9DO-r0ChKHxMXlg?pwdckcd 提取码&#xff1a;ckcd --来自百度网盘超级会员V5的分享二、Jmeter的常用元器件说明 jmeter八大元件件&#xff1a;取样器&#xff0c;前置处理器&#xff0c;后置处理器&a…

计算机网络——第六章笔记(1)

传输层 传输层是层是整个协议栈(TCP/IP)的核心。 任务&#xff1a;是提供可靠的、高效的数据传输。 面向连接的服务 1、端到端的连接管理 建立连接 数据传输 释放连接 2、流控制 3、差错控制 传输环境&#xff1a;通信子网、物理信道。 传输服务和网络服务的两个主…

网络了解编程五层协议

一:了解 1.了解一下网络: 局域网(LAN),一个上课的机房,多个连在同一个路由器上的设备,就是在一个局域网中---打游戏 (局域网内的主机之间能方便的进行网络通信&#xff0c;又称为内网&#xff1b;局域网和局域网之间在没有连接的情况下&#xff0c;是无法通信的) 广域网(WAN) ,…

无线 LAN 服务概述

无线 LAN 服务是 Windows Server 2008 R2 和 Windows Server 2008 中的一项功能&#xff0c;可用于启用无线 WLAN 自动配置服务&#xff0c;以及配置 WLAN 自动配置以自动启动。一旦启用后&#xff0c;WLAN 自动配置会动态选择计算机将自动连接的无线网络&#xff0c;并配置无线…

项目管理的四大模型,PM必懂的事半功倍模型!

瀑布模型、迭代模型、增量模型、原型模型&#xff0c;是项目管理常见的四种模型。每种模型都有其优缺点和适用的项目类型。项目经理针对不同的项目用对模型&#xff0c;才能起到事半功倍的作用。 今天就讲讲这四种模型及其优缺点&#xff1a; 如果你需要项目管理相关资料可拉…

代码质量与安全 | “吃狗粮”能够影响到代码质量?来了解一下!

“dogfooding”是什么&#xff1f;乍一看&#xff0c;这就是“吃狗粮”的意思&#xff0c;但其实这来源于一句俚语&#xff1a;“Eat your own dog food”&#xff0c;直译过来就是“吃自己的狗粮”&#xff0c;常用于描述公司使用自己产品的这一种情况。 “吃自己的狗粮”实践…

(更新中)【后端入门到入土!】Java+Servlet+JDBC+SSM+SpringBoot+SpringCloud 基础入门

目录 第一部分&#xff1a;Java 基础语法&#xff08;已完结&#xff09; 第二部分&#xff1a;Java 高级&#xff08;已完结&#xff09; 第三部分&#xff1a;Servlet&#xff08;待更新……&#xff09; 第四部分&#xff1a;JDBC&#xff08;待更新……&#xff09; 第…

如何搭建一套指标体系?

一、引言 (1)为什么指标体系这么重要? (2)什么是指标体系? (3)指标体系的衡量标准是什么? (4)如何去搭建一套好好的指标体系? 只要弄清楚了这4个问题,指标体系的搭建工作就迅速地开展、快速地落地,精准地产生业务价值。以上是对于数据同学而言的工作。此外,对于…

漏洞练习环境搭建笔记

Docker 安装&#xff08;ubuntu&#xff09; 1.常归删除操作 sudo apt-get autoremove docker docker-ce docker-engine docker.io containerd runc 2. 删除docker其他没有没有卸载 dpkg -l | grep docker dpkg -l |grep ^rc|awk ‘{print $2}’ |sudo xargs dpkg -P # 删除无…

雷神MixBook Air笔记本系统故障怎么重装?

雷神MixBook Air笔记本系统故障怎么重装&#xff1f;有用户使用的雷神MixBook Air笔记本系统出现了故障&#xff0c;导致无法正常的使用电脑了。这个情况我们可以使用U盘来重装一个系统&#xff0c;恢复正常的使用。那么具体要怎么去进行操作&#xff0c;看看具体的方法吧。 准…

python多维数组切片

1、数组a第0个元素&#xff08;二维数组&#xff09;下的所有子元素&#xff08;一维数组&#xff09;的第一列 import numpy as np bnp.arange(24) ab.reshape(2,3,4) print a print a[0,:,0] 2、取所有二维数组下的每个二维数组的第0个元素&#xff08;一维数组&#xff09; …

会计部门通过数字化工作流程提高生产力

会计部门通过数字化工作流程提高生产力 基于纸张的会计流程令人担忧&#xff0c;在一些企业中&#xff0c;基于纸张的会计流程正在削弱企业的竞争力。 现在&#xff0c;企业高管们比以往任何时候都更想知道哪些技术在数字业务战略中作用最大&#xff0c;在简化会计流程方面&…

机器学习模型评价指标

前文&#xff1a;https://www.cnblogs.com/odesey/p/16902836.html 如果图不能加载&#xff0c;请查看原文&#xff1a;https://www.cnblogs.com/odesey/p/16907351.html 介绍了混淆矩阵。本文旨在说明其他机器学习模型的评价指标。 1. 准确率&#xff08;Accuracy-Acc&#xf…

企业新闻媒体资源有哪些类型?从哪里找?

互联网时代&#xff0c;人们获取信息的来源主要就是网络。企业想要将自己的品牌或是产品推广出去&#xff0c;互联网宣传是很重要的步骤。 企业新闻媒体发稿推广是打响品牌知名度、做好全网营销的第一步。新闻媒体宣传不仅仅性价比很高&#xff0c;同时持续时间长&#xff0c;…

细说智能家居新标准-Matter,蓝牙在智能家居中发挥的作用

Matter是在连通性标准联盟(CSA&#xff09;下开发的互操作性协议。其联盟成员包括最大的智能家居技术制造商&#xff0c;如Amazon、Apple、Google、Samsung和其他400多家企业。Matter的通用性和开放性使物联网设备能够安全地连接和交互&#xff0c;无论制造商。例如&#xff0c…