阿里TTl使用管理日志

在管理日志的时候我们需要查看生成日志都是那些人干了那些事,那么怎么在日志上查看这些事情呢,首先呢可以直接使用Slf4j,然后再配置文件里配置一下

#日志文件最大上限
logging.file.max-size=100MB
#日志文件存储位置
logging.file.path=./logs
#日志文件输出格式	(5level:线程等级,traceID:线程名,PIDL:当前线程ID, logger{50}:日志名称最多50字符 ,%m:日志内容 ,%n:换行)
logging.pattern.console= "%d{yyyy-MM-dd HH:mm:ss.SSS} %clr(%-5level) %clr([%X{traceId}]) %clr(${PID:-}) --- %clr(%logger{50}) - %m%n"

这里我们的线程名可以使用用户信息里具有唯一标识的来标记,我们直接在过滤器里获取用户信息

package com.sunflower.filter;

import cn.hutool.core.util.IdUtil;
import org.slf4j.MDC;
import org.springframework.core.annotation.Order;
import org.springframework.util.StringUtils;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@Order(1)
@WebFilter(urlPatterns = "/*",filterName = "traceIdFilter")
public class ThreadFilter implements Filter {

    public final static String MDC_TRACE_ID = "traceId";

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String traceId = httpRequest.getHeader(MDC_TRACE_ID);
        if (StringUtils.isEmpty(traceId)) {
            traceId = IdUtil.fastSimpleUUID();;
        }
        MDC.put(MDC_TRACE_ID, traceId);

        chain.doFilter(request, response);
    }
}

配置完成后输出端日志就是这样的
在这里插入图片描述
但是这样只能在同一个日志里操作,如果开启一个子线程那么子线程就没法获取到thradId 因为Slf4j默认只用的是Threadlocal线程,普通 ThreadLocal 类允许每个线程拥有自己的独立变量副本,这些变量只对该线程可见,且不会影响其他线程。然而,当使用 InheritableThreadLocal 时,如果一个线程创建了另一个线程(例如,在调用 Thread.start() 方法时),父线程中 InheritableThreadLocal 绑定的值会被自动复制(或通过 childValue 方法进行转换)给新创建的子线程。
这意味着,如果你在一个线程中设置了一个 InheritableThreadLocal 的值,那么这个值不仅在这个线程中可用,而且在任何由这个线程派生出来的子线程中也将是初始的有效值。这对于需要跨线程层级传递特定上下文信息的场景非常有用。
在内部实现上,InheritableThreadLocal 同样依赖于 ThreadLocalMap 结构来为每个线程存储本地变量的值,不过在子线程初始化时,会额外处理父线程的 InheritableThreadLocal 值的传递。
但是当你new一个新线程后还是一样子线程没有内容,那怎么办呢
在这里插入图片描述
这时我们就用到了阿里提供的TTL(TransmittableThreadLoca)
引入依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>transmittable-thread-local</artifactId>
    <version>2.11.5</version>
    <scope>compile</scope>
</dependency>

重写(LogbackMDCAdapter)类(必须写在org.slf4j包下)

在这里插入图片描述

package org.slf4j;
 
import ch.qos.logback.classic.util.LogbackMDCAdapter;
import org.slf4j.spi.MDCAdapter;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
 
/**
 * 描述:
 * 重写{@link LogbackMDCAdapter}类,搭配TransmittableThreadLocal实现父子线程之间的数据传递
 * 内容直接从{@link LogbackMDCAdapter}类中copy。把copyOnThreadLocal实例化对象更改为TransmittableThreadLocal即可
 */
public class TtlMDCAdapter implements MDCAdapter {
    final ThreadLocal<Map<String, String>> copyOnThreadLocal = new InheritableThreadLocal();
    private static final int WRITE_OPERATION = 1;
    private static final int MAP_COPY_OPERATION = 2;
    final ThreadLocal<Integer> lastOperation = new ThreadLocal();
    private static TtlMDCAdapter mtcMDCAdapter;
    static {
        mtcMDCAdapter = new TtlMDCAdapter();
        MDC.mdcAdapter = mtcMDCAdapter;
    }
 
    public TtlMDCAdapter() {
    }
    public static MDCAdapter getInstance() {
        return mtcMDCAdapter;
    }
 
    private Integer getAndSetLastOperation(int op) {
        Integer lastOp = (Integer)this.lastOperation.get();
        this.lastOperation.set(op);
        return lastOp;
    }
 
