使用MDC跟踪日志

Java之使用 MDC 实现日志链路跟踪 本文介绍了一种轻量级的日志链路跟踪实现方案,采用MDC技术解决微服务环境下请求日志追踪问题。该方案通过拦截器为每个请求生成唯一traceId并存入MDC,在日志格式中配置traceId输出,实现服务内部日志追踪。同时封装HTTP工具类传递traceId,并改造异步线程池确保多线程环境下traceId正常传递。相比Skywalking等复杂方案,本方法具有零侵入、低运维成本的优势,通过简单的uuid生成和日志配置,即可实现全链路请求日志追踪,提升问题排查效率。 阅读详情

1.概念

MDC(Mapped Diagnostic Context,映射调试上下文)是 log4j 和 logback 提供的一种方便在多线程条件下记录日志的功能,也可以说是一种轻量级的日志跟踪工具。
MDC使用的是ThreadLocal记录数据,把数据和当前线程进行绑定,一次请求进来的所有操作都能看到

2.使用:

在用户请求进来的时候生成当前请求的traceId(使用拦截器在请求进来的时候生成,traceId要唯一),放到MDC中

拦截器

@Component
@Slf4j
public class MDCInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String tractId = MDC.get(Constants.TRACE_ID);
        if (tractId == null) {
            tractId = TraceIdUtil.getTraceId();
        }
        MDC.put(Constants.TRACE_ID, tractId);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        MDC.remove(Constants.TRACE_ID);
    }
}

配置拦截器

@Autowired
	private MDCInterceptor mdcInterceptor;
	
@Override
	public void addInterceptors(InterceptorRegistry registry) {
		registry.addInterceptor(mdcInterceptor).excludePathPatterns("/login");
	}

2.在日志配置文件中配置打印traceId

    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %thread | [%X{traceId}] | %-5level %logger{50} %msg%n</pattern>
            <charset>utf8</charset>
        </encoder>
    </appender>

配置完之后打印一个日志就能看到traceId

3.MDC传递值给子线程

MDC.getCopyOfContextMap() 该方法获取当前线程MDC中的数据,再把获取到的数据放到子线程中即可,项目中我们一般不会直接new一个thread,而是使用线程池,所以这里继承ThreadPoolExecutor重写线程池,在线程池的线程中进行设置

线程池代码

public class ThreadPoolExecutorMdcWrapper extends ThreadPoolExecutor {
    public ThreadPoolExecutorMdcWrapper(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    public ThreadPoolExecutorMdcWrapper(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
    }

    public ThreadPoolExecutorMdcWrapper(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
    }

    public ThreadPoolExecutorMdcWrapper(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
    }

    @Override
    public void execute(Runnable runnable) {
        super.execute(MDCUtil.wrap(runnable,MDC.getCopyOfContextMap()));
    }

    @Override
    public Future<?> submit(Runnable runnable) {
        return super.submit(MDCUtil.wrap(runnable,MDC.getCopyOfContextMap()));
    }

    @Override
    public <T> Future<T> submit(Runnable runnable, T result) {
        return super.submit(MDCUtil.wrap(runnable,MDC.getCopyOfContextMap()), result);
    }

    @Override
    public <T> Future<T> submit(Callable<T> callable) {
        return super.submit(MDCUtil.wrap(callable,MDC.getCopyOfContextMap()));
    }
}

MDC工具类

public class MDCUtil {
    public static Runnable wrap(final Runnable runnable, final Map<String, String> context) {
        return () -> {
            if (context == null) {
                MDC.clear();
            }else {
                MDC.setContextMap(context);
            }
            //如果不是子线程的话先生成traceId
            setTraceIdIfAbsent();
            try {
                runnable.run();
            }finally {
                MDC.clear();
            }
        };
    }


    public static <T> Callable<T> wrap(final Callable<T> callable, final Map<String, String> context) {
        return () -> {
            if (context == null) {
                MDC.clear();
            } else {
                MDC.setContextMap(context);
            }
            setTraceIdIfAbsent();
            try {
                return ((T) callable.call());
            }finally {
                MDC.clear();
            }
        };

    }

    public static void setTraceIdIfAbsent() {
        if (MDC.get(Constants.TRACE_ID) == null) {
            MDC.put(Constants.TRACE_ID, TraceIdUtil.getTraceId());
        }
    }
}

把线程池交给spring管理

@Configuration
public class BeanConfig {

