Springboot+MDC+traceId日志中打印唯一traceId

目录
  • 1. 为什么需要这个traceId
  • 2.通过MDC设置traceId
    • 2.1 使用filter过滤器设置traceId
    • 2.2 使用JWT token过滤器的项目
    • 2.3 使用Interceptor拦截器设置traceId
  • 3.logback.xml中配置traceId
  • 4.补充异步方法带入上下文的traceId
  • 5.在接口放回中,增加traceId返回

先看一张图:

有同学问:日志中[]中类似uuid的这个traceId是怎么实现的,这边文章就介绍下如何在springboot工程下用MDC实现日志文件中打印traceId。

1. 为什么需要这个traceId

我们在定位问题的时候需要去日志中查找对应的位置,当我们一个接口的请求同用唯一的一个traceId,那我们只需要知道这个traceId,使用 grep ‘traceId' xxx.log 语句就能准确的定位到目标日志。在这边文章会介绍如何去设置这个traceId,而后如何在接口返回这个traceId。

#接口返回:
{
    "code": "0",
    "dealStatus": "1",
    "TRACE_ID": "a10e6e8d-9953-4de9-b145-32eee6aa5562"
}
#查询日志
 grep 'a10e6e8d-9953-4de9-b145-32eee6aa5562' xxxx.log

2.通过MDC设置traceId

笔者目前遇到的项目,可以有三种情况去设置traceId。先简单的介绍MDC

 #MDC定义
 Mapped Diagnostic Context,即:映射诊断环境。
 MDC是 log4j 和 logback 提供的一种方便在多线程条件下记录日志的功能。
 MDC 可以看成是一个与当前线程绑定的哈希表,可以往其中添加键值对。
 #MDC的使用方法
 向MDC中设置值:MDC.put(key, value);
 从MDC中取值:MDC.get(key);
 将MDC中内容打印到日志中:%X{key}

2.1 使用filter过滤器设置traceId

新建一个过滤器,实现Filter,重写init,doFilter,destroy方法,设置traceId放在doFilter中,在destroy中调用MDC.clear()方法。

@Slf4j
@WebFilter(filterName = "traceIdFilter",urlPatterns = "/*")
public class traceIdFilter implements Filter {

	/**
     * 日志跟踪标识
     */
    private static final String TRACE_ID = "TRACE_ID";

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException {
        MDC.put(TRACE_ID, UUID.randomUUID().toString());
        filterChain.doFilter(request, servletResponse);
    }

    @Override
    public void destroy() {
    	MDC.clear();
    }
}

2.2 使用JWT token过滤器的项目

springboot项目经常使用spring security+jwt来做权限限制,在这种情况下,我们通过新建filter过滤器来设置traceId,那么在验证token这部分的日志就不会带上traceId,因此我们需要把代码放在jwtFilter中,如图:

/**
 * token过滤器 验证token有效性
 *
 * @author china
 */
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    @Autowired
    private TokenService tokenService;

    /**
     * 日志跟踪标识
     */
    private static final String TRACE_ID = "TRACE_ID";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {
        MDC.put(TRACE_ID, UUID.randomUUID().toString());
        LoginUser loginUser = tokenService.getLoginUser(request);
        if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication())) {
            tokenService.verifyToken(loginUser);
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        }
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        MDC.clear();
    }
}

2.3 使用Interceptor拦截器设置traceId

定义一个拦截器,重写preHandle方法,在方法中通过MDC设置traceId

/**
 * MDC设置traceId拦截器
 *
 * @author china
 */
@Component
public abstract class TraceIdInterceptor extends HandlerInterceptorAdapter {
    private static final String UNIQUE_ID = "TRACE_ID";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        MDC.put(UNIQUE_ID, UUID.randomUUID().toString());
        return true;
    }

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

3.logback.xml中配置traceId

与之前的相比只是添加了[%X{TRACE_ID}], [%X{***}]是一个模板,中间属性名是我们使用MDC put进去的。