    private boolean wasLastOpReadOrNull(Integer lastOp) {
        return lastOp == null || lastOp == MAP_COPY_OPERATION;
    }
 
    private Map<String, String> duplicateAndInsertNewMap(Map<String, String> oldMap) {
        Map<String, String> newMap = Collections.synchronizedMap(new HashMap());
        if (oldMap != null) {
            synchronized(oldMap) {
                newMap.putAll(oldMap);
            }
        }
 
        this.copyOnThreadLocal.set(newMap);
        return newMap;
    }
 
    public void put(String key, String val) throws IllegalArgumentException {
        if (key == null) {
            throw new IllegalArgumentException("key cannot be null");
        } else {
            Map<String, String> oldMap = (Map)this.copyOnThreadLocal.get();
            Integer lastOp = this.getAndSetLastOperation(WRITE_OPERATION);
            if (!this.wasLastOpReadOrNull(lastOp) && oldMap != null) {
                oldMap.put(key, val);
            } else {
                Map<String, String> newMap = this.duplicateAndInsertNewMap(oldMap);
                newMap.put(key, val);
            }
 
        }
    }
 
    public void remove(String key) {
        if (key != null) {
            Map<String, String> oldMap = (Map)this.copyOnThreadLocal.get();
            if (oldMap != null) {
                Integer lastOp = this.getAndSetLastOperation(WRITE_OPERATION);
                if (this.wasLastOpReadOrNull(lastOp)) {
                    Map<String, String> newMap = this.duplicateAndInsertNewMap(oldMap);
                    newMap.remove(key);
                } else {
                    oldMap.remove(key);
                }
 
            }
        }
    }
 
    public void clear() {
        this.lastOperation.set(WRITE_OPERATION);
        this.copyOnThreadLocal.remove();
    }
 
    public String get(String key) {
        Map<String, String> map = (Map)this.copyOnThreadLocal.get();
        return map != null && key != null ? (String)map.get(key) : null;
    }
 
    public Map<String, String> getPropertyMap() {
        this.lastOperation.set(MAP_COPY_OPERATION);
        return (Map)this.copyOnThreadLocal.get();
    }
 
    public Set<String> getKeys() {
        Map<String, String> map = this.getPropertyMap();
        return map != null ? map.keySet() : null;
    }
 
    public Map<String, String> getCopyOfContextMap() {
        Map<String, String> hashMap = copyOnThreadLocal.get();
        return hashMap == null ? null : new HashMap(hashMap);
    }
 
    public void setContextMap(Map<String, String> contextMap) {
        this.lastOperation.set(WRITE_OPERATION);
        Map<String, String> newMap = Collections.synchronizedMap(new HashMap());
        newMap.putAll(contextMap);
        this.copyOnThreadLocal.set(newMap);
    }
}

更改spring.factories
在resource/META-INF/spring.factories中进行配置。

org.springframework.context.ApplicationContextInitializer=\
com.jarvan.demo.config.TtlMDCAdapterInitializer

TtlMDCAdapterInitializer类如下

package com.by.config;
 
import org.slf4j.TtlMDCAdapter;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
 
public class TtlMDCAdapterInitializer implements ApplicationContextInitializer {
    @Override
    public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
        TtlMDCAdapter.getInstance();
    }
}

结果展示
在这里插入图片描述

