当前位置: 首页 > news >正文

MDC 日志跟踪笔记

一、前言

本文主要解析应用中比较实在的一个log4j的链路应用,在经过一段时间的应用后发现还是很稳的,就记录一下这个MDC 代表Mapped Diagnostic Context(映射式诊断上下文)的情况。

Tisp: 它是一个线程安全的存放诊断日志的容器

Tisp: 它是一个线程安全的存放诊断日志的容器

Tisp: 它是一个线程安全的存放诊断日志的容器

二、接口相关内容

相关slf4j 官方的接口文档: https://www.slf4j.org/api/org/slf4j/MDC.html

1、常用直接调用接口

  • clear() :移除所有MDC

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

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

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

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

2、调试用接口

  • pushByKey(String key, String value) :将指定的键值对推入 MDC 上下文信息的栈

​ 可以在之后 在之后通过 popByKey 恢复原始值

  • popByKey(String key) :从 MDC 上下文信息的栈中弹出指定键的值

​ 如果在没有先前推送的情况下调用 popByKey,则会将键从 MDC 中移除。

3、配置用接口

  • getCopyOfContextMap() : 一个包含当前线程的 MDC 上下文信息的 不可修改的 映射副本

  • getMDCAdapter() :获取当前线程的 MDC 适配器,可进行 MDC 上下文信息的设置和清除

  • setContextMap(Map<String,String> contextMap) : 设置整个 MDC 上下文信息的映射

三、应用配置

下属内容中

TraceUtils 仅为生成traceId

TraceConstant 对应枚举类

logging:pattern:console: "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %X{traceId} - %msg%n"
logging.pattern.console=%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %X{traceId} - %msg%n
<?xml version="1.0" encoding="UTF-8"?>
<configuration><property name="logDirectory" value="${LOG_DIRECTORY:-logs}"/><appender name="Console" class="ch.qos.logback.core.ConsoleAppender"><layout><Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %X{traceId} - %msg%n</Pattern></layout></appender><appender name="File" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>${logDirectory}/app.log</file><!-- 其他 RollingFileAppender 配置省略 --></appender><root level="info"><appender-ref ref="Console"/><appender-ref ref="File"/></root></configuration>

1、异步任务线程配置

针对独立线程修饰

TaskDecorator 接口允许在任务执行之前和之后对执行线程进行修改或装饰

