SpringBoot + MDC 实现全链路调用日志跟踪

 

往期热门文章:

1、去TM的领导:发烧请病假,不意味着在家睡大觉
2、5年半老程序员被System.out.println() 考懵逼了...
3、妙用Java 8中的 Function接口,消灭if...else(非常新颖的写法)
4、Controller中的请求方法,private和public有什么区别?
5、再见Jenkins!一款更适合国人的自动化部署工具,贼带劲!!

写在前面

通过本文将了解到什么是 MDC、MDC 应用中存在的问题、如何解决存在的问题。

MDC 介绍


简介

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

API 说明
  • clear() => 移除所有 MDC

  • get (String key) => 获取当前线程 MDC 中指定 key 的值

  • getContext() => 获取当前线程 MDC 的 MDC

  • put(String key, Object o) => 往当前线程的 MDC 中存入指定的键值对

  • remove(String key) => 删除当前线程 MDC 中指定的键值对


优点

代码简洁,日志风格统一,不需要在 log 打印中手动拼写 traceId,即

LOGGER.info("traceId:{} ", traceId)

暂时只能想到这一点。

MDC 使用

添加拦截器

public class LogInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//如果有上层调用就用上层的IDString traceId = request.getHeader(Constants.TRACE_ID);if (traceId == null) {traceId = TraceIdUtil.getTraceId();}MDC.put(Constants.TRACE_ID, traceId);return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)throws Exception {}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception {//调用结束后删除MDC.remove(Constants.TRACE_ID);}
}

修改日志格式

 
<property name="pattern">[TRACEID:%X{traceId}] %d{HH:mm:ss.SSS} %-5level %class{-1}.%M()/%L - %msg%xEx%n</property>

重点是 %X{traceId},traceId 和 MDC 中的键名称一致。


简单使用就这么容易,但是在有些情况下 traceId 将获取不到。

a62b97732329d0e968871e35cc479812.jpeg

MDC 存在的问题

  • 子线程中打印日志丢失 traceId

  • HTTP 调用丢失 traceId

......丢失traceId的情况,来一个再解决一个,绝不提前优化


解决 MDC 存在的问题

子线程日志打印丢失 traceId

子线程在打印日志的过程中 traceId 将丢失,解决方式为重写线程池。对于直接 new 创建线程的情况不考略,实际应用中应该避免这种用法。重写线程池无非是对任务进行一次封装。

线程池封装类:ThreadPoolExecutorMdcWrapper.java

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);}@Overridepublic void execute(Runnable task) {super.execute(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));}@Overridepublic <T> Future<T> submit(Runnable task, T result) {return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()), result);}@Overridepublic <T> Future<T> submit(Callable<T> task) {return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));}@Overridepublic Future<?> submit(Runnable task) {return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));}
}

说明:

    • 继承 ThreadPoolExecutor 类,重新执行任务的方法;

    • 通过 ThreadMdcUtil 对任务进行一次包装

线程 traceId 封装工具类:ThreadMdcUtil.java

public class ThreadMdcUtil {public static void setTraceIdIfAbsent() {if (MDC.get(Constants.TRACE_ID) == null) {MDC.put(Constants.TRACE_ID, TraceIdUtil.getTraceId());}}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 callable.call();} finally {MDC.clear();}};}public static Runnable wrap(final Runnable runnable, final Map<String, String> context) {return () -> {if (context == null) {MDC.clear();} else {MDC.setContextMap(context);}setTraceIdIfAbsent();try {runnable.run();} finally {MDC.clear();}};}
}

说明(以封装Runnable为例):

  • 判断当前线程对应 MDC 的 Map 是否存在,存在则设置;

  • 设置 MDC 中的 traceId 值,不存在则新生成,针对不是子线程的情况,如果是子线程,MDC 中 traceId 不为 null;

  • 执行 run 方法。

代码等同于以下写法,会更直观。

public static Runnable wrap(final Runnable runnable, final Map<String, String> context) {return new Runnable() {@Overridepublic void run() {if (context == null) {MDC.clear();} else {MDC.setContextMap(context);}setTraceIdIfAbsent();try {runnable.run();} finally {MDC.clear();}}};
}

