美文网首页
为SpringBoot的日志扩展链路追踪能力

为SpringBoot的日志扩展链路追踪能力

作者: 吃竹子的程序熊 | 来源:发表于2019-12-09 19:50 被阅读0次

在单体SpringBoot项目中,虽然没有复杂的微服务调用,但是查看日志依然是一件很头痛的事情,当我需要要梳理出某一个接口调用过程中产生的所有日志,往往需要根据线程ID时间戳来筛选出我们需要的日志数据。

针对这个问题,我产生了一个念头,为每次接口调用生成一个TraceId用于追踪本次调用过程中产生的日志。

基本思路是,借助于Spring的HandlerInterceptor接口,实现对每次调用的前后处理,在调用前生成一个TraceId存放到ThreadLocal中,在调用完成后,清理该ThreadLocal

在最初的实现版本中,在日志中记录TraceId的工作需要手动完成。

后来想到了LogbackConverter,于是就优化了实现,将其统一加入到日志中,不再污染日志代码。

TraceId的生成相对比较简单,直接使用了UUID

在代码实现上,TraceContextHolder用于维护每个线程的TraceId

import org.springframework.util.StringUtils;

import java.util.UUID;

/**
 * 用于链路追踪
 * @author HanQi [Jpanda@aliyun.com]
 * @version 1.0
 * @since 2019/12/6 10:40
 */
public class TraceContextHolder {
    private static final ThreadLocal<String> TRACE_INFO= ThreadLocal.withInitial(() -> UUID.randomUUID().toString().replaceAll("-",""));

    public static void init(){
        if (StringUtils.isEmpty(TRACE_INFO.get())){
            TRACE_INFO.set(UUID.randomUUID().toString().replaceAll("-",""));
        }
    }

    public static String getTrace(){
        return  TRACE_INFO.get();
    }

    public static void  clear(){
        TRACE_INFO.remove();
    }
}

TraceHandlerInterceptor负责为每次请求生成TraceId和在请求玩抽清理TraceId

/**
 * 便于日志追踪
 * @author HanQi [Jpanda@aliyun.com]
 * @version 1.0
 * @since 2019/12/6 10:37
 */
public class TraceHandlerInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        TraceContextHolder.init();
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        TraceContextHolder.clear();
    }
}

WebMvcConfiguration实现了WebMvcConfigurer接口,负责完成拦截器的注册工作。

@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
  
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 链路追踪
        registry.addInterceptor(new TraceHandlerInterceptor());
    }
}

上面的代码,已经足够支撑我们使用简单的链路追踪能力了。

log.info("TraceId:{},实际日志信息", TraceContextHolder.getTrace());

但是这种方式还是不够友好,需要污染我们的日志记录代码,不仅增加了代码量,连后期的维护也变得复杂,比如,当我们需要移除或者替换链路追踪功能时,需要调整大量的代码,或者冗余无效代码。

因此在这个基础上,借助于logbackConverter转换器功能,通过自定义占位符的解析来进一步完成链路追踪的能力。

TraceMessageConverter实现了ClassicConverter接口,作用是在当前日志中追加TraceId的输出能力。

import ch.qos.logback.classic.pattern.ClassicConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import com.xiling.configuration.trace.TraceContextHolder;

/**
 * %trace 占位符的解析器,作用是获取当前TraceId
 *
 * @author HanQi [Jpanda@aliyun.com]
 * @version 1.0
 * @since 2019/12/7 11:32
 */
public class TraceMessageConverter extends ClassicConverter {
    @Override
    public String convert(ILoggingEvent event) {
        return TraceContextHolder.getTrace();
    }
}

修改logback的配置文件,注册TraceMessageConverterlogback中,并告诉logback``TraceMessageConverter用于解析pattern表达式中%trace占位符。

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
 <conversionRule conversionWord="trace" converterClass="完整包名.TraceMessageConverter"/>
</configuration>

接下来我们只需要在我们的pattern表达式中添加%trace参数即可。

    <!-- ch.qos.logback.core.ConsoleAppender 控制台输出 -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>[%-5level] %d{HH:mm:ss.SSS} [%thread] [%X{X-B3-TraceId}] [%trace] %logger{36} - %msg%n
            </pattern>
        </encoder>
    </appender>