<think>好的,我现在需要详细分析用户关于logback-mdc-ttl使用示例的查询。用户之前已经询问过仅使用Logback时增强MDC功能的方法,现在特别关注logback-mdc-ttl的具体使用示例。 首先,用户可能已经了解了logback-mdc-ttl的基本概念,但需要具体的步骤和代码示例来实际应用。我需要确保回答的结构清晰,步骤详细,并且符合用户之前提到的系统级指令,比如正确使用LaTeX格式,中文化回答,以及结构分明。 接下来,我应该回顾logback-mdc-ttl的作用。它通过TransmittableThreadLocal(TTL)来传递MDC上下文,解决线程池中MDC内容丢失的问题。用户可能需要知道如何添加依赖、配置Logback以及编写示例代码,同时验证功能是否生效。 我需要检查是否有遗漏的步骤,比如依赖版本是否正确,配置是否需要特别注意,以及示例代码是否完整。同时,用户可能关心常见问题,比如是否需要修改原有代码,以及与其他库的兼容性。 另外,用户之前提到的注意事项,如清理MDC避免内存泄漏,也需要在示例中提及。确保用户了解在使用线程池后正确清理MDC的重要性。 最后,总结使用logback-mdc-ttl的优势,比如简化代码、自动传递上下文,同时提醒可能的兼容性问题。确保回答覆盖所有关键点,帮助用户顺利实现MDC的跨线程传递。</think>--- ### **`logback-mdc-ttl` 使用示例** `logback-mdc-ttl` 是 Logback 的扩展库,基于阿里开源的 `TransmittableThreadLocal`(TTL)实现 **跨线程池的 MDC 上下文传递**,无需修改默认的 `MDCAdapter`。以下是完整使用步骤: --- #### **1. 添加依赖** 需在项目中引入 `logback-mdc-ttl`(以 Maven 为例): ```xml <dependency> <groupId>com.ofcoder</groupId> <artifactId>logback-mdc-ttl</artifactId> <version>1.0.1</version> <!-- 检查最新版本 --> </dependency> ``` --- #### **2. 配置 `logback.xml`** 在 Logback 配置文件中启用 TTL 的上下文监听器: ```xml <configuration> <!-- 启用 TTL 的 MDC 上下文传递 --> <contextListener class="com.ofcoder.logback.mdc.TtlMDCContextListener" /> <!-- 示例:控制台输出 MDC 字段 --> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{HH:mm:ss} [%thread] %X{requestId} - %msg%n</pattern> </encoder> </appender> <root level="INFO"> <appender-ref ref="CONSOLE" /> </root> </configuration> ``` --- #### **3. 代码示例** 在父线程设置 MDC,提交任务到线程池时自动传递上下文: ```java import org.slf4j.MDC; public class Demo { public static void main(String[] args) { // 主线程设置 MDC MDC.put("requestId", "REQ-123"); // 使用线程池(需配合 TTL 线程池包装类) ExecutorService executor = Executors.newFixedThreadPool(2); ExecutorService ttlExecutor = com.alibaba.ttl.threadpool.TtlExecutors.getTtlExecutorService(executor); // 提交任务 ttlExecutor.submit(() -> { // 子线程自动继承 MDC 的 requestId System.out.println("子线程 MDC: " + MDC.get("requestId")); // 输出 REQ-123 logger.info("异步任务执行"); // 日志中会包含 requestId=REQ-123 }); // 清理主线程 MDC MDC.clear(); executor.shutdown(); } } ``` --- #### **4. 关键注意事项** 1. **必须包装线程池** 使用 `TtlExecutors.getTtlExecutorService()` 对原生线程池进行包装,否则 MDC 无法传递。 2. **自动清理 MDC** 子线程任务结束后,`logback-mdc-ttl` 会自动清理 MDC,无需手动调用 `MDC.clear()`(除非任务中新增了 MDC 字段)。 3. **支持场景** - 线程池(`ExecutorService`) - 定时任务(`ScheduledExecutorService`) - ForkJoinPool(需配合 `TtlForkJoinPoolHelper`) 4. **日志格式配置** 确保日志模式(`<pattern>`)包含 `%X{key}` 占位符以输出 MDC 字段(例如 `%X{requestId}`)。 --- #### **5. 验证 MDC 传递** 运行代码后检查日志输出,确认子线程日志中包含父线程设置的 `requestId`: ``` 14:30:45 [pool-1-thread-1] REQ-123 - 异步任务执行 ``` --- ### **常见问题** 1. **不生效怎么办?** - 检查是否遗漏了 `TtlMDCContextListener` 配置。 - 确保线程池已通过 `TtlExecutors` 包装。 - 确认日志模式中正确使用了 `%X{key}`。 2. **兼容性** `logback-mdc-ttl` 依赖 `TransmittableThreadLocal`,需避免与其他 TTL 封装库冲突(如 `alibaba/transmittable-thread-local`)。 --- ### **总结** 通过 `logback-mdc-ttl` 可实现: - ✅ **零侵入**:无需修改现有 `MDC.put()`/`MDC.remove()` 代码。 - ✅ **自动跨线程传递**:解决异步任务中 MDC 上下文丢失问题。 - ✅ **轻量级**:仅需添加依赖和简单配置。 适用于需要链路追踪(如请求ID透传)或异步日志分类的场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值