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

参考

阿里TTL:github.com/alibaba/tra...

TLog:tlog.yomahub.com/

背景

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

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

核心组件原理

MDC机制

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

TransmittableThreadLocal(TTL)

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

日志模板配置

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

xml 复制代码
<Property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{traceId}] [%t] %-5level %logger{36} - %msg%n"/>

实现方案详解

1、TraceId生成

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

2、组件集成

xml 复制代码
<!-- Log4j2核心依赖 -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.17.1</version>
</dependency>

<!-- 阿里TTL -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>transmittable-thread-local</artifactId>
    <version>2.14.2</version>
</dependency>

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

xml 复制代码
<!-- 排除默认日志 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <!--排除默认log-->
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<!-- log4j2 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

3、链路上下文

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

    private static final TransmittableThreadLocal<String> 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并不适用。"

java 复制代码
/**
 * @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直接包装线程池等方式。

java 复制代码
/**
 * @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}]。

xml 复制代码
<Property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{traceId}] [%t] %-5level %logger{36} - %msg%n"/>

效果展示

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

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

关于项目

github.com/wnhyang/coo...

进度

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

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

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

推荐文章

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

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

风控系统之规则重复触发

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

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

策略/规则篇

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

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

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

指标篇

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

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

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

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

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

数据篇

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

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

相关推荐
步行cgn1 小时前
Spring MVC 视图机制的核心接口与类
java·开发语言·后端·spring·mvc
码事漫谈2 小时前
AI 读你的提示词,其实很没耐心
后端
用户8356290780513 小时前
如何使用 Python 格式化 Excel 单元格中的长文本
后端·python
我命由我123453 小时前
Python 开发 - Python 的 pathlib
java·开发语言·后端·python·学习·java-ee·学习方法
IT_陈寒3 小时前
Java性能优化完全指南
前端·人工智能·后端
电子IT笔记3 小时前
对接群晖NAS,上传和删除踩坑记录
前端·后端
Mk598783 小时前
P4:把 Agent 的记忆搬进 Redis —— AgentScope-Java 工程化实战
后端
雨中飘荡的记忆3 小时前
Claude Code 入门必会:@、!、Slash Commands,一篇讲清楚
人工智能·后端
用户8356290780513 小时前
如何使用 Python 在 Word 文档中创建和格式化列表
后端·python
开心就好20253 小时前
uni-app 本地打包 iOS,在 Windows 上使用 KXBuild 产出 ipa 并上架的完整流程
后端·ios