一个完整的logback配置

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
    <property name="APP_NAME" value="APP_NAME"/>
    <property name="LOG_HOME" value="/data/logs/${APP_NAME}"/>
    <property name="ENCODER_PATTERN"
              value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%10.10thread] [%X{X-B3-TraceId}] %logger{20} - %msg%n"/>
    <contextName>${APP_NAME}</contextName>
    <conversionRule conversionWord="trace" converterClass="完整包名.TraceMessageConverter"/>


    <appender name="FILE"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_HOME}/${APP_NAME}.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}/backup/${APP_NAME}.%d{yyyy-MM-dd}.log
            </fileNamePattern>
            <maxHistory>100</maxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${ENCODER_PATTERN}</pattern>
        </encoder>
    </appender>

    <appender name="ERROR_FILE"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_HOME}/error.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}/backup/error.%d{yyyy-MM-dd}.log
            </fileNamePattern>
            <maxHistory>100</maxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${ENCODER_PATTERN}</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
    </appender>

    <!-- ch.qos.logback.core.ConsoleAppender 控制台输出 -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>[%-5level] %d{HH:mm:ss.SSS} [%thread] [%X{X-B3-TraceId}] [%trace] %logger{36} - %msg%n
            </pattern>
        </encoder>
    </appender>


    <root>
        <level value="info"/>
        <appender-ref ref="console"/>
        <appender-ref ref="FILE"/>
        <appender-ref ref="ERROR_FILE"/>
    </root>

</configuration>

接下来,当我们在代码中正常使用logback功能即可在日志中获取traceId

log.info("简单的输出");

实际日志为:

[INFO ] 17:43:50.097 [http-nio-8080-exec-7] [] [cd21d506088649809000a3de4c66650e] c.j.t.TraceShower - 简单的输出

日志中的cd21d506088649809000a3de4c66650e就是我们生成的TraceId

这样,当我们需要定位具体的接口调用日志时,只需要使用类似于awk '{print}' log.file|grep TraceId的方式即可。

这只是一个简单的链路追踪的代码,有很多缺陷,比如,不支持调用中的多线程,虽如此,但也简单满足了基本的使用。

如果需要解决多线程的链路追踪问题,可以参考TransmittableThreadLocal

相关文章

  • 为SpringBoot的日志扩展链路追踪能力

    在单体SpringBoot项目中,虽然没有复杂的微服务调用,但是查看日志依然是一件很头痛的事情,当我需要要梳理出某...

  • Kubernetes深度实践(七)

    CICD搭建完成之后又迎来新的问题,链路追踪、日志、监控告警、在线调试、服务更新策略,先从链路追踪说起。 链路追踪...

  • 14. SpringCloud之Sleuth+Zipkin链路追

    1、链路追踪简介 其实链路追踪就是日志追踪,微服务下日志跟踪,微服务系统之间的调用变得非常复杂,往往一个功能的调用...

  • 基于SLF4J MDC机制实现日志的链路追踪

    基于SLF4J MDC机制实现日志的链路追踪

  • 日更十五

    Apache SkyWalking是一款云原生的APM(应用性能管理)系统,包含了事件日志、链路追踪、指标监控能力...

  • Spring Cloud进阶

    分布式链路追踪 场景 动态展示服务调用链路 分析服务调用链路的瓶颈 服务链路故障发现 核心思想 本质: 记录日志 ...

  • 彻底把「链路追踪」整明白

    本篇主要内容 本篇知识点: 链路追踪基本原理 如何在项目中轻松加入链路追踪中间件 如何使用链路追踪排查问题 一、为...

  • SkyWalking + SpringBoot 链路追踪

    准备工作 系统:Centos7.6 / 64 位elasticsearch[https://artifacts.e...

  • 2021-07-13 基于aws链路追踪方案

    链路追踪方案 背景 目前日志使用aop+logback生成。存在问题1.高并发下日志大量生成以至于无法确认哪条日志...

  • ELK

    为什么选择ELK日志管理系统 1 基于日志的问题排查,提高故障处理效率,支持全链路追踪技术。2 监控和预警3 关联...

网友评论

      本文标题:为SpringBoot的日志扩展链路追踪能力

      本文链接:https://www.haomeiwen.com/subject/wkyigctx.html