关于java:全链路追踪体验最简陋TraceId的生成

42次阅读

共计 3105 个字符,预计需要花费 8 分钟才能阅读完成。

对于后端开发来说,排查问题是常有的事件。而排查问题时最罕用的就是看日志,看一次调用中通过了哪些零碎,是那个零碎出问题了。这就须要业务日志中关联调用链的 TraceId 信息,从而在利用呈现问题时,可能通过调用链的 TraceId 疾速关联到业务日志,及时定位剖析、解决问题。

之前从事的公司都有这种链路中间件,当初阿里团体的 eagleeye 鹰眼零碎也是的,接入应用就行了,然而原理齐全不分明。不理解 TraceId 怎么生成的,怎么在零碎之间传递。所以明天先来实现一个最简略的 TraceId

解决方案

  1. 本人生成 traceId 并 put 到 MDC 外面

MDC

MDC(Mapped Diagnostic Context)是一个映射,用于存储运行上下文的特定线程的上下文数据。因而,如果应用 log4j 进行日志记录,则每个线程都能够领有本人的 MDC,该 MDC 对整个线程是全局的。属于该线程的任何代码都能够轻松拜访线程的 MDC 中存在的值。申请时,将 TraceId 放在 header 里,服务方从 header 里读取进去,并在日志上打印即可

如何将 TraceId 放到 MDC 中

1. 日志文件 logback-spring.xml 配置

打印黑白日志,须要在日志格局中加上[%X{TRACE_ID}],变量名 TRACE_ID 是本人定义的

<?xml version="1.0" encoding="UTF-8"?>
<configuration  scan="true" scanPeriod="60 seconds">

    <contextName>logback</contextName>
    <!-- name 的值是变量的名称,value 的值时变量定义的值。通过定义的值会被插入到 logger 上下文中。定义变量后,能够使“${}”来应用变量。-->
    <property name="log.path" value="log" />

    <!-- 黑白日志 -->
    <!-- 黑白日志依赖的渲染类 -->
    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
    <conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
    <conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
    <!-- 黑白日志格局,留神加上 TRACE_ID -->
    <property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:-}){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} [%X{TRACE_ID}] %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>

    <!-- 输入到控制台 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!-- 此日志 appender 是为开发应用,只配置最底级别,控制台输入的日志级别是大于或等于此级别的日志信息 -->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>info</level>
        </filter>
        <encoder>
            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <root level="info">
        <appender-ref ref="CONSOLE" />
    </root>

</configuration>

2. 新增拦截器

拦挡所有申请,从 header 里取出 traceId 而后放到 MDC 中,这样该工程所有地位都能读取到。如果 header 里没有,则本人生成一个,生成规定可参考阿里云帮忙文档: TraceId 和 SpanId 生成规定

@Component
public class LogInterceptor implements HandlerInterceptor {

    private String TRACE_ID = "TRACE_ID";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 如果有下层调用就用下层的 ID
        String traceId = request.getHeader(TRACE_ID);
        if (traceId == null) {traceId = UUID.randomUUID().toString();}

        MDC.put(TRACE_ID, traceId);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
            throws Exception {// Do nothing because of no business}

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        // 调用完结后删除
        MDC.remove(TRACE_ID);
    }
}

3. 注册拦截器

将上一步的日志拦截器注册,并拦挡所有门路的 HTTP 申请,让其失效

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Resource
    private LogInterceptor logInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(logInterceptor)
                .addPathPatterns("/**");
    }
}

4. 测试成果

到这一步就能够试试日志打印 TraceId 的成果了,轻易写一个接口,并打印日志。如下图曾经胜利打印出 Traceid 了

其余计划

下面演示的是最简陋的形式,公司外部个别都会再造一遍轮子,让各个利用接入应用的。前面再写几个开源的 TraceId 生成框架,更深刻的去理解全链路治理

正文完
 0