public class MdcTaskDecorator implements TaskDecorator {@Overridepublic Runnable decorate(Runnable runnable) {// 获取当前线程的 MDC 上下文信息//final var contextMap = MDC.getCopyOfContextMap();Map<String, String> contextMap = MDC.getCopyOfContextMap();return () -> {try {// 设置新的执行线程的 MDC 上下文信息if (contextMap != null) {MDC.setContextMap(contextMap);}// 执行任务runnable.run();} finally {// 清除 MDC 上下文信息MDC.clear();}};}
}

针对线程工厂修饰

利用 ThreadFactory 创建线程针对内容修饰MDC

public static class MdcThreadFactory implements ThreadFactory {@Overridepublic Thread newThread(Runnable r) {// 在创建线程时设置 MDC 上下文信息return new Thread(() -> {// 获取当前线程的 MDC 上下文信息final Map<String, String> contextMap = MDC.getCopyOfContextMap();try {// 在新线程中设置 MDC 上下文信息if (contextMap != null) {MDC.setContextMap(contextMap);}// 执行任务r.run();} finally {// 清除 MDC 上下文信息MDC.clear();}});}
}

1.1、设置注解线程池的配置

1、利用配置异步方法的执行器 AsyncConfigurer 来配置注解线程池

2、 setTaskDecoratorsetThreadFactory 会以最后一个配置的为准

@Component
public class TraceAsyncConfigurer implements AsyncConfigurer {@Overridepublic Executor getAsyncExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(8);executor.setMaxPoolSize(16);executor.setQueueCapacity(100);executor.setThreadNamePrefix("async-pool-");//①用来直接修改装饰器executor.setTaskDecorator(new MdcTaskDecorator());//② 也可以直接用ThreadFactoryexecutor.setThreadFactory(new MdcThreadFactory());executor.setWaitForTasksToCompleteOnShutdown(true);executor.initialize();return executor;}}

1.2、设置直接使用的线程配置

在工厂类直接配置MDC即可

ThreadPoolExecutor executor = new ThreadPoolExecutor(8, 16, 15000, TimeUnit.MILLISECONDS, new SynchronousQueue<Runnable>(),new MdcThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

2、针对web上下文配置

利用 HandlerInterceptor 直接拦截

public class TraceHandlerInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String traceId = request.getHeader(TraceConstant.TRACE_ID);if (!StringUtils.hasText(traceId)) {traceId = TraceUtils.traceIdGenerator();}MDC.put(TraceConstant.TRACE_ID, traceId);return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {MDC.remove(TraceConstant.TRACE_ID);HandlerInterceptor.super.afterCompletion(request, response, handler, ex);}
}

3、Feign客户端的配置

RequestInterceptor 是Feign客户端库中的一个接口,用于拦截Feign客户端发出的HTTP请求,利用这个特性包装

@Component
public class FeignRequestInterceptor implements RequestInterceptor {private final Logger logger = LoggerFactory.getLogger(FeignRequestInterceptor.class);public FeignRequestInterceptor(){logger.info("Initializing feign trace interceptor");}@Overridepublic void apply(RequestTemplate requestTemplate) {requestTemplate.header(TraceConstant.TRACE_ID, TraceContextUtils.getTraceId());}
}

4、利用javaagent的思路配置

这里可以看做是相对无侵入式的一种模式把,偷懒是要慢慢来

其实这里存在 静态注入动态注入 ,但是走静态把

动态注入的方式: 实际上需要改造一下代码,使得可以后续动态java-agent内容。

**静态注入的方式:**只需要变更jvm,增加 -javaagent: /路径 即可

4.1、基础环境内容

1、全限定类名: org.examlpe.MDCAgent

2、 测试端点包名 :org.example.controller

3、 打包后路径地址 :/opt/java-agent-1.0-SNAPSHOT-jar-with-dependencies.jar

<!-- https://mvnrepository.com/artifact/org.javassist/javassist当然这里需要引入一个依赖方便下面对类的魔改 
-->
<dependency><groupId>org.javassist</groupId><artifactId>javassist</artifactId><version>3.29.1-GA</version>
</dependency>

构建打包插件

  <build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-assembly-plugin</artifactId><version>3.1.1</version><configuration><descriptorRefs><!--  这个为最终输出的名称--><descriptorRef>jar-with-dependencies</descriptorRef></descriptorRefs><archive><!-- 设置manifest配置文件--><manifestEntries><!--Premain-Class: 代表 Agent 静态加载时会调用的类全路径名。--><Premain-Class>org.example.MDCAgent</Premain-Class><!--Agent-Class: 代表 Agent 动态加载时会调用的类全路径名。--><Agent-Class>org.example.MDCAgent</Agent-Class><!--Can-Redefine-Classes: 是否可进行类定义。--><Can-Redefine-Classes>true</Can-Redefine-Classes><!--Can-Retransform-Classes: 是否可进行类转换。--><Can-Retransform-Classes>true</Can-Retransform-Classes></manifestEntries></archive></configuration><executions><execution><!--绑定到package生命周期阶段上--><phase>package</phase><goals><!--绑定到package生命周期阶段上--><goal>single</goal></goals></execution></executions></plugin></plugins></build>

4.2、利用 premain 预加载

利用 premain 的特性提前于主方法前加载 Instrumentation

使得我们可以方便操作类的一些内容(悄悄改)

public class MDCAgent {private static final Logger logger = LoggerFactory.getLogger(MDCAgent.class);/*** @todo 这里实际上我们只需要针对控制层进行补充即可*/private static final String TRANSFORM_PREIFX = "org/example/controller";public static void premain(String args, Instrumentation instrumentation) {System.out.println("premain start!");addTransformer(instrumentation);System.out.println("premain end!");}private static void addTransformer(Instrumentation instrumentation) {....   }
}

4.3、利用 Instrumentation 对类加载前魔改

1、 Modifier.isNative(method.getModifiers()) 这部分是搜罗的经验之谈

2、主要目的是针对 测试端点包名 内容进行注入