#之前
<property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
#增加traceId后
<property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - [%X{TRACE_ID}] - %msg%n" />

4.补充异步方法带入上下文的traceId

异步方法会开启一个新线程,我们想要是异步方法和主线程共用同一个traceId,首先先新建一个任务适配器MdcTaskDecorator,如图:

public class MdcTaskDecorator implements TaskDecorator
    /**
     * 使异步线程池获得主线程的上下文
     * @param runnable
     * @return
     */
    @Override
    public Runnable decorate(Runnable runnable) {
        Map<String,String> map = MDC.getCopyOfContextMap();
        return () -> {
            try{
                MDC.setContextMap(map);
                runnable.run();
            } finally {
                MDC.clear();
            }
        };
    }
}

然后,在线程池配置中增加executor.setTaskDecorator(new MdcTaskDecorator())的设置

 /**
 * 线程池配置
 *
 * @author china
 **/
@EnableAsync
@Configuration
public class ThreadPoolConfig {
    private int corePoolSize = 50;
    private int maxPoolSize = 200;
    private int queueCapacity = 1000;
    private int keepAliveSeconds = 300;

    @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开启异步方法即可

    @Async("threadPoolTaskExecutor")
    void testSyncMethod();

5.在接口放回中,增加traceId返回

在笔者项目中,接口返回都使用了一个叫AjaxResult自定义类来包装,所以只需要把这个类的构造器中增加traceId返回即可,相对简单。

   /**
     * 日志跟踪标识
     */
    private static final String TRACE_ID = "TRACE_ID";

    /**
     * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。
     */
    public AjaxResult() {
        super.put(TRACE_ID, MDC.get(TRACE_ID));
    }

    /**
     * 初始化一个新创建的 AjaxResult 对象
     *
     * @param code 状态码
     * @param msg  返回内容
     */
    public AjaxResult(int code, String msg) {
        super.put(CODE_TAG, code);
        super.put(MSG_TAG, msg);
        super.put(TRACE_ID, MDC.get(TRACE_ID));
    }

    /**
     * 初始化一个新创建的 AjaxResult 对象
     *
     * @param code 状态码
     * @param msg  返回内容
     * @param data 数据对象
     */
    public AjaxResult(int code, String msg, Object data) {
        super.put(CODE_TAG, code);
        super.put(MSG_TAG, msg);
        super.put(TRACE_ID, MDC.get(TRACE_ID));
        if (StringUtils.isNotNull(data)) {
            super.put(DATA_TAG, data);
        }
    }

以上就是Springboot+MDC+traceId日志中打印唯一traceId的详细内容,更多关于Springboot 打印唯一traceId的资料请关注我们其它相关文章!

(0)

相关推荐

  • Spring Boot配置AOP打印日志的全过程

    前言 在项目开发中,日志系统是必不可少的,用AOP在Web的请求做入参和出参的参数打印,同时对异常进行日志打印,避免重复的手写日志,完整案例见文末源码. 一.Spring AOP AOP(Aspect-Oriented Programming,面向切面编程),它利用一种"横切"的技术,将那些多个类的共同行为封装到一个可重用的模块.便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性. AOP中有以下概念: Aspect(切面):声明类似于Java中的类声明,在

  • springboot配置aop切面日志打印过程解析

    这篇文章主要介绍了springboot配置aop切面日志打印过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一.SpringBoot Aop说明 1. Aop AOP(Aspect-Oriented Programming,面向切面编程),它利用一种"横切"的技术,将那些多个类的共同行为封装到一个可重用的模块.便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性. 2. AOP相关概念: Aspect

  • springboot+mybatis配置控制台打印sql日志的方法

    此处项目环境为简单的springboot+mybatis环境.可查看到上一篇文章搭建的简单springboot+mybatis的项目 想要控制台打印sql日志. 只需要在resources目录下添加logback.xml文件即可 logback内容如下.直接复制即可 <?xml version="1.0" encoding="UTF-8"?> <configuration debug="false"> <!--定义日

