提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
目录
一、traceId作用
当请求执行的过程中,会产生各种log日志,我们希望能通过一个ID,贯穿整个请求的链条,将完整的日志打印出来,便于进行日志分析,traceId就是起这样一种作用,将traceId输出到每一行日志中,即可实现我们需要的功能。
二、MDC实现方案
MDC 全称是 Mapped Diagnostic Context,是 Spring 框架中的一个类,它可以MDC 全称是 Mapped Diagnostic Context,是 Spring 框架中的一个类,它可以粗略的理解成是一个线程安全的存放诊断日志的容器。 MDC 可以将一些上下文信息(如用户 ID、请求 IP 等)添加到日志中,方便后续的日志分析和排查问题
话不多说,上代码
@Slf4j
@WebFilter(filterName = "httpTraceFilter", urlPatterns = "/*")
public class HttpTraceFilter extends OncePerRequestFilter implements Ordered {
private static final String TRACE_ID = "traceId";
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 10;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// 初始化traceId值
String traceIdValue = StringUtils.isEmpty(MDC.get(TRACE_ID)) ? IdUtil.fastSimpleUUID() : MDC.get(TRACE_ID);
// 设置traceId值
MDC.put(TRACE_ID, traceIdValue);
StopWatch stopWatch = new StopWatch();
stopWatch.start();
try {
filterChain.doFilter(request, responseNew);
// 重置traceId值
MDC.put(TRACE_ID, traceIdValue);
} finally {
stopWatch.stop();
// 线程结束,移除线程变量
MDC.remove(TRACE_ID);
}
}
}
三、ThreadContext实现方案
org.apache.logging.log4j.ThreadContext
是 Apache Log4j 2.x 中的一个类,用于在多线程环境中管理和传递上下文信息,类似于 Mapped Diagnostic Context (MDC)
的概念
代码如下(示例):
import org.apache.logging.log4j.ThreadContext;
@Slf4j
@WebFilter(filterName = "httpTraceFilter", urlPatterns = "/*")
public class HttpTraceFilter extends OncePerRequestFilter implements Ordered {
private static final String TRACE_ID = "traceId";
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 10;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// 获取初始的traceId值
String traceIdValue = StringUtils.isEmpty(ThreadContext.get(TRACE_ID)) ? IdUtil.fastSimpleUUID() : ThreadContext.get(TRACE_ID);
// 设置traceId
ThreadContext.put(TRACE_ID, traceIdValue);
StopWatch stopWatch = new StopWatch();
stopWatch.start();
try {
filterChain.doFilter(request, responseNew);
// 如果有后续操作,务必要重置traceId,否则会丢失
ThreadContext.put(TRACE_ID, traceIdValue);
} finally {
stopWatch.stop();
// 执行完后,清除线程变量
ThreadContext.remove(TRACE_ID);
}
}
}
logback xml文件traceId配置实例:
<springProperty scope="context" name="log.filePattern" source="hcf.log.file-pattern" defaultValue="%d{yyyy-MM-dd HH:mm:ss.SSS}|%level|%thread|%X{clientIp}|%X{traceId}|%X{rpcId}|%c.%M[%L]|%msg%n"/>
<springProperty scope="context" name="log.consolePattern" source="hcf.log.console-pattern" defaultValue="%red(%d{yyyy-MM-dd HH:mm:ss.SSS})|%highlight(%level)|%green(%thread)|%X{clientIp}|%X{traceId}|%X{rpcId}|%boldMagenta(%c.%M[%L])|%cyan(%msg%n)"/>
<!--控制台输出-->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<charset>UTF-8</charset>
<pattern>${log.consolePattern}</pattern>
</encoder>
</appender>
<!-- 日志文件 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--
按照时间与文件大小两个维度进行分割文件,%d{yyyy-MM-dd}代表按天分割,%d{yyyy-MM-dd-HH}代表按小时分割
maxHistory最大保留文件时间数,按天分割代表几天,小时分割则代表几个小时
maxFileSize单文件最大容量,超过则增加%i的值
totalSizeCap总计文件容量
-->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<FileNamePattern>${log.path}/${log.system}/${log.appName}/${log.appName}.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<maxHistory>15</maxHistory>
<maxFileSize>500MB</maxFileSize>
<totalSizeCap>20GB</totalSizeCap>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${log.filePattern}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
总结
实际使用中,用MDC的方案,发现生产上有部分日志没有traceId,有部分日志traceId错乱,A线程的traceId在B线程日志中输出了,如果遇到此类情况但又排查不出来原因,可以切换为第二种方案。