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

手写链路追踪优化-自动全局追踪代替局部手动追踪

1. 前文回顾和优化需求分析

前文的demo已经具备了简单的日志追踪能力
前文:手写链路追踪
它的缺点也明显,它是API level的,也就是如果想全局追踪,每个API都需要手写一份,这就会产生很多重复代码,同时我们在API层面也不想见到跟业务无关的代码,该怎么优化呢?
要想全局有效,一劳永逸,你可能会想到放到filter或者aspect处理,让我们对比一下哪个更合适

特性FilterAspect
作用范围Web请求层面方法调用层面
依赖关系依赖Servlet容器依赖AOP框架(如Spring AOP)
触发条件所有HTTP请求特定方法调用
配置方式web.xml或注解注解或XML配置
执行顺序按注册顺序按优先级
灵活性相对较低较高,可精确控制切入点

因为是从API转移,符合Web请求层面和http触发条件,跟filter情景一致,所以考虑把它转移到filter

2. 代码实现

2.1 创建一个filter

实现如下功能 :

  • servletRequest拦截trace id的请求头
  • 如果upstream没有传入trace id,系统内部自己随机生成一个
  • 用trace id替换当前线程的线程名(这是利用了在传统的Spring MVC中,默认情况下是一个请求对应一个阻塞线程的原理,这里留个伏笔,请思考还有什么遗漏)
  • 为了线程安全,直接修改线程名需要注意恢复
package com.sandwich.logtracing.filter;import com.sandwich.logtracing.util.RandomStrUtils;
import jakarta.servlet.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.RequestFacade;
import org.apache.commons.lang3.StringUtils;import java.io.IOException;/*** @Author 公众号: IT三明治* @Date 2025/8/30* @Description: log filter, to update the log thread name with a trace id*/
@Slf4j
public class LogFilter implements Filter {@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {String traceId = ((RequestFacade) servletRequest).getHeader("x-request-correlation-id");//if the request header don't have a trace id,then generate a random oneif (StringUtils.isBlank(traceId)) {traceId = RandomStrUtils.generateRandomString(15);}// keep original thread nameThread currentThread = Thread.currentThread();String originalName = currentThread.getName();try {//replace current thread name with a trace idThread.currentThread().setName(traceId);filterChain.doFilter(servletRequest, servletResponse);} finally {//restore thread name before api request endThread.currentThread().setName(originalName);}}
}

2.2 注册filter

package com.sandwich.logtracing.config;import com.sandwich.logtracing.filter.LogFilter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;/*** @Author 公众号: IT三明治* @Date 2025/8/30* @Description:*/
@Configuration
public class WebConfiguration {@Bean@ConditionalOnMissingBean(LogFilter.class)@Order(Ordered.HIGHEST_PRECEDENCE + 101)public FilterRegistrationBean<LogFilter> logFilterFilterRegistrationBean() {FilterRegistrationBean<LogFilter> bean = new FilterRegistrationBean<>();bean.setFilter(new LogFilter());bean.addUrlPatterns("/*");return bean;}
}

2.3 删除API中trace id的处理逻辑

package com.sandwich.logtracing.controller;import com.sandwich.logtracing.entity.ApiResponse;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;/*** @Author 公众号: IT三明治* @Date 2025/8/29* @Description: login demo controller*/
@Slf4j
@RestController
@RequestMapping("/test")
public class LoginController {@PostMapping("/login")public ApiResponse<String> login(@RequestBody LoginRequest loginRequest) {for (int i=1; i<= 10; i++) {log.info("processing login for user {}, login step {} done", loginRequest.getUsername(), i);}log.info("user {} login success", loginRequest.getUsername());return ApiResponse.success("Sandwich login success", Thread.currentThread().getName());}@Datapublic static class LoginRequest {private String username;private String password;}
}

注意:Thread.currentThread().getName()已经变成trace id了。

2.4 为了显示API请求的所有内容,还是用shell的方式请求

#!/bin/bash# Define the API endpoint
API_URL="http://localhost:8080/test/login"function generate_random_string() {# 使用openssl生成随机字符串(如果已安装)if command -v openssl &> /dev/null; thenopenssl rand -base64 20 | tr -dc 'a-zA-Z0-9' | fold -w 15 | head -n 1else# 使用系统方法生成local chars="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"local result=""result=$(printf "%s" "${chars:$((RANDOM % ${#chars})):1}"{1..15} | tr -d '\n')echo "$result"fi
}function normalLogin() {# 生成15位随机字符串作为traceIdtraceId=$(generate_random_string)echo "Generated traceId from client side: $traceId"response=$(curl -X POST $API_URL \-H "Content-Type: application/json" \-H "x-request-correlation-id: $traceId" \-d '{"username": "Sandwich", "password": "test"}')echo "Response from login API:"# 通过python工具将返回信息格式化成json格式echo "$response" | python -m json.tool
}normalLogin

3. 验证测试

  • 启动项目
  • 执行shell请求
Administrator@USER-20230930SH MINGW64 /d/git/java/log-tracing/shell (master)
$ ./login.sh
Generated traceId from client side: ubBKwVCauRVV78m% Total    % Received % Xferd  Average Speed   Time    Time     Time  CurrentDload  Upload   Total   Spent    Left  Speed
100   144    0   100  100    44   7636   3360 --:--:-- --:--:-- --:--:-- 11076
Response from login API:
{"responseCode": 200,"message": "success","data": "Sandwich login success","traceId": "ubBKwVCauRVV78m"
}
  • 用trace id追踪日志信息
    日志追踪

4. 总结

经过以上实现,我们把日志追踪搬到了filter实现,API只需要完成业务逻辑即可,新增API接口也不再需要手工去写日志追踪逻辑。但是这个优化还不够好,请关注我,下期告诉你为什么。这期只对以下文件做了修改

http://www.xdnf.cn/news/19376.html

相关文章:

  • 做一个实用的节假日工具
  • Java面试-spring boot框架
  • 98、23种设计模式之代理模式(7/23)
  • 【SpringMVC】SSM框架【二】——SpringMVC超详细
  • ModuleNotFoundError: No module named ‘cairosvg‘
  • 浔川社团阅读量破历史记录
  • 得物25年春招-安卓部分编程题
  • GD32入门到实战21--输入捕获
  • 【C++】日期类实现详解:代码解析与复用优化
  • C#正则表达式与用法
  • 【基础-单选】关于Tabs组件页签的位置设置,下面描述错误的是
  • 免费在线图片合成视频工具 ,完全免费
  • uni.onBLECharacteristicValueChange接收到数据,返回的value为{}的原因及其获取方法
  • 佳易王钟表维修养护管理系统:开启钟表维修高效管理新篇章​就#软件操作教程
  • Mysql 学习day 2 深入理解Mysql索引底层数据结构
  • React前端开发_Day6-Day9_极客园项目
  • C语言 - 输出参数详解:从简单示例到 alloc_chrdev_region
  • Spring AI 的应用和开发
  • 如何简单建设一个网站,让用户快速找到你。
  • 在PowerPoint和WPS演示让蝴蝶一直跳8字舞
  • Python生成免安装exe
  • SAP PP模块的MPS
  • Vue加载速度优化,verder.js和element.js加载速度慢解决方法
  • 防火墙技术(二):安全区域
  • C#调用c++ dll读取2进制文件时而正常,时而异常
  • 语义分割目前还是研究热点吗?
  • 如何快速了解项目管理基础
  • 【具身智能】【机械臂】机械臂轨迹规划项目以及资料汇总【持续更新】
  • 【物联网】MQTT / Broker / Topic 是什么?
  • windows 谷歌浏览器把英文改成中文