重新返回的是包装后的 Runnable,在该任务执行之前 runnable.run() 先将主线程的 Map 设置到当前线程中(即 MDC.setContextMap(context)),这样子线程和主线程 MDC 对应的 Map 就是一样的了。

    • 判断当前线程对应 MDC 的 Map 是否存在,存在则设置;

    • 设置 MDC 中的 traceId 值,不存在则新生成。针对不是子线程的情况,如果是子线程,MDC 中 traceId 不为 null;

    • 执行 run 方法。


HTTP 调用丢失 traceId

在使用 HTTP 调用第三方服务接口时 traceId 将丢失,需要对 HTTP 调用工具进行改造。发送时,在 request header 中添加 traceId,在下层被调用方添加拦截器获取 header 中的 traceId 添加到 MDC 中。

HTTP 调用有多种方式,比较常见的有 HttpClient、OKHttp、RestTemplate,所以只给出这几种 HTTP 调用的解决方式。

HttpClient

实现 HttpClient 拦截器

 
public class HttpClientTraceIdInterceptor implements HttpRequestInterceptor {@Overridepublic void process(HttpRequest httpRequest, HttpContext httpContext) throws HttpException, IOException {String traceId = MDC.get(Constants.TRACE_ID);//当前线程调用中有traceId,则将该traceId进行透传if (traceId != null) {//添加请求体httpRequest.addHeader(Constants.TRACE_ID, traceId);}}
}

实现 HttpRequestInterceptor 接口并重写 process 方法。

如果调用线程中含有 traceId,则需要将获取到的 traceId 通过 request 中的 header 向下透传下去。

为 HttpClient 添加拦截器

private static CloseableHttpClient httpClient = HttpClientBuilder.create().addInterceptorFirst(new HttpClientTraceIdInterceptor()).build();

通过 addInterceptorFirst 方法为 HttpClient 添加拦截器。

OKHttp

实现 OKHttp 拦截器

public class OkHttpTraceIdInterceptor implements Interceptor {@Overridepublic Response intercept(Chain chain) throws IOException {String traceId = MDC.get(Constants.TRACE_ID);Request request = null;if (traceId != null) {//添加请求体request = chain.request().newBuilder().addHeader(Constants.TRACE_ID, traceId).build();}Response originResponse = chain.proceed(request);return originResponse;}
}

实现 Interceptor 拦截器,重写 interceptor 方法。实现逻辑和 HttpClient 差不多,如果能够获取到当前线程的 traceId 则向下透传。

为 OkHttp 添加拦截器

private static OkHttpClient client = new OkHttpClient.Builder().addNetworkInterceptor(new OkHttpTraceIdInterceptor()).build();

调用 addNetworkInterceptor 方法添加拦截器。

RestTemplate

实现 RestTemplate 拦截器

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

实现 ClientHttpRequestInterceptor 接口,并重写 intercept 方法,其余逻辑都是一样的,这里不做重复说明。

为 RestTemplate 添加拦截器

restTemplate.setInterceptors(Arrays.asList(new RestTemplateTraceIdInterceptor()));

调用 setInterceptors 方法添加拦截器。

第三方服务拦截器

HTTP 调用第三方服务接口全流程 traceId 需要第三方服务配合,第三方服务需要添加拦截器拿到 request header 中的 traceId 并添加到 MDC 中。

public class LogInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//如果有上层调用就用上层的IDString traceId = request.getHeader(Constants.TRACE_ID);if (traceId == null) {traceId = TraceIdUtils.getTraceId();}MDC.put("traceId", traceId);return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)throws Exception {}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception {MDC.remove(Constants.TRACE_ID);}
}

说明:

  • 先从 request header 中获取t raceId;

  • 从 request header 中获取不到 traceId 则说明不是第三方调用,直接生成一个新的 traceId;

