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

spring webflux链路跟踪【traceId日志自动打印】

前言

关于Spring Webflux链路跟踪的问题,在几年前我这篇博客讨论过,在那个时候并没有什么好的方式,所以里面提到的方式并不优雅。几年过去了,社区关于这个问题也做了很多努力让它更好用,并且提供了新的实现方式,这篇博客就介绍一下新的方式。建议看到最后,最优雅最简单的方式在最后描述。

传统Servlet中实现链路跟踪

实现单例中链路跟踪,在使用Spring Boot传统Servlet编程模式时,我们只需要在日志配置文件的日志输出格式中增加[%X{tid}],然后使用拦截器在请求到达时在MDC中设置tid就可以轻松实现。在Servlet中能如此轻松实现的前提是它的单线程处理单个请求的模型。而MDC存储tid这样的变量底层使用的是ThreadLocal。所以在Servlet中如果在一个请求中切面线程去处理逻辑也会出现tid丢失的问题。不过就一个应用的整体而言,在一个请求中大部分情况都不会切换线程,所以这也就不是什么大问题。可以在响应式中,一个请求可能需要切换好几次线程,并且是每个请求会切线程,这就是大问题了。

以前Spring WebFlux中实现链路跟踪

为了避免在响应式编程线程切换导致tid丢失的问题,所以借助了Reactor3中专门用来替换ThreadLocal的Context特性。在几年的博客中,我提到实现的方式就是基于Reactor3的Context来传递tid,然后在打印日志时手动去Context中取出数据然后打印。显而易见并没有Servlet中那么优雅,甚至可以说很笨。

全新的Spring WebFlux中实现链路跟踪

为了更优雅的实现链路跟踪,在Reactor 3.5.3这个版本中引入了Hooks.enableAutomaticContextPropagation()这个特性。从名称就能推测这个特性是用来自动传递Context的。下面是这个特性的全部说明注释。

Globally enables automatic context propagation to ThreadLocals.
It requires the context-propagation library  to be on the classpath to have an effect. Using the implicit global ContextRegistry it reads entries present in the modified Context using Flux.contextWrite(ContextView) (or Mono.contextWrite(ContextView)) and Flux.contextWrite(Function) (or Mono.contextWrite(Function)) and restores all ThreadLocals associated via same keys for which ThreadLocalAccessors are registered.
The ThreadLocals are present in the upstream operators from the contextWrite(...) call and the unmodified (downstream) Context is used when signals are delivered downstream, making the contextWrite(...) a logical boundary for the context propagation mechanism.
This mechanism automatically performs Flux.contextCapture() and Mono.contextCapture() in Flux.blockFirst(), Flux.blockLast(), Flux.toIterable(), and Mono.block() (and their overloads).

大致意思就是会自动的将设置在Context的key/value设置到ThreadLocals中去,使用它的前提是需要引入[这里是代码009]。当然,除了会自动将Context中的key/value设置到ThreadLocals,也会在调用链结束也就是发生订阅关系时自动将当前线程中的ThreadLocals中的key/value设置到Context。
有了Hooks.enableAutomaticContextPropagation()就可以和Servlet一样使用MDC了,因为Reactor会自动将Context信息同步到ThreadLocal,同样也会将ThreadLocal信息同步到Context。

用法(后面还有其他实现方式)

有了理论基础就可以动手写代码实际验证了。笔者的实现是在servlet上使用Reactor,所以部分代码可能在Spring WebFlux不一样,理论上直接在Spring WebFlux使用会更容易。

Jar包引入

首先引入Jar包,建议使用最新的版本的包,将Spring Webflux中的Reactor-core移除掉。虽然这个特性在3.5.3就引入,但是我实际使用过程中发现使用3.5.3并没有达到理想的效果,可能是有一些瑕疵,但是最新版本的没有问题,所以推荐使用最新版本的。

<dependency><groupId>io.projectreactor</groupId><artifactId>reactor-core</artifactId><version>3.7.2</version></dependency><dependency><groupId>io.micrometer</groupId><artifactId>context-propagation</artifactId><version>1.1.2</version></dependency>
过滤器设置TRACE_ID

如果是Spring WebFlux过滤器的写法需要修改,这里是Servlet的写法。

@Beanpublic Filter correlationFilter() {return (request, response, chain) -> {String traceId = IdUtil.fastSimpleUUID();try {MDC.put(TRACE_ID, traceId);chain.doFilter(request, response);} finally {MDC.remove(TRACE_ID);}};}
开启自动传递

在项目启动时执行Hooks.enableAutomaticContextPropagation();建议就放在main方法的第一行。