    @Bean
    public ThreadPoolExecutorMdcWrapper getThreadPoolExecutorMdcWrapper() {
        return new ThreadPoolExecutorMdcWrapper(5,
                10,
                5,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(5)
        );
    }
}

测试

@GetMapping("getIp")
	public String getIp() {
		log.info("getIp");
		threadPoolExecutorMdcWrapper.execute(()->{
			log.info("子线程  getIp");
		});
		return "getIp";
	}

打印出来的traceId一致
在这里插入图片描述

4.使用restTemplate进行http调用MDC传值

restTemplate拦截器

public class RestTemplateTraceIdInterceptor implements ClientHttpRequestInterceptor {
    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        String traceId = MDC.get(Constants.TRACE_ID);
        if (traceId != null) {
            request.getHeaders().add(Constants.TRACE_ID, traceId);
        }
        return execution.execute(request, body);
    }
}

配置拦截器

 @Bean
    public RestTemplate restTemplate( ) {
        RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
        RestTemplate restTemplate = restTemplateBuilder.build();
        //设置Resttemplate的拦截器
        restTemplate.setInterceptors(Collections.singletonList(new RestTemplateTraceIdInterceptor()));
        return restTemplate;
    }
Springboot使用MDC进行日志追踪 MDC(Mapped Diagnostic Context)是一个可以追踪程序上下文日志的东西,是springboot项目自带的org.slf4j包下的类,无需引入额外依赖即可使用。 阅读详情

相关推荐

【日志】MDC 一个Java 项目中用于日志跟踪的工具

MDC 是一个在 Java 项目中用于日志跟踪的工具,它允许你在多线程环境下关联和传递特定的上下文信息。

代码的魅力,根本走不出来 3433

MDC学习笔记

MDC是可以帮组我们 在多线程条件下记录追踪日志的功能,它支持 Log4J和LogBack 两种日志框架通常打印出的日志会有线程号等信息来标志当前日志属于哪个线程,然而由于线程是可以重复使用的,所以并不能很清晰的确认一个请求的日志范围。手动生成一个唯一序列号打印在日志中;使用日志控件提供的MDC功能,生成一个唯一序列标记一个线程的日志在现网出现故障时,我们经常需要获取一次请求流程里的所有日志进行定位。

Shawn的个人博客 4450

Java中MDC是干嘛的,有什么作用

Log4j、Logback或者Log4j2等日志中最常见区分同一个请求的方式是通过线程名,而如果请求量大,线程名在相近的时间内会有很多重复的而无法分辨,因此引出了trace-id,即在接收到的时候生成唯一的请求id,在整个执行链路中带上此唯一id.2、上面方式,只能在当前线程中获取到值,因为 MDC 的实现原理就是基于 ThreadLocal,而 ThreadLocal 的主要作用就是只对当前线程有效。MDC.java本身不提供传递traceId的能力,真正提供能力的是MDCAdapter接口的实现。

hkl_Forever的博客 3233

MDC:更好的日志记录方式

MDC(Mapped Diagnostic Context)是一种用于在日志记录过程中传递上下文信息的机制。它允许将自定义的键值对与日志记录相关联,并在日志输出时自动将这些键值对添加到日志消息中。

于琦海 2994

QComboBox设置最大显示数量多余的用滑动条,以及QComboBox::setMaxVisibleItems()不起作用的问题

QComoboBox是Qt封装的一个选择框的东西,但是我在使用过程中有一点感觉很不好,如图: 不论你往里面添加多少个它都会给你显示全部,甚至给你铺满全屏,后来看了官方文档发现有个setMaxVisibleItems()的方法,我欣喜若狂啊,终于特么的能解决了,但是,很遗憾,不知为啥,我设置上之后并没有起作用,依然会显示全部,后来搜了百度发现有个人是这样解决的 QComboBox *box = ne...

Aboveground的博客 4918

【成功安装】Ubuntu22.04极简方式安装Nvidia驱动

今天下午安装了实机的Ubuntu,面临在Linux上安装Nvidia的问题,被这个问题困扰了一整个下午,各种报错整的我头大。后来发现了不需要按照传统的方式去Nvidia官网下载,再命令行安装,直接在线安装即可。

清筱明的博客 5345

使用 MDC 实现日志链路跟踪

1、背景简述在技术运维过程中,很难从某服务庞杂的日志中,单独找寻出某次 API 调用的全部日志。为提高排查问题的效率,在多个系统及应用内根据 统一的 TraceId 查找同一次请求链路上的日志,根据日志快速定位问题,同时需对业务代码无侵入,特别是在高频请求下,也可以方便的搜索此次请求的日志内容。

