MDC 日志跟踪笔记

2024-02-19 23:44
文章标签 日志 笔记 跟踪 mdc

本文主要是介绍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 日志跟踪笔记的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/726372

相关文章

SQL Server跟踪自动统计信息更新实战指南

《SQLServer跟踪自动统计信息更新实战指南》本文详解SQLServer自动统计信息更新的跟踪方法,推荐使用扩展事件实时捕获更新操作及详细信息,同时结合系统视图快速检查统计信息状态,重点强调修... 目录SQL Server 如何跟踪自动统计信息更新:深入解析与实战指南 核心跟踪方法1️⃣ 利用系统目录

深度解析Nginx日志分析与499状态码问题解决

《深度解析Nginx日志分析与499状态码问题解决》在Web服务器运维和性能优化过程中,Nginx日志是排查问题的重要依据,本文将围绕Nginx日志分析、499状态码的成因、排查方法及解决方案展开讨论... 目录前言1. Nginx日志基础1.1 Nginx日志存放位置1.2 Nginx日志格式2. 499

使用Python构建一个高效的日志处理系统

《使用Python构建一个高效的日志处理系统》这篇文章主要为大家详细讲解了如何使用Python开发一个专业的日志分析工具,能够自动化处理、分析和可视化各类日志文件,大幅提升运维效率,需要的可以了解下... 目录环境准备工具功能概述完整代码实现代码深度解析1. 类设计与初始化2. 日志解析核心逻辑3. 文件处

C++ Log4cpp跨平台日志库的使用小结

《C++Log4cpp跨平台日志库的使用小结》Log4cpp是c++类库,本文详细介绍了C++日志库log4cpp的使用方法,及设置日志输出格式和优先级,具有一定的参考价值,感兴趣的可以了解一下... 目录一、介绍1. log4cpp的日志方式2.设置日志输出的格式3. 设置日志的输出优先级二、Window

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

解读GC日志中的各项指标用法

《解读GC日志中的各项指标用法》:本文主要介绍GC日志中的各项指标用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、基础 GC 日志格式(以 G1 为例)1. Minor GC 日志2. Full GC 日志二、关键指标解析1. GC 类型与触发原因2. 堆

MySQL 打开binlog日志的方法及注意事项

《MySQL打开binlog日志的方法及注意事项》本文给大家介绍MySQL打开binlog日志的方法及注意事项,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要... 目录一、默认状态二、如何检查 binlog 状态三、如何开启 binlog3.1 临时开启(重启后失效)

SpringBoot项目配置logback-spring.xml屏蔽特定路径的日志

《SpringBoot项目配置logback-spring.xml屏蔽特定路径的日志》在SpringBoot项目中,使用logback-spring.xml配置屏蔽特定路径的日志有两种常用方式,文中的... 目录方案一:基础配置(直接关闭目标路径日志)方案二:结合 Spring Profile 按环境屏蔽关

Golang 日志处理和正则处理的操作方法

《Golang日志处理和正则处理的操作方法》:本文主要介绍Golang日志处理和正则处理的操作方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考... 目录1、logx日志处理1.1、logx简介1.2、日志初始化与配置1.3、常用方法1.4、配合defer

Apache 高级配置实战之从连接保持到日志分析的完整指南

《Apache高级配置实战之从连接保持到日志分析的完整指南》本文带你从连接保持优化开始,一路走到访问控制和日志管理,最后用AWStats来分析网站数据,对Apache配置日志分析相关知识感兴趣的朋友... 目录Apache 高级配置实战:从连接保持到日志分析的完整指南前言 一、Apache 连接保持 - 性