MDC使用手册精讲

MDC

背景: 线上排查问题时,请求在多个微服务之间进行调用,并发量较大的情况下,想跟踪某一个请求的链路,是需要花费一些时间才能梳理出来,而且还依赖于你的业务字段。而我们需要的是快速定位,快速纠错,因此就需要一个唯一的全局标识用来识别并跟踪这个请求。

想法: 一个唯一全局标识,并且还能在上下游之间进行传递,那可以对这个请求 request 做些特殊操作,我们可以在请求头上添加这个全局标识,比如使用UUID,在进入每一个微服务之前进行拦截 (filter/intercept),获取这个UUID并设置到当前线程中。输出日志时,就把这个UUID也输出到日志中,这样便可以快速识别并定位。如果在业务代码中使用了多线程,也应该记录,这就需要使用到本次介绍的重点内容:MDC

介绍: MDC(Mapped Diagnostic Context,映射调试上下文)是 log4j 和 logback 提供的一种方便在多线程条件下记录日志的功能。MDC 中包含的内容可以被同一线程中执行的代码所访问,当前线程的子线程也会继承其父线程中的 MDC 的内容。

我们日志用的是 slf4j + logback,看看 slf4j-api 包结构,发现了一个 spi ,是不是很熟悉,虽然这个 spi 只是一个包名,但是实质是一样的

在这里插入图片描述

slf4j-api 包下有一个 MDCAdapter接口,如下

public interface MDCAdapter {//通过k-v设置  public void put(String key, String val);public String get(String key);public void remove(String key);public void clear();public Map getCopyOfContextMap();public void setContextMap(Map contextMap);
}

由于使用的是logback日志,对应的logback也提供了MDCAdapter接口的实现,如下

在这里插入图片描述

我们看看logback里面是如何实现 MDCAdapter 接口的。发现是基于InheritableThreadLocal 来实现的,这个就很好的处理的父子线程的问题,也就是可以trace线程池

public final class LogbackMDCAdapter implements MDCAdapter {final InheritableThreadLocal<Map<String, String>> copyOnInheritThreadLocal = new InheritableThreadLocal<Map<String, String>>();private static final int WRITE_OPERATION = 1;private static final int READ_OPERATION = 2;// keeps track of the last operation performedfinal ThreadLocal<Integer> lastOperation = new ThreadLocal<Integer>();private Integer getAndSetLastOperation(int op) {Integer lastOp = lastOperation.get();lastOperation.set(op);return lastOp;}private boolean wasLastOpReadOrNull(Integer lastOp) {return lastOp == null || lastOp.intValue() == READ_OPERATION;}private Map<String, String> duplicateAndInsertNewMap(Map<String, String> oldMap) {Map<String, String> newMap = Collections.synchronizedMap(new HashMap<String, String>());if (oldMap != null) {// we don't want the parent thread modifying oldMap while we are// iterating over itsynchronized (oldMap) {newMap.putAll(oldMap);}}copyOnInheritThreadLocal.set(newMap);return newMap;}public void put(String key, String val) throws IllegalArgumentException {if (key == null) {throw new IllegalArgumentException("key cannot be null");}Map<String, String> oldMap = copyOnInheritThreadLocal.get();Integer lastOp = getAndSetLastOperation(WRITE_OPERATION);if (wasLastOpReadOrNull(lastOp) || oldMap == null) {Map<String, String> newMap = duplicateAndInsertNewMap(oldMap);newMap.put(key, val);} else {oldMap.put(key, val);}}public void remove(String key) {if (key == null) {return;}Map<String, String> oldMap = copyOnInheritThreadLocal.get();if (oldMap == null) return;Integer lastOp = getAndSetLastOperation(WRITE_OPERATION);if (wasLastOpReadOrNull(lastOp)) {Map<String, String> newMap = duplicateAndInsertNewMap(oldMap);newMap.remove(key);} else {oldMap.remove(key);}}public void clear() {lastOperation.set(WRITE_OPERATION);copyOnInheritThreadLocal.remove();}public String get(String key) {Map<String, String> map = getPropertyMap();if ((map != null) && (key != null)) {return map.get(key);} else {return null;}}public Map<String, String> getPropertyMap() {lastOperation.set(READ_OPERATION);return copyOnInheritThreadLocal.get();}public Set<String> getKeys() {Map<String, String> map = getPropertyMap();if (map != null) {return map.keySet();} else {return null;}}public Map getCopyOfContextMap() {lastOperation.set(READ_OPERATION);Map<String, String> hashMap = copyOnInheritThreadLocal.get();if (hashMap == null) {return null;} else {return new HashMap<String, String>(hashMap);}}@SuppressWarnings("unchecked")public void setContextMap(Map contextMap) {lastOperation.set(WRITE_OPERATION);Map<String, String> newMap = Collections.synchronizedMap(new HashMap<String, String>());newMap.putAll(contextMap);//赋值给threadlocalcopyOnInheritThreadLocal.set(newMap);}
}

那么日志的输出格式是怎么样的呢?

MDC使用 %X{key} 来定义日志格式,会在此处打印MDC设置的key的value,如果所定义的key不存在,那么将不会打印,会留一个占位符

<configuration><appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"><encoder><pattern>%date{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{requestId}] %logger{36} - %msg%n</pattern></encoder></appender><root level="DEBUG"><appender-ref ref="STDOUT" /></root>
</configuration>

实战测试

