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

2023-10-28

日志拦截器

import com.evcas.charge.pile.platform.common.annotation.LogNoTrace;
import com.evcas.charge.pile.platform.common.utils.StringUtils;
import org.slf4j.MDC;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.UUID;

public class LogInterceptor implements HandlerInterceptor {

    private static final String TRACE_ID = "traceId";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        Method method = ((HandlerMethod) handler).getMethod();
        if (method.isAnnotationPresent(LogNoTrace.class)) {
            return true;
        }
        String tid = UUID.randomUUID().toString().replace("-", "").substring(0, 10);
        // 如果有上层调用就用上层的ID
        if (!StringUtils.isEmpty(request.getHeader(TRACE_ID))) {
            tid = request.getHeader(TRACE_ID);
        }
        MDC.put(TRACE_ID, tid);
        return true;
    }

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

}
import java.lang.annotation.*;

/**
 * 不需要日志跟踪
 */
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogNoTrace {

}
import com.gotion.opencenter.interceptor.LogInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfigurerAdapter implements WebMvcConfigurer {
    @Bean
    public LogInterceptor logInterceptor() {
        return new LogInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(logInterceptor());
    }
}

修改日志格式

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

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

trackId 丢失解决

异步子线程会丢失了trackId:
思路: 将父线程的trackId传递下去给子线程即可。

1、自定义线程池,交给 Spring 管理

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import java.util.concurrent.Executor;

@Configuration
@EnableAsync
public class MyThreadPoolConfig {
    @Bean("MyExecutor")
    public Executor asyncExecutor() {
        MyThreadPoolTaskExecutor executor = new MyThreadPoolTaskExecutor();
        //核心线程数5:线程池创建时候初始化的线程数
        executor.setCorePoolSize(5);
        //最大线程数5:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
        executor.setMaxPoolSize(5);
        //缓冲队列500:用来缓冲执行任务的队列
        executor.setQueueCapacity(500);
        //允许线程的空闲时间60秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
        executor.setKeepAliveSeconds(60);
        //线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
        executor.setThreadNamePrefix("myExecutor");
        executor.initialize();
        return executor;
    }
}
import org.slf4j.MDC;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Callable;
import java.util.concurrent.Future;

public class MyThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {

    public MyThreadPoolTaskExecutor() {
        super();
    }

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


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

    @Override
    public Future<?> submit(Runnable task) {
        return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
    }
}
import org.slf4j.MDC;

import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable;

public class ThreadMdcUtil {

    private static final String TRACE_ID = "traceId";

    // 获取唯一性标识
    public static String generateTraceId() {
        return UUID.randomUUID().toString();
    }

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