 instrumentation.addTransformer(new ClassFileTransformer() {public byte[] transform(ClassLoader l, String className, Class<?> c, ProtectionDomain pd, byte[] b) {try {//判断是否为目标包下的类if (className != null && className.startsWith(TRANSFORM_PREIFX)) {// 类属于目标包名下的处理逻辑System.out.println("Class " + className + " belongs to target package.");// 其他的字节码转换逻辑final ClassPool classPool = ClassPool.getDefault();final CtClass clazz = classPool.get(className.replace("/", "."));for (CtMethod method : clazz.getMethods()) {/** Modifier.isNative(methods[i].getModifiers())过滤本地方法,否则会报* javassist.CannotCompileException: no method body  at javassist.CtBehavior.addLocalVariable()*/if (Modifier.isNative(method.getModifiers())) {continue;}String traceId = MDC.get(TraceConstant.TRACE_ID);if (!StringUtils.hasText(traceId)) {traceId = TraceUtils.traceIdGenerator();}MDC.put(TraceConstant.TRACE_ID, traceId);
//							这里正常不需要,仅用于查看效果                            
//                            method.insertBefore("System.out.println(\"" + clazz.getSimpleName() + "."
//                                    + method.getName() + "-" + traceId + " start.\");");
//
//                            method.insertAfter("System.out.println(\"" + clazz.getSimpleName() + "."
//                                    + method.getName() + "-" + MDC.get("haha") + " end.\");", false);}return clazz.toBytecode();}} catch (Exception e) {e.printStackTrace();}return b;}}, true);}

4.4、构建测试

目标vm参数 :-javaagent: /opt/java-agent-1.0-SNAPSHOT-jar-with-dependencies.jar

@GetMapping("ping")
public String ping() throws InterruptedException {return "pong";
}//样例测试结果
//DemoController.ping1-ab83c3c3-08aa-4e9d-9d6f-513967eb28ff start.
//DemoController.ping1-ab83c3c3-08aa-4e9d-9d6f-513967eb28ff end.

相关文章:

MDC 日志跟踪笔记

一、前言 本文主要解析应用中比较实在的一个log4j的链路应用&#xff0c;在经过一段时间的应用后发现还是很稳的&#xff0c;就记录一下这个MDC 代表Mapped Diagnostic Context&#xff08;映射式诊断上下文&#xff09;的情况。 Tisp: 它是一个线程安全的存放诊断日志的容器 T…...

MySQL错误-this is incompatible with sql_mode=only_full_group_by完美解决方案

项目场景 有时候&#xff0c;遇到数据库重复数据&#xff0c;需要将数据进行分组&#xff0c;并取出其中一条来展示&#xff0c;这时就需要用到group by语句。 但是&#xff0c;如果mysql是高版本&#xff0c;当执行group by时&#xff0c;select的字段不属于group by的字段的…...

人工智能|机器学习——基于机器学习的舌苔检测

代码下载&#xff1a; 基于深度学习的舌苔检测毕设留档.zip资源-CSDN文库 1 研究背景 1.1.研究背景与意义 目前随着人们生活水平的不断提高&#xff0c;对于中医主张的理念越来越认可&#xff0c;对中医的需求也越来越多。在诊断中&#xff0c;中医通过观察人的舌头的舌质、苔…...

SQL查询转化为 Elasticsearch 查询

使用SQL 转化为查询 Elasticsearch 支持 sql 语句转化为 elasticsearch 的 查询语句 第一步&#xff1a; 打开在线转换工具的网页&#xff0c;进入工具页面 第二步&#xff1a;在指定的输入框中输入需要转换的 sql 语句。 您学会了这么简单的办法...

目标检测教程视频指南大全

魔鬼面具-哔哩哔哩视频指南 必看干货系列(建议搞深度学习的小伙伴都看看,特别是图像相关) 深度学习常见实验问题与实验技巧(适用于所有模型&#xff0c;小白初学者必看!)还在迷茫深度学习中的改进实验应该从哪里开始改起的同学&#xff0c;一定要进来看看了&#xff01;用自身…...

【Linux取经路】文件系统之重定向的实现原理

文章目录 一、再来理解重定向1.1 输出重定向效果演示1.2 重定向的原理1.3 dup21.4 输入重定向效果演示1.5 输入重定向代码实现 二、再来理解标准输出和标准错误2.1 同时对标准输出和标准错误进行重定向2.2 将标准输出和标准错误重定向到同一个文件 三、再看一切皆文件四、结语 …...

JAVA设计模式结构型模式

一、前言 java设计模式主要分为创建型模式&#xff0c;结构型模式和行为型模式。上一篇主要总结了行为型设计模式&#xff0c;本章总结&#xff0c;结构型模式。像创建型模式就不写了&#xff0c;比较简单。大概知道是工厂模式和建造者模式&#xff0c;原型模式就行&#xff0…...

第4讲引入JWT前后端交互

引入JWT前后端交互 Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准&#xff08;(RFC 7519)&#xff1b; JWT就是一段字符串&#xff0c;用来进行用户身份认证的凭证&#xff0c;该字符串分成三段【头部、载荷、签证】 后端接口测试&…...

基于Java的车辆租赁管理平台/租车系统

功能介绍 平台采用B/S结构&#xff0c;后端采用主流的Springboot框架进行开发&#xff0c;前端采用主流的Vue.js进行开发。 整个平台包括前台和后台两个部分。 前台功能包括&#xff1a;首页、车辆详情、车辆预订、用户中心模块。后台功能包括&#xff1a;车辆管理、分类管理…...

如何升级至ChatGPT Plus:快速指南,ChatGPT的秘密武器GPT4.0是什么?

提到 ChatGPT。想必大家都有所耳闻。自从 2022 年上线以来&#xff0c;就受到国内外狂热的追捧和青睐&#xff0c;上线2个月&#xff0c;月活突破1个亿&#xff01;&#xff01;&#xff01; 而且还在持续上涨中。因为有很多人都在使用 ChatGPT 。无论是各大头条、抖音等 App、…...

【天衍系列 05】Flink集成KafkaSink组件:实现流式数据的可靠传输 高效协同

文章目录 01 KafkaSink 版本&导言02 KafkaSink 基本概念03 KafkaSink 工作原理1.初始化连接2.定义序列化模式3.创建KafkaSink算子4.创建数据源5.将数据流添加到KafkaSink6.内部工作机制 04 KafkaSink参数配置05 KafkaSink 应用依赖06 KafkaSink 快速入门6.1 包结构6.2 项目…...

深度学习之pytorch实现逻辑斯蒂回归

深度学习之pytorch实现逻辑斯蒂回归 解决的问题数学公式logiatic函数损失值 代码与线性回归代码的区别数据损失值构造回归的函数 结果分析 解决的问题 logistic 适用于分类问题&#xff0c;这里案例( y为0和1 &#xff0c;0和 1 分别代表一类&#xff09; 于解决二分类&#xf…...

有事休假店铺无人看守怎么办?智能远程视频监控系统保卫店铺安全

在春节期间&#xff0c;很多自营店主也得到了久违的假期&#xff0c;虽然很多店主都是长期在店铺中看守&#xff0c;但遇到春节这样的日子&#xff0c;多数人还是选择回乡休假。面对店主休假或有事不能管理店铺时&#xff0c;传统的监控虽然可以做到单一的监控&#xff0c;却仍…...

酷开科技 | 酷开系统壁纸模式,让过年更有氛围感!

在阵阵爆竹声中&#xff0c;家家户户都沉浸在浓浓的年味中。过年&#xff0c;是团圆&#xff0c;是温暖。团团圆圆的日子里&#xff0c;仪式感不可少&#xff0c;换上一张喜气洋洋的电视壁纸吧&#xff0c;寓意幸福一年又一年。打开酷开系统壁纸模式挑选一张年味十足的壁纸&…...

Docker中部署flink集群的两种方式

文章目录 一、概述二、准备工作三、方式一四、方式二1、准备配置文件2、执行 docker 命令 一、概述 本文将通过 2 种方式在 docker 中部署 flink standalone 集群&#xff0c;集群中共有 4 个节点&#xff0c;分别是 1 个 jobManager 节点和 3 个 taskManager 节点。方式一能快…...

八、计算机视觉-边界填充

文章目录 前言一、原理二、具体的实现 前言 在Python中使用OpenCV进行边界填充&#xff08;也称为zero padding&#xff09;是一种常见的图像处理操作&#xff0c;通常用于在图像周围添加额外的像素以便进行卷积或其他操作。下面是使用OpenCV进行边界填充的基本原理和方法 一…...

ffmpeg 硬件加速介绍

基于OS的硬件加速 Windows 参考[2],基于windows的硬件加速都是基于DirectX API,我们可以用ffmpeg -hwaccels查看当前环境支持的硬件加速接口,如下为windows上的执行ffmpeg --hwaccels的结果。 在linux上执行ffmpeg -hwaccels的结果如下: 可以看到windows上支持的硬件加速…...

【QT+QGIS跨平台编译】之三十九:【Exiv2+Qt跨平台编译】(一套代码、一套框架,跨平台编译)

文章目录 一、Exiv2介绍二、文件下载三、文件分析四、pro文件4.1 exiv2-xmp4.2 exiv2lib_int4.3 exiv2lib五、编译实践一、Exiv2介绍 Exiv2是一个开源的C++库,用于读取、编辑和写入图片和视频文件的元数据。它可以处理各种类型的元数据,包括EXIF、IPTC、XMP等。 元数据是与…...

术业有专攻!三防加固平板助力工业起飞

在日常使用中的商业电脑比较追求时效性&#xff0c;以市场定位做标准&#xff0c;内部元件只需满足一般要求就行&#xff0c;使用寿命比较短。而三防平板电脑是主要运用在复杂、恶劣的环境下所以在需求方面较高,需要保证产品在恶劣条件下正常使用&#xff0c;满足行业领域的需求…...

适合tiktok运营的云手机需要满足什么条件?

TikTok作为一款全球热门的社交媒体平台&#xff0c;具有无限的市场潜力。然而&#xff0c;卖家在运营过程中常常会面临到视频0播、账号被降权、限流等问题&#xff0c;甚至可能因为多人同时使用一个IP而导致封号的风险。为了规避这些问题&#xff0c;越来越多的卖家将目光投向了…...

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…...

ES6从入门到精通:前言

ES6简介 ES6&#xff08;ECMAScript 2015&#xff09;是JavaScript语言的重大更新&#xff0c;引入了许多新特性&#xff0c;包括语法糖、新数据类型、模块化支持等&#xff0c;显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var&#xf…...

脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)

一、数据处理与分析实战 &#xff08;一&#xff09;实时滤波与参数调整 基础滤波操作 60Hz 工频滤波&#xff1a;勾选界面右侧 “60Hz” 复选框&#xff0c;可有效抑制电网干扰&#xff08;适用于北美地区&#xff0c;欧洲用户可调整为 50Hz&#xff09;。 平滑处理&…...

React Native 导航系统实战(React Navigation)

导航系统实战&#xff08;React Navigation&#xff09; React Navigation 是 React Native 应用中最常用的导航库之一&#xff0c;它提供了多种导航模式&#xff0c;如堆栈导航&#xff08;Stack Navigator&#xff09;、标签导航&#xff08;Tab Navigator&#xff09;和抽屉…...

零基础设计模式——行为型模式 - 责任链模式

第四部分&#xff1a;行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习&#xff01;行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想&#xff1a;使多个对象都有机会处…...

LRU 缓存机制详解与实现(Java版) + 力扣解决

&#x1f4cc; LRU 缓存机制详解与实现&#xff08;Java版&#xff09; 一、&#x1f4d6; 问题背景 在日常开发中&#xff0c;我们经常会使用 缓存&#xff08;Cache&#xff09; 来提升性能。但由于内存有限&#xff0c;缓存不可能无限增长&#xff0c;于是需要策略决定&am…...

NPOI操作EXCEL文件 ——CAD C# 二次开发

缺点:dll.版本容易加载错误。CAD加载插件时&#xff0c;没有加载所有类库。插件运行过程中用到某个类库&#xff0c;会从CAD的安装目录找&#xff0c;找不到就报错了。 【方案2】让CAD在加载过程中把类库加载到内存 【方案3】是发现缺少了哪个库&#xff0c;就用插件程序加载进…...

怎么让Comfyui导出的图像不包含工作流信息,

为了数据安全&#xff0c;让Comfyui导出的图像不包含工作流信息&#xff0c;导出的图像就不会拖到comfyui中加载出来工作流。 ComfyUI的目录下node.py 直接移除 pnginfo&#xff08;推荐&#xff09;​​ 在 save_images 方法中&#xff0c;​​删除或注释掉所有与 metadata …...

基于Java+VUE+MariaDB实现(Web)仿小米商城

仿小米商城 环境安装 nodejs maven JDK11 运行 mvn clean install -DskipTestscd adminmvn spring-boot:runcd ../webmvn spring-boot:runcd ../xiaomi-store-admin-vuenpm installnpm run servecd ../xiaomi-store-vuenpm installnpm run serve 注意&#xff1a;运行前…...

【LeetCode】算法详解#6 ---除自身以外数组的乘积

1.题目介绍 给定一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&#xff0c;且在 O…...