jj1245_的博客 882

Unity WebGL发布避坑指南:从内存设置到字体打包,一次搞定所有常见问题

本文详细解析Unity WebGL发布的完整流程,从内存设置到字体打包,提供全面的避坑指南。涵盖关键检查清单、内存与性能优化配置、资源处理专项优化及平台适配技巧,帮助开发者高效解决常见问题,确保项目顺利发布。

weixin_28693309的博客 147

使用MDC和AOP及拦截器为日志加跟踪标识

1. 参考文章:https://segmentfault.com/a/1190000022721682,https://blog.youkuaiyun.com/millery22/article/details/86672354 2. 开始使用了TreadLocal和拦截器增加标识,但是发现想中消费mq消息的日志,无法打印唯一标识。文章:https://blog.youkuaiyun.com/whxwkb/article/details/111594712。查询到MDC可以。 3. 拦截器: public class L

whxwkb的博客 992

为什么你的单细胞数据总有问题?可能是忽略了scDblFinder这个神器

本文深入剖析了单细胞数据分析中异质性双细胞这一常见但易被忽视的痛点,并重点介绍了scDblFinder这一神器。scDblFinder通过机器学习模型,能精准识别并过滤同一样本内的异质性双细胞,有效解决其导致的聚类模糊、信号混淆等问题,从而显著提升数据洁净度与下游分析结果的可靠性。

brandy的博客 452

Coze智能体开发:搭建一个解析 PDF 文件的工作流

大模型本身并不具备文件读取和解析的能力,无法直接处理文件字节流。对于支持工具调用(Function calling)的模型,扣子提供插件功能来帮助大模型调用外部工具 API,拓展大模型的能力边界。扣子插件商店提供海量官方插件和第三方插件,例如链接读取插件可以将链接对应的文件内容解析为纯文本传递给大模型。

王国平的博客 2812

使用 MDC 实现日志链路跟踪,包教包会!

使用 MDC 实现日志链路跟踪,包教包会!

2144

一文讲透 SAP SD:把报价、订单、交付、开票串成一条可落地的业务闭环

摘要:SAP SD(销售与分销)模块是企业O2C(订单到收款)流程的核心,管理从客户意向到回款的完整链路。该模块围绕销售订单、交付、开票等关键环节,与财务、物料管理等模块深度集成。SD的核心在于主数据管理、单据驱动流程、定价规则引擎和信用风控,通过条件技术实现复杂定价策略,并支持多维度分析。典型应用场景包括多渠道销售企业的价格体系管理、信用控制和物流协同,有效解决销售与交付脱节、财务对账困难等业务痛点。(149字)

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 511

java 线程跟踪_java多线程使用mdc追踪日志

背景多线程情况下,子线程的sl4j打印日志缺少traceId等信息,导致定位问题不方便解决方案打印日志时添加用户ID、trackId等信息,缺点是每个日志都要手动添加使用mdc直接拷贝父线程值实现// 新建线程时:Map mdcContextMap = MDC.getCopyOfContextMap()// 子线程运行时:if(null != mdcContextMap){MDC.setConte...

weixin_33060757的博客 1494

线结构光相机原理详解以及精度控制总结

摘要:线阵结构光相机是工业三维检测的核心设备,结合线阵相机的高分辨率与结构光的主动三维成像能力。其工作原理基于线激光投射、逐线扫描、亚像素级条纹提取及三角测量法实现三维重建。精度受硬件、校准、环境和算法四因素影响,需通过优化硬件选型(如小像元相机、窄线宽激光)、精准校准(相机内参、激光平面)、环境控制(遮光、隔振、恒温)及算法优化(Steger算法、点云后处理)来提升。典型应用(卷材、轨道检测)中深度精度可达5-20μm,需根据场景需求平衡参数配置。

start_up_go的博客 937

15、人工智能社会学:关系视角下的机器相遇

本文探讨了将人工智能视为社会主体的可能性,提出了一种关系视角下的人工智能社会学。通过结合卢西亚娜·帕里西、艾伦·图灵和乔治·赫伯特·米德的理论,强调不确定性在人工智能运行中的核心地位,并分析了人工智能如何通过与社会环境的互动催生新奇性,从而拓展社会学的研究范畴。文章还讨论了人工智能对社会结构和社会互动的影响,以及其作为社会主体所带来的理论和实践意义,为未来人工智能社会学的发展提供了方向。