    /**
     * 用于父线程向线程池中提交任务时,将自身MDC中的数据复制给子线程
     *
     * @param callable
     * @param context
     * @param <T>
     * @return
     */
    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();
            }
        };
    }

    /**
     * 用于父线程向线程池中提交任务时,将自身MDC中的数据复制给子线程
     *
     * @param runnable
     * @param context
     * @return
     */
    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();
            }
        };
    }
}

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SpringBoot 之 MDC 实现全链路调用日志跟踪 的相关文章

  • 如何以编程方式检查应用程序是否在调试模式下运行?

    我必须在应用程序中的某个位置确定我的应用程序是在调试模式还是实时模式下运行 是否有任何函数或代码可用于检查 在开 关两种情况下都会返回 true false 如果是这样 请帮助我 提前致谢 从问题中尚不清楚调试模式是否指的是 应用程序是否可
  • 为移动设备扩展 libgdx UI?

    眼下desktop应用程序的版本很好 按钮缩放得很好 但是当我部署到android它们很小 几乎无法使用 DesktopLauncher public class DesktopLauncher public static void mai
  • mvn dependency:analyze 结果不正确

    我一直在寻找一种工具 它能够向您显示未使用的依赖项 我很快就偶然发现了 Maven 命令mvn dependency analyze 这样做的问题是 它经常检测到 未使用的 依赖项 如果缺失 这些依赖项就会导致构建失败 这是优化项目的示例
  • JLabel.setText() 中的换行符

    使用 JLabel setText 时如何插入换行符 我尝试使用 Html 但似乎可以使其适用于 setText 仅适用于 jLabel 的初始声明 最初声明 jlabel 时的方法是 label new JLabel Hello Worl
  • 如何使用 log4j 自动记录类中调用的每个方法

    我有一个包含数据库调用的类 我通常希望使用 log4j 记录该类中调用的每个方法 带参数 logger debug foo id id initiated 可以自动执行此操作吗 也许通过在每个方法的开头使用某种注释而不是编写每个 logge
  • JSP重定向和传值

    我有一个 JSP 其中我重定向到另一个 jsp 例如 我在该jsp中没有任何其他数据 我想将值从该jsp index jsp 传递到重定向jsp login jsp 我将如何做到这一点 这里的 logonInput 是在struts con
  • 用户“root”@“localhost”的访问被拒绝

    我正在尝试从数据库中获取记录 但我面临这个访问被拒绝的问题 我尝试了 Stack Overflow 上提到的其他解决方案 例如向用户授予权限 但没有任何效果 访问数据库的代码 public void service HttpServletR
  • 如何用java对jpg进行像素化?

    我正在尝试使用 Java 6 对 JPEG 进行像素化 但运气不佳 它需要使用 Java 而不是像 Photoshop 这样的图像处理程序 并且它需要看起来像老派 像这样 有谁能够帮助我 使用java awt image javadoc h
  • 用java解密AES加密文件

    我有一个使用 AES 使用 java 应用程序加密的文件 我还有一个加密的密钥文件 但我不明白如何使用密钥来解密文件 大多数教程和示例都会在一个地方创建临时随机密钥 加密文件和解密 所以 问题是如何指定解密时必须使用的密钥 EDIT 我发现
  • 从 Java 调用 Python 代码时出现问题(不使用 jython)

    我发现这是从 java 运行 使用 exec 方法 python 脚本的方法之一 我在 python 文件中有一个简单的打印语句 但是 我的程序在运行时什么也没做 它既不打印Python文件中编写的语句 也不抛出异常 程序什么都不做就终止了
  • 如何在最短的时间内克隆java中的输入流

    有人可以告诉我如何克隆输入流 并花费尽可能少的创建时间吗 我需要多次克隆输入流以使用多种方法来处理 IS 我尝试了三种方法 但由于这样或那样的原因 事情不起作用 方法 1 感谢 stackoverflow 社区 我发现以下链接很有帮助 并将
  • java3d 中的面部着色

    使用java3d 如何不在每个顶点基础上着色 而是在每个面基础上着色 我尝试学习 java3d 但我生成的 Shape3d 看起来并不符合预期 我想用不同的颜色给不同的三角形着色 但我不知道该怎么做 纹理看起来有点大材小用 而且我根本没有掌
  • 对于每个抛出异常的语句,try/catch 是否被视为反模式?

    我目前正在审查同事的 Java 代码 我看到很多情况下 每个可能抛出异常的语句都被封装在自己的 try catch 中 其中 catch 块都执行相同的操作 哪个操作与我的问题无关 对我来说 这似乎是一种代码味道 我记得读到过它是一种常见的
  • 使用 Java 进行 AES 加密并使用 Javascript 进行解密

    我正在制作一个需要基于 Java 的 AES 加密和基于 JavaScript 的解密的应用程序 我使用以下代码作为基本形式进行加密 public class AESencrp private static final String ALG
  • 如何实现再次播放功能?

    我希望在游戏结束时得到提示 如果我还想再玩一次的话 并使用 Y N 输入 退出游戏或重复游戏 我该如何以最有效的方式解决这个问题 编辑 描述资源路径位置类型 类型 Main Main java ScaredyCat src se grupp
  • 仅在java中使用数组计算50的阶乘

    我是java的初学者 我有一个作业要编写一个完整的程序 使用数组计算 50 的阶乘 我无法使用像 biginteger 这样的任何方法 我只能使用数组 因为我的教授希望我们理解背后的逻辑 我猜 然而 他并没有真正教我们数组的细节 所以我在这
  • 如何使用自定义转换器访问 jOOQ 生成的例程字段作为值?

    我在访问生成例程的字段时遇到问题PL pgSQL 用户定义函数 返回JSON 数据类型结果 已经提到this https stackoverflow com q 62535195 6805866问题 这是我的结果get all orders
  • SAXParseException:找不到元素“定义”的声明

    我对 camunda 和 DMN 完全陌生 我试图在 spring boot 中运行 DMN 示例 链接在这里 https github com camunda camunda bpm examples tree master dmn en
  • 当我必须在 Netty4 编码器中调用 ByteBuf.retain() 时?

    我正在编写一个以 NUL 终止 JSON 消息的编码器 以便在消息碎片的情况下可以对其进行解码 我找到了这个样本 gt click https github com netty netty blob master codec src mai
  • Spring JMS开始根据请求监听jms队列

    Spring提供 JMSListener用于监听来自特定队列的消息的注释 还有一个替代方案实施JmsListenerConfigurer http docs spring io spring docs current spring fram

