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

参考

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

TLog:tlog.yomahub.com/

背景

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

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

核心组件原理

MDC机制

Log4j2Mapped 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.puttraceId即可,如:[%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处理经纬度,降维,空间填充曲线

相关推荐
Asthenia041214 分钟前
AtomicStampedReference实现原理分析
后端
Starwow32 分钟前
微服务之gRPC
后端·微服务·golang
Asthenia041235 分钟前
AtomicMarkableReference如何解决ABA问题:深入分析
后端
Asthenia04121 小时前
Fail-Fast与快照机制深入解析及并发修改机制拷打
后端
Pasregret1 小时前
观察者模式:从博客订阅到消息队列的解耦实践
后端·观察者模式
考虑考虑2 小时前
Springboot捕获feign抛出的异常
spring boot·后端·spring
洛神灬殇2 小时前
【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 时间事件处理部分)
redis·后端
DataFunTalk2 小时前
Foundation Agent:深度赋能AI4DATA
前端·后端·算法
楽码2 小时前
一文看懂隐藏功能!语言的逃逸分析
后端·go·编程语言
RunsenLIu2 小时前
基于Django实现的图书分析大屏系统项目
后端·python·django