  • Spring Boot如何通过自定义注解实现日志打印详解

    前言 在我们日常的开发过程中通过打印详细的日志信息能够帮助我们很好地去发现开发过程中可能出现的Bug,特别是在开发Controller层的接口时,我们一般会打印出Request请求参数和Response响应结果,但是如果这些打印日志的代码相对而言还是比较重复的,那么我们可以通过什么样的方式来简化日志打印的代码呢? SpringBoot 通过自定义注解实现权限检查可参考我的博客:SpringBoot 通过自定义注解实现权限检查 正文 Spring AOP Spring AOP 即面向切面,是对OO

  • spring boot使用logback日志级别打印控制操作

    因为公司业务需要,需要把性能日志和业务日志分开打印,用elk收集处理,所以需要对不同的业务的日志,打印到不同文件. 使用的是spring boot自带的logback. 首先在yml文件配置logback.xml文件,默认会从resources下找logback.xml文件,找不到会从yml文件中找logging.config下的指定文件. logging: level: DEBUG config: classpath:logback.xml logback.xml是logback的配置文件,可

  • springboot log4j2不能打印框架错误日志的解决方案

    springboot log4j2为什么不能打印框架错误日志 问题 使用springboot时老的框架使用的是log4j2,为了兼容不能够使用logback,按照网上的教程导入了log4j2但是不能够输入框架错误日志,例如bean命名重复,springmvc url映射重复,这些都是开发中所需要的友好提示 解决方案 怎么出现的问题,就不详细描述了,原因是我引入的log42是散件不是基于springboot集成配置的,导致只能够输出基本信息,原理我会在 一个月后左右探究完毕补充. 下面我说说怎么正

  • 使用spring boot通过自定义注解打印所需日志