随机推荐

  • linux 下进程间通讯: 共享文件

    共享文件算是比较传统的进程间数据交换的一种方式 但是由于涉及到不同进程间反复文件I O 难免显得有些效率低下 共享文件的本质 实际是就是某个进程向共享为念写入数据 一个或多个进程从文件中读取数据 有可能涉及到进程之间资源竞争的问题 这里就涉
  • 【使用教程】一体化伺服电机在使能状态下如何切换模式

    PMM一体化伺服电机是一种集成了控制器 驱动器 编码器和伺服电机的设备 在使能状态下 电机进入 工作状态 在此状态下切换模式可以改变电机的运行方式 本文将介绍PMM一体化伺服电机在使能状态下如何切换模式 首先 我们需要了解PMM一体化伺服电
  • yolov2详细讲解

    yolov2详细讲解 概述 YOLO v1虽然检测速度快 但在定位方面不够准确 并且召回率较低 为了提升定位准确度 改善召回率 yolov2在yolov1的基础上提出了几种改进策略 如下图所示 一些改进方法能有效提高模型的mAP 改进方法介
  • java注解与反射

    一 注解Annotation 什么是注解 从jdk1 5之后 java增加对元数据 描述数据的数据 的支持 就是注解 注解可以再程序编译 类加载 运行时被读取 并执行相依的操作 注解 Annotation 它可以声明在包 类 字段 方法 局
  • Linux 环境部署 Nexus 服务

    一 私服是什么 一个特殊的远程仓库 它是架设在局域网内的仓库服务 供局域网内的开发人员使用 当Maven需要下载构建的使用 它先从私服请求 如果私服上没有的话 则从外部的远程仓库下载 然后缓存在私服上 再为Maven的下载请求提供服务 环境
  • 【满分】【华为OD机试真题2023 JS】狼羊过河

    华为OD机试真题 2023年度机试题库全覆盖 刷题指南点这里 狼羊过河 时间限制 1s 空间限制 256MB 限定语言 不限 题目描述 一农夫带着m只羊 n只狼过河 农夫有一条可载x只狼 羊的船 农夫在时或者羊的数量大于狼时 狼不会攻击羊
  • 剪映电脑版详细使用教程,让视频剪辑变得更简单了

    这几天关于剪映电脑版的消息非常多 相比于专业成熟的视频剪辑软件 但大家对这款剪映似乎特别感兴趣 小编也抽时间简单使用了一下 相比于adobe Premiere 和达芬奇来说 真的特别简单上手 结合了手机版的触摸与鼠标操作 新手也能简单上手了
  • 软件测试主要考点梳理以及真题讲解(附答案)

    需要题目答案及相关复习资料关注后留言私信即可 白驹过隙 转眼大学三年就过去了 软件测试与维护也成为大学中最后一门考试的科目 接下来为大家总结一下软件测试与维护考试的主要内容 题型 以及真题答案 一 题型 题型仅限于我们学校 SCUT 其他学
  • EDA12--DC脚本命令(一)

    这里写目录标题 一 流程简介 二 启动DC 三 读入与链接 3 1 analyze elaborate 3 1 1 analyze命令 3 1 2 elaborate命令 3 2 read命令 四 工作环境设置 4 1 设置工作条件 4 2
  • STM32——PWM(呼吸灯&舵机使用)

    目录 1 与pwm相关的函数介绍 1 1 输出比较函数配置 1 1 1 以下四个函数是配置图1中的四个比较单元 1 1 2 输出结构体赋默认值 1 2 单独修改参数的相关函数 1 2 1 单独设置极性相关函数 1 2 2 单独修改输出使能参
  • 数字图像处理领域的二十四个典型算法

    转自 http blog csdn net v JULY v article details 6210124 作者 July 二零一一年二月二十六日 参考 百度百科 维基百科 vc数字图像处理 数字图像处理领域的二十四个典型算法及vc实现
  • 计算机中丢失MSVCR120.dll,电脑找不到MSVCR120.dll怎么办

    在电脑打开浏览器后在顶部栏目搜索或许点击这里传送门 dll修复程序 site 按下回车键然后进入下载msvcp120 dll系统文件 1 然后再打开解压好的文件 打开后点击开始安装电脑丢失的msvcp120 dll文件 2 开始快速的进行相
  • 半监督目标检测(三)

    目录 ISMT 动机 1 Overview 2 Pseudo Labels Fusion 3 Interactive Self Training 4 Mean Teacher Unbiased Teacher 动机 1 Overview 2
  • H5使用hook实现网络连接情况判断

    最近使用hook写了一个小练习 作用就是判断网络的连接情况 并在连接情况变化的时候可以作出一系列的操作 话不多说 上代码 function useCheckOnline navigator onLine代表当前的网络连接情况 const o
  • 第六课:MAC去中心化钱包开发之备份私钥

    一 私钥 这节课继续将注册Token后的步骤 就是备份私钥 每个公链都会有私钥 但是不尽相同 MAC底层的私钥比较多 有4个 分别是 钱包钥匙 钱包钥匙是开启MAC钱包的必备信息之一 格式为mac三个字母开头的一长串字符 创建钱包账户后会提
  • 新人小白求助大佬

    本人在进行病例对照匹配 想要匹配比是1 2 匹配条件是年龄 3岁 孕周 3周 我之前用的R语言代码是MatchIt代码 代码如下 这个代码有两个问题 一个是匹配方法 method 中没有范围的选项 只能按照 nearest 或者 exact
  • Ubuntu18.04中修改Ubuntu的外观(菜单栏放到屏幕下方)

    因为用的synergy分屏使用 所以想要将Ubuntu18 04的左侧栏放到屏幕底部 因为鼠标在点击左侧另一个电脑侧边栏的时候会不小心点到 过程 安装gnome tweak tool感觉是最方便的 直接打开命令行 先安装配置工具 sudo
  • 第三回:布局格式定方圆

    文章目录 第三回 布局格式定方圆 一 子图 1 使用 plt subplots 绘制均匀状态下的子图 2 使用 GridSpec 绘制非均匀子图 二 子图上的方法 思考题 第三回 布局格式定方圆 import numpy as np imp
  • Vuejs实践--事件绑定

    Vue中的事件绑定一般通过v on指令来绑定事件 事件绑定 v on 事件绑定的表达式的值可以是js语句 也可以是在methods选项中定义好的方法名 有参数的时候当然需要传参 在vue事件中 如果要用到事件对象e 要将e作为形参传入函数
  • SpringBoot 之 MDC 实现全链路调用日志跟踪

    文章目录 日志拦截器 修改日志格式 trackId 丢失解决 日志拦截器 import com evcas charge pile platform common annotation LogNoTrace import com evcas