过滤器设置TRACE_ID

如果是Spring WebFlux过滤器的写法需要修改,这里是Servlet的写法。

@Beanpublic Filter correlationFilter() {return (request, response, chain) -> {String traceId = IdUtil.fastSimpleUUID();try {MDC.put(TRACE_ID, traceId);chain.doFilter(request, response);} finally {MDC.remove(TRACE_ID);}};}
注册ThreadLocalAccessor

下面是自定义的参数传达。

ContextRegistry.getInstance().registerThreadLocalAccessor(TRACE_ID,() -> MDC.get(TRACE_ID),traceId -> MDC.put(TRACE_ID, traceId),() -> MDC.remove(TRACE_ID));

当然如果想偷懒的话可以直接使用官方提供的Slf4jThreadLocalAccessor,它可以将MDC中所有的数据都设置到ThreadLocal,当然也可以指定部分key。

//全部keyContextRegistry.getInstance().registerThreadLocalAccessor(new Slf4jThreadLocalAccessor());//指定keyContextRegistry.getInstance().registerThreadLocalAccessor(new Slf4jThreadLocalAccessor("指定key"));
日志文件配置

在日志输出格式中增加上面配置的TRACE_ID。

pattern="${spring:spring.application.name}--[%X{TRACE_ID}]-[%d][%t][%level][%logger:%L] - %msg%n"charset="UTF-8" />       

至此就实现了Spring WebFlux的链路跟踪的功能,上面提到的方式是以最小包引入的实现的,所以我们自己做了一些比如像手动在过滤器设置TRACE_ID以及注册ThreadLocalAccessor额外的工作。其实在社区中有一种更简便的方式。

第二种用法(偷懒方式)

如果不介意项目中引入更多的包,可以使用这种方式。

jar包引入

在这个包中其实包含了上面的context-propagation,同样也注意要移除低版本的reactor-core。

<dependency><groupId>io.projectreactor</groupId><artifactId>reactor-core</artifactId><version>3.7.2</version></dependency><dependency><groupId>io.micrometer</groupId><artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
开启自动传递

在项目启动时执行Hooks.enableAutomaticContextPropagation();建议就放在main方法的第一行。

最后确定日志文件配置了链路id就ok了。

最后

对比几年前,现在实现Spring WebFlux的方式更加简便更加优雅,但是有一点需要注意。因为它多了很多从Context同步信息到ThreadLocal的操作,在引入这种方式时一定要做好压测,确保性能没有问题。

参考
https://blog.csdn.net/zhxdick/article/details/136092428

http://www.lryc.cn/news/610975.html

相关文章:

  • 移动端 WebView 调试实战 深色模式样式失效与主题切换异常排查指南
  • 前端1.0
  • Lua语言程序设计1:基础知识、数值、字符串与表
  • 针对软件定义车载网络的动态服务导向机制
  • linux_https,udp,tcp协议(更新中)
  • 实战项目3-工控软件-2.0- 自定义控件HMILabel的创建
  • 漏洞分析:90分钟安全革命
  • 赛灵思ZYNQ官方文档UG585自学翻译笔记:Quad-SPl Flash 闪存控制器
  • 信息系统项目管理中的沟通管理实战精解
  • 智慧油站误报率↓77%:陌讯多模态融合算法实战解析
  • 【Git】git提交代码报错Git: husky > pre-commit
  • 【Java面试题】注解,异常相关知识
  • 二维数点问题 1
  • Dell电脑Windows系统更新后声卡驱动无法识别插线耳机问题
  • 第13届蓝桥杯Scratch_选拔赛_初级组_真题2022年1月22日
  • leetcode-python-删除链表的倒数第 N 个结点
  • Leetcode 13 java
  • Linux网络编程:TCP初体验
  • 从递归到动态规划-解码方法Ⅱ
  • 【IDEA】IntelliJ IDEA 中文官方文档全面介绍与总结
  • 以Linux为例补充内存管理基础知识
  • 2025年服务器僵尸攻防战:从AI勒索到量子免疫,构建下一代“数字抗体”
  • Linux 常用命令大全
  • 基于vscode连接服务器实现远程开发
  • vi编辑器makefile的使用以及双向链表
  • 【C++详解】⼆叉搜索树原理剖析与模拟实现、key和key/value,内含优雅的赋值运算符重载写法
  • PHP实战代码解析与应用分享:用户管理、日志,配置管理与文件操作全解析
  • PostgreSQL——插入、更新与删除数据
  • [数组]977.有序数组的平方;209.长度最小的子数组
  • 初始化列表,变量存储区域和友元变量