Http请求:添加traceId

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

目录

一、traceId作用

二、MDC实现方案

三、ThreadContext实现方案

总结


一、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线程日志中输出了,如果遇到此类情况但又排查不出来原因,可以切换为第二种方案。

### traceID在分布式系统中的作用 在现代分布式系统中,traceID是一个用于唯一标识一次请求的关键概念。当一个请求进入系统后,它可能被分发到多个服务节点上处理,在此过程中每个节点都会生成相应的日志记录[^1]。由于这些日志分布在不同的服务中,单独查看某个服务的日志往往无法全面反映整个请求的执行过程。因此,引入traceID可以将不同服务产生的日志关联起来,从而形成一条完整的请求链路。 #### TraceID作用 TraceID的主要功能在于提供了一种机制,使开发者能够在复杂的服务调用网络中追踪单次请求的具体路径及其状态变化情况。这有助于提高系统的可观察性和故障排查效率[^2]。具体来说: - **统一标识**: 它作为全局唯一的标志符贯穿于整个请求生命周期之中。 - **日志聚合**: 不同微服务之间可以通过共享相同的traceID来识别属于同一事务的操作集合。 - **性能分析**: 借助traceID, 可以测量各个阶段耗时长短进而发现瓶颈所在位置。 ### 实现方式 要在一个实际的应用程序里实现traceID的功能,通常需要考虑以下几个方面: 1. **生成规则** - 应该设计一套算法确保每次新发起的外部请求都能获得独一无二的traceID值。 2. **传播机制** - 当前端向后端发送HTTP请求或者内部各层间相互调用的时候,都需要把当前上下文中携带的traceID附加到头部(header),以便接收方知晓这是哪个整体流程的一部分[^4]。 3. **存储与检索** - 所有的中间件组件以及最终的数据持久化环节都要支持保存这个特定字段,并允许后续按照指定条件筛选相关联的信息条目出来供审查之用。 下面给出一段简单的Java代码示例展示如何利用Spring框架下的`ClientHttpRequestInterceptor`接口完成上述提到的部分需求之一——自动注入traceID至outbound HTTP headers当中去: ```java import org.springframework.http.HttpRequest; import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpResponse; public class TraceIdInterceptor implements ClientHttpRequestInterceptor { @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { String currentTraceId = MDC.get("traceId"); // Assuming SLF4J's MDC is used to store the trace ID if (currentTraceId != null && !request.getHeaders().containsKey("X-Trace-ID")) { request.getHeaders().add("X-Trace-ID", currentTraceId); } return execution.execute(request, body); } } ``` 以上片段展示了如果存在有效的MDC变量,则将其添加给即将发出的新连接对象;如果没有设置的话则不会做任何修改动作继续往下走正常逻辑流线图即可满足基本应用场景的要求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值