网站首页 > 博客文章 正文
前言
查看服务日志时,当服务被调过于频繁,日志刷新太快,会影响到联调、测试、线上问题的排查效率,能不能为每一个请求的日志打一个唯一标识呢?后面使用该表示去匹配,直接检索出该请求的日志?引入本文的正题,“traceId”。
MDC
MDC定义 Mapped Diagnostic Context,即:映射诊断环境。
MDC是 log4j 和 logback 提供的一种方便在多线程条件下记录日志的功能。
MDC 可以看成是一个与当前线程绑定的哈希表,可以往其中添加键值对。
MDC的使用方法
向MDC设置值:MDC.put(key, value);
从MDC中取值:MDC.get(key);
将MDC中的内容打印到日志中:%X{key};
初始化TraceId并向MDC设置值
这里主要是利用切面,方法执行前设置MDC,方法执行后擦除MDC。具体实现方式有很多,如过滤器、拦截器、AOP等等。个人比较推荐Filter实现,因为Filter是请求最先碰到的,也是响应给前端前最后一个碰到的。
过滤器实现
@Slf4j
@WebFilter(filterName = "traceIdFilter", urlPatterns = "/*")
@Order(0)
@Component
public class TraceIdFilter implements Filter {
/**
* 日志跟踪标识
*/
public static final String TRACE_ID = "TRACE_ID";
@Override
public void init(FilterConfig filterConfig) {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
MDC.put(TRACE_ID, UUID.randomUUID().toString());
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
MDC.clear();
}
}
拦截器实现
@Component
public class TraceIdInterceptor extends HandlerInterceptorAdapter {
private static final String TRACE_ID = "TRACE_ID";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
MDC.put(TRACE_ID, UUID.randomUUID().toString());
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) {
MDC.clear();
}
}
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TraceIdInterceptor());
}
}
日志打印配置pattern中配置traceId
与之前的相比只是添加了[%X{TRACE_ID}], [%X{***}]是一个模板,中间属性名是我们使用MDC put进去的。
%d{yyyy-MM-dd} [%thread] %-5level %logger{50} - [%X{TRACE_ID}] - %msg%n
异步方法的日志打印traceId
异步方法会开启一个新线程,我们想要是异步方法和主线程共用同一个traceId,首先先新建一个任务适配器MdcTaskDecorator。
public class MdcTaskDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
Map<String, String> map = MDC.getCopyOfContextMap();
return () -> {
try {
MDC.setContextMap(map);
String traceId = MDC.get(TRACE_ID);
if (StringUtils.isBlank(traceId)) {
traceId = UUID.randomUUID().toString();
MDC.put(TRACE_ID, traceId);
}
runnable.run();
} finally {
MDC.clear();
}
};
}
}
在线程池配置中增加executor.setTaskDecorator(new MdcTaskDecorator())的设置
@EnableAsync
@Configuration
public class ThreadPoolConfig {
private int corePoolSize = Runtime.getRuntime().availableProcessors() + 1;
private int maxPoolSize = corePoolSize * 2;
private static final int queueCapacity = 50;
private static final int keepAliveSeconds = 30;
@Bean(name = "threadPoolTaskExecutor")
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setMaxPoolSize(maxPoolSize);
executor.setCorePoolSize(corePoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setKeepAliveSeconds(keepAliveSeconds);
executor.setTaskDecorator(new MdcTaskDecorator());
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
}
使用指定的线程池执行业务代码
@Async("threadPoolTaskExecutor")
public void testThreadPoolTaskExecutor(){
log.info("Async 测试一下");
}
在响应DTO中返回traceId
@Data
@AllArgsConstructor
public class RetResult<T> {
private Integer code;
private String msg;
private T data;
private String traceId;
public RetResult(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
this.traceId = MDC.get(TRACE_ID);
}
public static <T> RetResult<T> success(T t) {
return new RetResult<>(RetCode.SUCCESS.getCode(), "ok", t);
}
public static <T> RetResult<T> success() {
return new RetResult<>(RetCode.SUCCESS.getCode(), "ok", null);
}
public static <T> RetResult<T> fail() {
return new RetResult<>(RetCode.FAIL.getCode(), "fail", null);
}
public static <T> RetResult<T> fail(String msg) {
return new RetResult<>(RetCode.FAIL.getCode(), msg, null);
}
public static <T> RetResult<T> fail(Integer code, String msg) {
return new RetResult<>(code, msg, null);
}
}
使用JWTtoken过滤器的项目
Springboot项目经常使用spring security+jwt来做权限限制,在这种情况下,我们通过新建filter过滤器来设置traceId,那么在验证token这部分的日志就不会带上traceId,因此我们需要把代码放在jwtFilter中。
来源:
https://www.jianshu.com/p/00ce2ed403b2
猜你喜欢
- 2025-07-28 告别频繁登录!Nuxt3 + TS + Vue3实战:双Token无感刷新方案全解析
- 2025-07-28 SpringBoot实现单点登录(SSO)的4种方案
- 2025-07-28 随机密聊 匿名聊天室程序源码(随机匿名聊天在线)
- 2025-07-28 SpringBoot大文件上传卡死?分块切割术搞定GB级传输,速度飙升!
- 2025-07-28 Java 微服务从源码实战开始 | Gitee 项目推荐
- 2025-07-28 轻量级埋点sdk搭建,便捷更全面(埋点sdk是什么)
- 2025-07-28 Spring Boot 实现文件秒传功能(springboot上传文件到指定文件夹)
- 2025-07-28 项目中不用redis分布式锁,怎么防止用户重复提交?
- 2025-07-28 如何实现PC端网站扫码登录操作?(网页 扫码)
- 2025-07-28 密码加盐加密存储及其登录验证方式
你 发表评论:
欢迎- 最近发表
-
- 告别频繁登录!Nuxt3 + TS + Vue3实战:双Token无感刷新方案全解析
- SpringBoot实现单点登录(SSO)的4种方案
- 随机密聊 匿名聊天室程序源码(随机匿名聊天在线)
- SpringBoot大文件上传卡死?分块切割术搞定GB级传输,速度飙升!
- Java 微服务从源码实战开始 | Gitee 项目推荐
- 轻量级埋点sdk搭建,便捷更全面(埋点sdk是什么)
- Spring Boot 实现文件秒传功能(springboot上传文件到指定文件夹)
- 项目中不用redis分布式锁,怎么防止用户重复提交?
- SpringBoot项目日志打印traceId生成
- 如何实现PC端网站扫码登录操作?(网页 扫码)
- 标签列表
-
- ifneq (61)
- 字符串长度在线 (61)
- googlecloud (64)
- flutterrun (59)
- 系统设计图 (58)
- powershellfor (73)
- messagesource (71)
- plsql64位 (73)
- promise.race (63)
- 2019cad序列号和密钥激活码 (62)
- window.performance (66)
- qt删除文件夹 (72)
- mysqlcaching_sha2_password (64)
- ubuntu升级gcc (58)
- nacos启动失败 (64)
- ssh-add (70)
- yarnnode (62)
- abstractqueuedsynchronizer (64)
- source~/.bashrc没有那个文件或目录 (65)
- springboot整合activiti工作流 (70)
- jmeter插件下载 (61)
- 抓包分析 (60)
- idea创建mavenweb项目 (65)
- qcombobox样式表 (68)
- pastemac (61)
本文暂时没有评论,来添加一个吧(●'◡'●)