public static void main(String[] args) throws InterruptedException {MDC.put("requestId","这是我的全局唯一标识UUID");log.error("===========我是父线程main=============");new Thread(()-> {log.error("===========我是子线程child================");}).start();TimeUnit.SECONDS.sleep(3);
}
2024-04-16 20:28:42.498 [main] ERROR [这是我的全局唯一标识UUID] com.example.ssm.SsmApplication - ===========我是父线程main=============
2024-04-16 20:28:42.499 [Thread-1] ERROR [这是我的全局唯一标识UUID] com.example.ssm.SsmApplication - ===========我是子线程child================

现在我们已经可以在多个服务之间实现问题精准定位,但是还是需要在各个服务器上去取日志,因此我们可以进行日志的统一收集,比如常见的ELK( Elasticsearch+Logstash +Kibana),LPG( Loki+Promtail+Grafana)方案。

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

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

相关文章

Qt 实战(2)搭建开发环境 | 2.1、Windows下安装QT

一、Windows下安装QT 1、QT官网 QT官网&#xff1a;https://download.qt.io/&#xff0c;打开官网地址&#xff0c;如下&#xff1a; 目录结构介绍 目录说明snapshots预览版&#xff0c;最新的开发测试中的 Qt 库和开发工具onlineQt 在线安装源official_releases正式发布版&am…

python将日志写入文件(超详细)

python将日志写入文件(超详细) 1、需求 我们在训练模型的时候&#xff0c;有时候需要将训练日志输出到一个文件中&#xff0c;方便随时查看训练日志。 训练模型时候&#xff0c;训练日志在控制台展示&#xff0c;关闭控制台后日志会消失。这时&#xff0c;我们需要将控制台中…

STM32之HAL开发——CubeMX配置串行Flash文件系统

配置流程 在开始配置FATFS前&#xff0c;需要提前配置好RCC的时钟&#xff0c;以及时钟的频率&#xff0c;另外还要配置好Debug选项&#xff08;选择串行&#xff09; 选项介绍 文件系统适用于SD卡&#xff0c;Disk磁盘等&#xff0c;需要我们将对应的驱动打开才可以使用。 …

STM32H7 QSPI的寄存器和功能介绍

目录 概述 1 认识QSPI 1.1 QSPI介绍 1.2 QUADSPI 主要特性 2 QUADSPI 功能说明 2.1 双闪存模式禁止 2.2 双闪存模式使能 3 QUADSPI 的用法 3.1 间接模式的操作步骤 3.1.1 QUADSPI 间接模式时配置帧模式 3.1.2 写控制寄存器 (QUADSPI_CR) 3.1.3 写通信配置寄存器 (…

西北新作 | 咸阳金域华庭售楼处落成

优积科技再拓新品,在历史悠久的陕西省咸阳市核心区域&#xff0c;成功打造了一座创新模块化设计的售楼处。 咸阳&#xff0c;这座镶嵌于八百里秦川温润腹地的城市&#xff0c;坐拥渭水潺潺南流&#xff0c;峻嶒嵯峨的嵕山巍然北峙&#xff0c;因其南北皆得山水之阳刚之美&…

MATLAB绘制圆锥曲线:抛物线,双曲线,椭圆

MATLAB绘制圆锥曲线:抛物线,双曲线,椭圆 clc;close all;clear all;warning off;%清除变量x linspace(-10, 10, 1000); % 创建一个x值的向量&#xff0c;范围从-10到10&#xff0c;共1000个点 y x.^2; % 计算每个x值对应的y值% 使用plot函数绘制图形 figure; % 创建一个新的图…

springboot2集成东方通tongweb嵌入式版

由于最近项目需要国产化信创改造&#xff0c;引入东方通tongweb 联系东方通厂家 &#xff0c;将依赖导入到maven仓库&#xff0c;并获取嵌入式版license文件修改pom.xml&#xff0c;引入依赖&#xff0c;注意springboot版本&#xff0c;这里以springboot2举例 首先移除springb…

SpringBoot+Vue多模块项目宝塔部署(保姆级教程)

目录 服务器推荐 安装宝塔 进入宝塔 安装软件 安装 nginx ​编辑 安装mysql 安装java 配置数据库 启动模块下加打包插件 修改配置文件 添加java项目 放行端口 前端访问 本篇博文将向各位详细的介绍项目部署到服务器的详细过程&#xff0c;以及我配置过程中遇到的…

向量数据库与图数据库:理解它们的区别

作者&#xff1a;Elastic Platform Team 大数据管理不仅仅是尽可能存储更多的数据。它关乎能够识别有意义的见解、发现隐藏的模式&#xff0c;并做出明智的决策。这种对高级分析的追求一直是数据建模和存储解决方案创新的驱动力&#xff0c;远远超出了传统关系数据库。 这些创…

蓝桥杯第十五届javab组个人总结

javab组 额今天早上打完了得对自己此次比赛做总结&#xff0c;无论是明年还参赛还是研究生蓝桥杯&#xff0c;体验感有点差&#xff0c;第一题其实一开始想手算但怕进位导致不准确还是让代码跑了&#xff0c;但跑第202420242024个数&#xff08;被20和24整除&#xff09;一直把…

Aurora 协议学习理解与应用——Aurora 64B66B协议学习

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 Aurora 协议学习理解与应用——Aurora 64B66B协议学习 概述数据发送和接收帧传输过程链路层帧描绘64B/66B 编码多lane传输 帧接收过程Control Block Stripping 控制块剥离多l…

C语言学习笔记之指针(二)

指针基础知识&#xff1a;C语言学习笔记之指针&#xff08;一&#xff09;-CSDN博客 目录 字符指针 代码分析 指针数组 数组指针 函数指针 代码分析&#xff08;出自《C陷阱和缺陷》&#xff09; 函数指针数组 指向函数指针数组的指针 回调函数 qsort() 字符指针 一…