Logback日志框架之使用MDC动态变量根据业务、接口生成相应日志文件
前言
近日,项目上遇到个关于日志生成的特定要求:最初要求日志按日志级别进行生成相应文件,后来要求按业务、接口、服务代码生成对应的日志文件。
对于后者一下有点懵,从来没有这么玩过,因为需要动态从请求接口中获取服务代码,生成该服务代码对应的日志文件。
从网上查询了相关资料,都说使用
org.slf4j.MDC
变量,但是根据配置后,发现压根儿就不生效、不成功!于是采用了自定义Logger+ThreadLocal存储方案实现了该需求,但是总感觉代码不够优雅、不够灵活。
直到再次提了个需要:因为并发情况下日志混乱问题,要求每行日志中有相关接服务代码,方便区分、排查。这时候虽然目前方案可行,但是再次起了使用动态变量来实现的心思,因此查阅大量资料,终于成功实现,于是特此记录。
按照日志级别生成日志文件
创建一个Logback日志框架的配置文件,这个配置可以让应用程序的日志输出到控制台和按天生成的日志文件中,并根据日志级别分别记录到不同的文件中。这样可以更好地管理和分析应用程序的日志信息。
主要包含以下功能:
1.定义日志文件的存储地址LOG_PATH
2.配置控制台输出CONSOLE,输出格式为%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
3.配置按天生成日志文件APP,文件名格式为 ${LOG_PATH}/%d{yyyy-MM-dd}/app.%i.log,最大文件大小为 100MB,保存30天
4.配置按日志级别分别记录到不同文件:
ERROR_FILE: 记录错误级别的日志
WARN_FILE: 记录警告级别的日志
INFO_FILE: 记录信息级别的日志
DEBUG_FILE: 记录调试级别的日志
5.配置根Logger的日志级别为 INFO,并引用上述的所有Appender
配置logback.xml
创建日志配置文件:logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 定义日志文件的存储地址 -->
<property name="LOG_PATH" value="./logs"/>
<!-- 控制台输出 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 按天生成日志文件 -->
<appender name="APP" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 日志名称 -->
<fileNamePattern>${LOG_PATH}/%d{yyyy-MM-dd}/app.%i.log</fileNamePattern>
<!-- 日志保存天数 -->
<maxHistory>30</maxHistory>
<!-- 日志文件大小 -->
<maxFileSize>100MB</maxFileSize>
</rollingPolicy>
<encoder>
<!-- 日志输出格式 -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 按日志级别分别记录到不同文件 -->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 日志名称 -->
<fileNamePattern>${LOG_PATH}/%d{yyyy-MM-dd}/error.%i.log</fileNamePattern>
<!-- 日志保存天数 -->
<maxHistory>30</maxHistory>
<!-- 日志文件大小 -->
<maxFileSize>100MB</maxFileSize>
</rollingPolicy>
<encoder>
<!-- 日志输出格式 -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<!-- 输出错误日志 -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
</appender>
<appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/%d{yyyy-MM-dd}/warn.%i.log</fileNamePattern>
<maxHistory>30</maxHistory>
<maxFileSize>100MB</maxFileSize>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>WARN</level>
</filter>
</appender>
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/%d{yyyy-MM-dd}/info.%i.log</fileNamePattern>
<maxHistory>30</maxHistory>
<maxFileSize>100MB</maxFileSize>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
</appender>
<appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/%d{yyyy-MM-dd}/debug.%i.log</fileNamePattern>
<maxHistory>30</maxHistory>
<maxFileSize>100MB</maxFileSize>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
</appender>
<!-- 根Logger配置 -->
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="APP"/>
<appender-ref ref="ERROR_FILE"/>
<appender-ref ref="WARN_FILE"/>
<appender-ref ref="INFO_FILE"/>
<appender-ref ref="DEBUG_FILE"/>
</root>
</configuration>
配置application.yaml
配置项目使用指定的日志配置文件
logging:
config: classpath:logback.xml
输出
启动项目进行测试,如期生成相关日志文件
使用动态变量
使用MDC(Mapped Diagnostic Context) 可以为每个线程关联一些上下文信息,在日志输出时可以包含这些信息,从而区分不同线程的日志输出。
设置MDC变量
只需要通过MDC.put()
进行变量设置,然后就可以将该变量传递给Logback日志框架,当不需要的时候再通过MDC.remove()
进行删除
import org.slf4j.MDC;
@RequestMapping(value = "/test")
public String index() {
MDC.put("requestId", System.currentTimeMillis()+"");
log.info("---------------------- hello ----------------------");
// 业务逻辑
MDC.remove("requestId");
return "OK";
}
使用MDC变量
在logback.xml中,通过
%X{requestId}
形式的配置即可动态获取设置的MDC变量。
在日志输出格式中使用动态变量
<!-- 控制台输出 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] -%X{requestId}- %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
输出
使用压测工具进行大批量的发送请求测试,可如期得到预计结果。
自定义Logger
通过自定义Logger对象,在相关业务、接口中使用该日志对象,即可实现按业务、接口生成对应日志文件。但是此种方式需要定义大量相关Logger对象,非常不够优雅。
配置logback.xml
配置定义了一个名为 "ONEAPI" 的Appender,再将称为 "ONEAPI"的 Appender 与称为 "apiLog"的 Logger关联起来,将日志输出到 "ONEAPI" 关联的Appender定义文件中。
在特定的模块、包、类中使用这个指定的自定义Logger
<!-- 自定义某个接口日志文件 -->
<appender name="ONEAPI" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 日志名称 -->
<fileNamePattern>${LOG_PATH}/%d{yyyy-MM-dd}/apiLog.%i.log</fileNamePattern>
<!-- 日志保存天数 -->
<maxHistory>30</maxHistory>
<!-- 日志文件大小 -->
<maxFileSize>100MB</maxFileSize>
</rollingPolicy>
<encoder>
<!-- 日志输出格式 -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="apiLog" leve="info" additivity="false">
<appender-ref ref="ONEAPI"/>
</logger>
也可以直接指定包、类使用的Logger,在包中、类中使用Logger将自动关联这个自定义Logger
<logger name="com.xxx.face.web.HelloControler" leve="info" additivity="false">
<appender-ref ref="ONEAPI"/>
</logger>
使用指定Logger
@RequestMapping(value = "/test")
public String index() {
Logger apiLog = LoggerFactory.getLogger("apiLog");
apiLog.info("---------------------- apiLog ----------------------");
return "OK";
}
输出
经过测试也会如期得到期望的结果。
根据业务、接口划分日志文件
基于自定义Logger+TreadLocal方案实现后,由于需求、代码不优雅等原因,还是想找其他方法,在查询大量相关资料后,终于使用上述的MDC动态变量相关东西实现根据业务、接口划分日志文件。
配置logback.xml
如果要根据业务、接口划分日志文件,这里无疑要使用到上述的MDC动态变量相关东西。
配置分析:
当requestId这个MDC变量不存在时,就会使用other作为默认值。这可以确保每个请求都有独立的日志文件。
在appender的文件名中使用 ${requestId},这样可以根据requestId这个MDC变量的值,动态生成对应的日志文件名。
在rollingPolicy中配置按日期滚动,可以实现按天滚动日志文件,避免单个日志文件过大。
在encoder的pattern中使用%X{requestId},可以在日志输出中包含 requestId 这个 MDC 变量的值,方便追踪和定位问题。
<!-- 使用 MDC 的 appender -->
<appender name="FILE_CUSTOM" class="ch.qos.logback.classic.sift.SiftingAppender">
<discriminator>
<key>requestId</key>
<defaultValue>other</defaultValue>
</discriminator>
<sift>
<!-- 标准的文件输出Appender, 文件名根据MDC动态生成 -->
<appender name="FILE-%{requestId}" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 滚动策略:根据时间来制定滚动策略.既负责滚动也负责出发滚动 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志输出位置 可相对和绝对路径 -->
<fileNamePattern>${LOG_PATH}/%d{yyyy-MM-dd}/${requestId}.log</fileNamePattern>
</rollingPolicy>
<encoder charset="UTF-8">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] -%X{requestId}- %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
</sift>
</appender>
设置MDC变量
通过MDC.put()
进行变量设置,然后就可以将该变量传递给Logback日志框架,当不需要的时候再通过MDC.remove()
进行删除
@RequestMapping(value = "/test")
public String index() {
MDC.put("requestId", System.currentTimeMillis()+"");
log.info("---------------------- HelloControler ----------------------");
MDC.remove("requestId");
return "OK";
}
输出
同样经过测试得出预期结果。
按日期滚动、按大小分割
这里模拟测试一个称为000001
服务接口,指定文件大小1MB,进行循环请求生成日志
<appender name="FILE_CUSTOM" class="ch.qos.logback.classic.sift.SiftingAppender">
<discriminator>
<key>requestId</key>
<defaultValue>other</defaultValue>
</discriminator>
<sift>
<appender name="FILE-%{requestId}" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/%d{yyyy-MM-dd}/${requestId}-%i.log</fileNamePattern>
<maxFileSize>1MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
<encoder charset="UTF-8">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] -%X{requestId}- %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
</sift>
</appender>
同样如期得出正确结果:
转载自:https://juejin.cn/post/7390645125907234866