阿里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处理经纬度,降维,空间填充曲线

相关推荐
鬼火儿4 小时前
SpringBoot】Spring Boot 项目的打包配置
java·后端
cr7xin4 小时前
缓存三大问题及解决方案
redis·后端·缓存
间彧5 小时前
Kubernetes的Pod与Docker Compose中的服务在概念上有何异同?
后端
间彧5 小时前
从开发到生产,如何将Docker Compose项目平滑迁移到Kubernetes?
后端
间彧5 小时前
如何结合CI/CD流水线自动选择正确的Docker Compose配置?
后端
间彧5 小时前
在多环境(开发、测试、生产)下,如何管理不同的Docker Compose配置?
后端
间彧5 小时前
如何为Docker Compose中的服务配置健康检查,确保服务真正可用?
后端
间彧5 小时前
Docker Compose和Kubernetes在编排服务时有哪些核心区别?
后端
间彧6 小时前
如何在实际项目中集成Arthas Tunnel Server实现Kubernetes集群的远程诊断?
后端
brzhang6 小时前
读懂 MiniMax Agent 的设计逻辑,然后我复刻了一个MiniMax Agent
前端·后端·架构