pea55的博客 72

Slf4j MDC使用transmittable-thread-local解决多线程日志跟踪

使用Slf4j的MDC方案可以在日志中打印TraceID来实现链路追踪,可以很好的帮助定位线上问题,但是传统的MDC方案不能处理线程池或者其他的异步场景,为此研究下其他的解决方案。

xiaolong7713的博客 4745

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

从零搭建开发脚手架 使用MDC实现日志链路跟踪

文章目录前言原理实现过滤器logback.xml返回体效果日志 前言 在微服务环境中,我们经常使用Skywalking、CAT等去实现整体请求链路的追踪,但是这个整体运维成本高,架构复杂,我们来使用MDC通过Log来实现一个轻量级的会话事务跟踪功能。 原理 MDC org.sl4j.MDC其实内部就是ThreadLocal,MDC提供了put/get/clear等几个核心接口,用于操作ThreadLocal中的数据;ThreadLocal中的K-V,可以在logback.xml中声明,最终将会打印在日志中。

laker的博客 967

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

Spring Boot 中使用 MDC 追踪一次请求全过程

MDC 的全称是 Mapped Diagnostic Context,映射诊断上下文(MDC)。本质上是由日志框架维护的映射,其中应用程序代码提供键-值对,然后日志框架可以在日志消息中插入这些键-值对。MDC数据在过滤消息或触发某些操作方面也非常有用。管理开销。 SLF4J支持MDC,即映射的诊断上下文。如果底层日志框架提供MDC功能,那么SLF4J将委托给底层框架的MDC。注意,此时,只有log4j和logback提供MDC功能。如果底层框架不提供MDC,例如java.util。日志记录后,SLF4J仍将

carl.zhao的专栏 1万+

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

springboot 单机应用使用MDC生成唯一日志id

MDC是一种日志记录技术,它允许我们将上下文信息存储在线程局部变量中,并在整个线程执行期间共享这些信息。这些上下文信息可以是任何东西,例如请求ID、用户ID、会话ID等等。MDC允许我们在跨多个线程和组件的日志记录中,轻松地将所有相关日志记录关联起来。MDC(Mapped Diagnostic Context,映射调试上下文)是 log4j 、logback及log4j2 提供的一种方便在多线程条件下记录日志的功能。MDC 可以看成是一个与当前线程绑定的哈希表,可以往其中添加键值对。

你就像甜甜的益达 2717

logback mdc日志跟踪

1、简介 MDC(Mapped Diagnostic Context,映射调试上下文)是 log4j 、logback及log4j2 提供的一种方便在多线程条件下记录日志的功能。MDC 可以看成是一个与当前线程绑定的哈希表,可以往其中添加键值对。MDC 中包含的内容可以被同一线程中执行的代码所访问。 当前线程的子线程会继承其父线程中的 MDC 的内容。当需要记录日志时,只需要从 MDC 中获取所需的信息即可。MDC 的内容则由程序在适当的时候保存进去。对于一个 Web 应...

幽静净土的技术分享 1813

MDC日志追踪(一)介绍

追踪日志。

w_t_y_y的博客 743

在Java项目中使用traceId跟踪请求全流程日志

最近在项目开发中遇到了一些问题,项目为多机部署,使用kibana收集日志,但并发大时使用日志定位比较麻烦,大量日志输出导致很难筛出指定请求的全部相关日志,以及下游服务调用对应的日志。因此计划对项目日志打印进行一些小改造,使用一个traceId跟踪请求的全部路径,前提是不修改原有的打印方式。 简单的解决思路 想要跟踪请求,第一个想到的就是当请求来时生成一个traceId放在ThreadLoc...

Stay Foolish 8万+

MDC 日志跟踪

在 preHandle() 方法中,生成一个唯一的事务 ID,将其添加到 MDC 中,并使用 ThreadLocal 存储到当前线程的局部变量中,以便在后续的代码中获取该事务 ID。在这个示例代码中,使用 MDC.put() 方法将上下文变量设置为一个唯一的事务 ID,然后在日志中输出该事务 ID,以便在日志中跟踪该事务的执行流程和状态。在这个 logback 配置文件中,与之前的示例代码相比,只是将占位符中的 “userId” 改为了 “transactionId”,以对应 MDC 中的事务 ID。

tanwu1的博客 525
上一篇: kafka搭建kraft集群模式
下一篇: Spring Cloud Sleuth 全链路日志跟踪
livel_java
博客等级 码龄9年 2916粉丝 29原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值