    spring boot自定义注解打印日志 在实际项目中可能需要监控每个接口的请求时间以及请求参数等相关信息,那么此时我们想到的就是两种实现方式,一种是通过拦截器实现,另一种则通过AOP自定义注解实现. 本文介绍自定义注解实现方式 自定义注解,四个元注解这次就不解释了. @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface WebLog { /** * 日志信息描述 */ String d

  • SpringBoot项目的logback日志配置(包括打印mybatis的sql语句)

    关于logback日志的详解见这位仁兄的博客:Spring Boot-日志配置(超详细) 我在这就开门见山直接介绍我们项目日志的配置使用吧!~ 1.基本介绍 默认情况下,Spring Boot项目就会用Logback来记录日志,并用INFO级别输出到控制台.如下图: 实际开发中我们不需要直接添加logback日志依赖. 你会发现 spring-boot-starter 其中包含了 spring-boot-starter-logging,该依赖内容就是 Spring Boot 默认的日志框架 lo

  • Springboot+MDC+traceId日志中打印唯一traceId

    目录 1. 为什么需要这个traceId 2.通过MDC设置traceId 2.1 使用filter过滤器设置traceId 2.2 使用JWT token过滤器的项目 2.3 使用Interceptor拦截器设置traceId 3.logback.xml中配置traceId 4.补充异步方法带入上下文的traceId 5.在接口放回中,增加traceId返回 先看一张图: 有同学问:日志中[]中类似uuid的这个traceId是怎么实现的,这边文章就介绍下如何在springboot工程下用MD

  • log4j配置失效日志中打印Debug信息问题

    目录 log4j配置失效日志中打印Debug信息 去除依赖的方法 log4j日志的配置--Debug log4j配置失效日志中打印Debug信息 最近发布项目的时候发现控制台打印的日志较往常多了很多,仔细一看,debug和info信息也赫然在列,打开log4j.xml配置文件看一下: <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> <log4j:configuration xmlns:log4j="http

  • java多线程使用mdc追踪日志方式

    目录 多线程使用mdc追踪日志 背景 解决方案 实现 参考 多线程日志追踪 1.问题描述 2. 代理实现日志追踪 多线程使用mdc追踪日志 背景 多线程情况下,子线程的sl4j打印日志缺少traceId等信息,导致定位问题不方便 解决方案 打印日志时添加用户ID.trackId等信息,缺点是每个日志都要手动添加 使用mdc直接拷贝父线程值 实现 // 新建线程时: Map<String, String> mdcContextMap = MDC.getCopyOfContextMap() //

  • 使用log4j MDC实现日志追踪

    目录 log4j MDC实现日志追踪 1.新建线程处理类 ThreadContext 2.添加工具类TraceUtil 3.添加ContextFilter 4.在webConfiguriation注册filter 5.修改log4j日志配置文件,设置日志traceId log4j2实现日志跟踪 日志跟踪 我们可以通过过滤器实现以上的功能 log4j MDC实现日志追踪 MDC 中包含的可以被同一线程中执行的代码所访问内容.当前线程的子线程会继承其父线程中的 MDC 的内容.记录日志时,只需要从

  • Springboot整合log4j2日志全解总结

    在项目推进中,如果说第一件事是搭Spring框架的话,那么第二件事情就是在Sring基础上搭建日志框架,我想很多人都知道日志对于一个项目的重要性,尤其是线上Web项目,因为日志可能是我们了解应用如何执行的唯一方式. 在18年大环境下,更多的企业使用Springboot和Springcloud来搭建他们的企业微服务项目 ,此篇文章是博主在实践中用Springboot整合log4j2日志的总结. 常用日志框架 java.util.logging:是JDK在1.4版本中引入的Java原生日志框架 Lo

  • Python 根据日志级别打印不同颜色的日志的方法示例

    本文介绍了Python 根据日志级别打印不同颜色的日志的方法示例,分享给大家,具体如下: # -*-coding:UTF-8-*- import logging import os import time class logger(object): """ 终端打印不同颜色的日志,在pycharm中如果强行规定了日志的颜色, 这个方法不会起作用, 但是 对于终端,这个方法是可以打印不同颜色的日志的. """ #在这里定义StreamHandler

  • SpringBoot整合log4j2日志的实现

    关于日志级别 共有8个级别,按照从低到高为:All < Trace < Debug < Info < Warn < Error < Fatal < OFF. All: 最低等级的,用于打开所有日志记录. Trace: 是追踪,就是程序推进以下,你就可以写个trace输出,所以trace应该会特别多,不过没关系,我们可以设置最低日志级别不让他输出. Debug: 指出细粒度信息事件对调试应用程序是非常有帮助的. Info: 消息在粗粒度级别上突出强调应用程序的运行过

  • SpringBoot动态修改日志级别的操作

    前言 为了减少日志频繁打印带来的性能影响,线上环境设置的日志级别一般都相对较高.而当出现生产问题需要排查的时候,可能需要适当降低日志级别(例如DEBUG)来打印更多的日志信息帮助定位问题. 传统的做法一般是: 1.配置里修改日志级别 2.重启应用 3.问题复现查看报错日志排查问题 这个过程需要重启应用,比较麻烦,效率较低,而且针对大型在线项目,不可能随便停机重启.那么有没有一种方式在不重启应用的情况下实现动态修改日志级别呢? 下面,让老万教你如何通过SpringBoot的actuator组件来实

  • 在日志中记录Java异常信息的正确姿势分享

    目录 日志中记录Java异常信息 遇到的问题 原因分析 正确的做法 java异常在控制台和日志里面的打印记录 1.e.printStackTrace()打印在哪里 2.e.printStackTrace()打印的内容是什么 3.如果将e.printStackTrace()的信息打印在日志里应该怎么做呢? 日志中记录Java异常信息 遇到的问题 今天遇到一个线上的BUG,在执行表单提交时失败,但是从程序日志中看不到任何异常信息. 在Review源代码时发现,当catch到异常时只是输出了e.get

随机推荐