阿里TTL+Log4j2+MDC实现轻量级日志链路追踪

阿里TTL+Log4j2+MDC实现轻量级日志链路追踪

精选文章moguli202025-03-27 12:23:1311A+A-

参考

阿里TTL:
https://github.com/alibaba/transmittable-thread-local

TLog:https://tlog.yomahub.com/

背景

推荐阅读:
https://tlog.yomahub.com/pages/5b7bd2/,本篇文章也是看了TLog的官方文档和相关源码而产生的。

在微服务架构中,由于线程池复用、异步调用等机制的存在,传统的线程级日志标识(如ThreadLocal)会导致请求链路断裂。例如,当主线程将任务提交到线程池时,子线程无法自动继承主线程的上下文信息,使得日志中的traceId丢失。

核心组件原理

MDC机制

Log4j2的Mapped Diagnostic Context(MDC)通过ThreadLocal存储线程级上下文数据。例如,在HTTP请求进入时,通过拦截器将traceId存入MDC,日志模板中通过%X{traceId}动态替换值。

TransmittableThreadLocal(TTL)

阿里的TTL组件解决了线程池场景下的上下文传递问题。通过装饰线程池,TTL在任务提交时自动拷贝父线程的上下文到子线程,并在任务结束后清理副本,确保多级线程池调用链路完整。

日志模板配置

在log4j2.xml中配置PatternLayout,添加%X{traceId}占位符即可实现日志标识嵌入。


实现方案详解

1、TraceId生成

  • 简单场景:使用UUID生成唯一标识(代码示例)
  • 分布式场景:建议采用雪花算法(Snowflake),结合机器ID和时间戳生成全局唯一ID

2、组件集成



    org.apache.logging.log4j
    log4j-core
    2.17.1




    com.alibaba
    transmittable-thread-local
    2.14.2

如果是Springboot使用Log4j2要排除默认日志



    org.springframework.boot
    spring-boot-starter-web
    
        
        
            org.springframework.boot
            spring-boot-starter-logging
        
    




    org.springframework.boot
    spring-boot-starter-log4j2

3、链路上下文

/**
 * 基于TransmittableThreadLocal实现线程池安全的TraceID传递
 *
 * @author wnhyang
 * @date 2025/3/3
 **/
public class TraceContext {

    private static final TransmittableThreadLocal TRACE_ID = new TransmittableThreadLocal<>();

    /**
     * 设置TraceID,并同步到Log4j2的MDC
     */
    public static void setTraceId(String traceId) {
        TRACE_ID.set(traceId);
    }

    public static String getTraceId() {
        return TRACE_ID.get();
    }

    public static void clear() {
        TRACE_ID.remove();
    }

    public static String generateTraceId() {
        return IdUtil.simpleUUID();
    }
}

4、生成并传递TraceId

需要注意这里只是写了作为链路源头生成TraceId和向指定的下游传递的示例。

然而在实际情况下通常会比较复杂,因为源头通常是不可知的(或是说不是那么清楚的),所以要包含几个步骤:1、检查上游是否有TraceId传递;2、生成唯一TraceId;3、传递TraceId。

这些在TLog中都以插件的形式实现,可以参考,不过TLog好像没有兼容JDK17,可以参考自己实现。另外正如TLog官方所讲:“当然分布式追踪系统是一个最终的解决方案,如果您的公司已经上了分布式追踪系统,那TLog并不适用。”

/**
 * @author wnhyang
 * @date 2025/3/3
 **/
@Slf4j
public class TraceFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try {
            // 生成并设置TraceID
            String traceId = TraceContext.generateTraceId();
            TraceContext.setTraceId(traceId);
            MDC.put(TraceConstants.TRACE_KEY, traceId);

            // 透传TraceID到下游(可选)
            response.setHeader(TraceConstants.TRACE_HEADER_KEY, traceId);

            filterChain.doFilter(request, response);
        } finally {
            // 必须清理上下文
            TraceContext.clear();
            MDC.remove(TraceConstants.TRACE_KEY);
        }
    }

}

5、装饰线程池

链路上下文中已经使用了TTL存放TraceId, 所以这里就是装饰线程池。

当然方式也不至这里的一种,还有如使用TtlExecutors直接包装线程池等方式。

/**
 * @author wnhyang
 * @date 2025/3/3
 **/
public class ThreadPoolFactory {
    public static ThreadPoolTaskExecutor createExecutor(int coreSize,
                                                        int maxSize,
                                                        int queueCapacity,
                                                        String threadNamePrefix,
                                                        RejectedExecutionHandler rejectedExecutionHandler) {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(coreSize);
        executor.setMaxPoolSize(maxSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setThreadNamePrefix(threadNamePrefix);
        executor.setRejectedExecutionHandler(rejectedExecutionHandler);
        executor.setTaskDecorator(getTraceContextDecorator());
        executor.initialize();
        return executor;
    }

    private static TaskDecorator getTraceContextDecorator() {
        return runnable -> TtlRunnable.get(() -> {
            try {
                MDC.put(TraceConstants.TRACE_KEY, TraceContext.getTraceId());
                runnable.run();
            } finally {
                MDC.clear();
            }
        });
    }
}

6、Log4j2配置

配置上前面MDC.put的traceId即可,如:[%X{traceId}]。

效果展示

这里只有复用线程池的效果展示。。。

可以看到链路号在多线程下仍然是一致的,而且多个链路下不会污染。

关于项目

https://github.com/wnhyang/coolGuard

进度

最近主要有两点更新:1、决策流程优化,之前太过依赖LiteFlow了,最近的更新对这块进行了改善;2、项目组织结构,这个一直是我不满意的地方,而且历史提交中这样的大改已经不是一次两次的了。

之后应该要考虑分支管理了,在开源项目完善的同时新增Pro版,增加一些更丰富的内容。

当然上面的测试不是很严谨,不过也能说明一些问题。而且项目开发期间一直有向性能、项目架构等等方面考虑。

推荐文章

规则引擎可以应用于哪些系统,用户画像、触达、风控、推荐、监控...

基于规则引擎的风控决策系统介绍与演示

风控系统之规则重复触发

LiteFlow上下文与组件设计,数据依赖梳理

交易事件的生命周期,事前事中事后风控,结果通知/回调

策略/规则篇

风控系统之普通规则条件,使用LiteFlow实现

风控系统之通用规则条件设计,算术单元/逻辑单元/函数式接口

LiteFlow决策系统的策略模式,顺序、最坏、投票、权重

指标篇

风控系统指标计算/特征提取分析与实现01,Redis、Zset、模版方法

基于LiteFlow的风控系统指标版本控制

风控系统指标版本管理,前端实现

风控系统之指标回溯,历史数据重跑

业务链指标,用户行为模式识别,埋点系统

数据篇

风控系统之数据服务,名单、标签、IP、设备、地理信息、征信等

GeoHash处理经纬度,降维,空间填充曲线

点击这里复制本文地址 以上内容由莫古技术网整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!
qrcode

莫古技术网 © All Rights Reserved.  滇ICP备2024046894号-2