  • 将生成的 traceId 存入 MDC 中。

除了需要添加拦截器之外,还需要在日志格式中添加 traceId 的打印,如下:

<property name="pattern">[TRACEID:%X{traceId}] %d{HH:mm:ss.SSS} %-5level %class{-1}.%M()/%L - %msg%xEx%n</property>

需要添加 %X{traceId}。

https://github.com/TiantianUpup/springboot-log/tree/master/springboot-trace

转自:何甜甜在吗,

链接:juejin.cn/post/6844904101483020295

往期热门文章:
1、IntelliJ IDEA终于支持对Redis 的可视化窗口操作了,真香!
2、ChatGPT能接入微信了!
3、Java 反射慢?它到底慢在哪?
4、当我去了不到 20 人的 IT 公司后。。。
5、GitHub 被超火的 ChatGPT 霸榜!
6、Java使用 try catch会影响性能?
7、Java使用 try catch会影响性能?
8、原来count(*)是接口性能差的真凶!
9、大公司病了,这也太形象了吧!!!
10、全球最大资源站创始人被抓,但网站还会继续活下去

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/63028.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

chatgpt赋能python:Python安装Kivy:强大的跨平台应用程序框架

Python安装Kivy: 强大的跨平台应用程序框架 Kivy是一个基于Python的跨平台开源应用程序框架&#xff0c;它可以用于开发多点触摸应用程序&#xff0c;使开发者能够轻松地创建适用于Windows&#xff0c;Mac&#xff0c;Linux&#xff0c;Android和iOS等平台的应用程序。Python作…

chatgpt赋能python:Python与界面绘制

Python与界面绘制 在现代计算机应用程序中&#xff0c;良好的用户界面设计和交互是至关重要的。它可以建立用户对应用程序的信任和舒适感&#xff0c;从而使应用程序更加实用和易于使用。而Python作为一种非常流行的编程语言&#xff0c;也提供了一些强大的工具和库&#xff0…

chatgpt赋能python:Python手机运行:轻巧便捷的编程语言

Python 手机运行&#xff1a;轻巧便捷的编程语言 Python作为一门高效&#xff0c;简单&#xff0c;易学的编程语言&#xff0c;逐渐成为程序员和数据科学家们的首选语言。无论是数据处理&#xff0c;机器学习还是人工智能&#xff0c;Python都具有出色的表现。不仅如此&#x…

chatgpt赋能python:Python调用ADBShell命令:使你的Android开发更高效

Python调用ADB Shell命令&#xff1a;使你的Android开发更高效 如果你是一名Android开发者&#xff0c;你一定会知道ADB&#xff08;Android Debug Bridge&#xff09;&#xff0c;它是一个可调试Android设备的多用途命令行工具。在许多情况下&#xff0c;ADB是许多Android开发…

Android 百度图像识别(详细步骤+源码)

百度图像识别 运行效果图一、创建平台应用二、创建Android项目三、网络访问框架四、添加请求API接口五、获取鉴权认证Token六、网络图片Url识别七、相册图片识别八、拍照图片识别九、源码 运行效果图 如果你对这个效果图不满意就不用往下看了&#xff0c;那样只会浪费你的时间。…

微信小程序,图像识别源码

目录 前言百度端配置信息小程序中代码&#xff1a;结语智能识图小程序源码下载路径&#xff1a;https://pan.baidu.com/s/1OGE7vhogS7L7nn0JIFPVWw 提取码&#xff1a;8ze9 前言 基于近期的工作内容关系&#xff0c;在查询一些资料的同时&#xff0c;在微信小程序端集成了图像…

图像识别小程序(含源码)【推荐】

目录 前言百度端配置信息小程序中代码&#xff1a;结语智能识图小程序源码下载路径&#xff1a;https://pan.baidu.com/s/1OGE7vhogS7L7nn0JIFPVWw 提取码&#xff1a;8ze9 前言 基于近期的工作内容关系&#xff0c;在查询一些资料的同时&#xff0c;在微信小程序端集成了图像…

(数学实验)Matlab实现猜数小游戏(增加了错误输入的判断)

刚开始做的时候不知道matlab没有自减运算&#xff0c;在网上查了很久资料&#xff0c;都没发现有对猜数游戏加错误输入判断的&#xff0c;经过多次试错&#xff0c;我弄出来了有判断的程序&#xff0c;在这里分享一下。 文章目录 前言一、问题描述二、解题思路 1.for循环2.whil…

使用Python为二年级的学生批量生成数学题

文章目录 一.使用Python为二年级的学生批量生成数学题1.1 背景 二.解决思路及其代码三.排版及其打印四.本文源码 一.使用Python为二年级的学生批量生成数学题 1.1 背景 我妹妹今年上二年级&#xff0c;她的老师今天给他们布置了一项作业&#xff1a; 从今天起到开学&#xff…

Fdog系列(一):思来想去,不如写一个聊天软件,那就从仿QQ注册页面开始吧。

文章目录 一.前言1. 基础布局2. 自动切换图片3. 添加内容4. 自动缩放&#xff0c;控件的显示和隐藏5.响应用户输入操作 所有文章源码已整体打包上传至github&#xff0c;求星星&#xff01; 一.前言 两年的大学生活马上就要结束了&#xff0c;马上面临实习&#xff0c;突然心…

基于Python的网络拓扑图绘制

最近写论文画了许多图&#xff0c;在这里记录一些。当然&#xff0c;如果仅仅是展示性图片的话也可以使用visio&#xff0c;但是这里我仍然想探究一下如何使用pyhon画出美观的网络拓扑图。 一、画出网络拓扑图 给出邻接矩阵&#xff0c;画出网络的拓扑图&#xff1a; import…

认识网络、几种常用的网络拓扑图

交换协议&#xff1a; VLAN技术&#xff1a;虚拟局域网 STP技术&#xff1a;生成树协议 VRRP技术&#xff1a;虚拟路由冗余协议 VPN&#xff1a;虚拟专用网络 名词解释 路由协议&#xff1a;http、HTTPS、tcp、ip 静态路由配置 OSPF协议 RIP协议 ACL访问控制 什么是网络&…

快速读懂网络拓扑图

快速读懂网络拓扑图 几重常见的网络拓扑总线型拓扑简介优点缺点 环型拓扑简介优点缺点 星型拓扑简介优点缺点 网络层级机构节点结点链路通路 不同的连接线代表什么意思&#xff1f;不同颜色、粗细的直线代表什么意思&#xff1f;闪电线-串行链路 几重常见的网络拓扑 总线型拓扑…

盘点5款常用的网络拓扑图制作工具

网络拓扑能直观明了的展示网络中各网元之间的关系&#xff0c;极大方便运维人员对网络进行优化配置、故障排查等操作。那么这个专业性比较强的拓扑图&#xff0c;要用什么工具把它轻松&#xff0c;简便的画出来呢&#xff1f;现对市场5款主流的拓扑制作软件进行介绍&#xff1a…

网络拓扑图怎么画 详细教程

大数据时代&#xff0c;如何更好地去运营、呈现数据&#xff0c;并从其中发掘出更多信息成为了人们探索的方向。网络拓扑图就是一种非常有用的信息化图表&#xff0c;这种网状关系呈现出来的利器可以使我们把想要传递的信息更加清晰、有逻辑的呈现在别人的眼前。 1. 什么是网络…

网络拓扑图

转载自&#xff1a;https://blog.csdn.net/weixin_40792878/article/details/82555594 什么是拓扑结构?   首先我们来解释一下拓扑的含义&#xff0c;所谓“拓扑”就是把实体抽象成与其大小、形状无关的“点”&#xff0c;而把连接实体的线路抽象成“线”&#xff0c;进而…

【新手】网络拓扑图要这样画

网络拓扑设计分为单核心和双核心两种 1. 单核心网络拓扑设计&#xff08;如图&#xff09; &#xff08;上图写的是路由器连接外网&#xff0c;也可以连接公司别的分支机构&#xff0c;比如现在是上海分公司&#xff0c;也可以用路由器和北京分公司相连&#xff0c;当然要借助…

关于网络拓扑图,你想知道的都在这

这篇文章&#xff0c;我将集中回答以下这四个问题&#xff1a; 网络拓扑图的定义网络拓扑图的分类网络拓扑图的设计规范网络拓扑图的绘制步骤 一、网络拓扑图的定义 在认识网络拓扑图前&#xff0c;我们先来了解下网络拓扑结构。 所谓网络拓扑结构&#xff0c;是指用传输媒…

作为程序员, 我如何使用ChatGPT来帮我写代码

从快速学习到调试程序&#xff0c; 甚至将繁琐的工作自动化。 我们每个人都渴望成功&#xff0c; 而获得成功的最有效的方式之一就是&#xff0c; 在尽可能短的时间内&#xff0c; 解决尽可能多的人的问题&#xff0c; 特别是大家都有的问题。 我们可以观察身边优秀的产品&…

只知道ChatGPT?这些AI工具同样值得收藏

B站|公众号&#xff1a;啥都会一点的研究生 人工智能革命带来了许多能够提高生产力和转变工作方式的工具&#xff0c;本期将重点介绍音频、视频、设计以及图像和数据清理中的顶级 AI 工具。 音视频类AI工具&#xff1a; VoicePen AI https://voicepen.ai&#xff1a;该工具可…