幾行代碼輕鬆實現跨系統傳遞 traceId,不再用擔憂對不上日誌了!

前言

新項目查日誌太麻煩,多臺機器之間查來查去,還不知道是否是同一個請求的。打印日誌時使用 MDC 在日誌上添加一個 traceId,那這個 traceId 如何跨系統傳遞呢?html

背景

一樣是新項目開發的筆記,由於使用的是分佈式架構,涉及到各個系統之間的交互java

hHJYwn-b0KzBU

這時候就會遇到一個很常見的問題:git

  1. 單個系統是集羣部署,日誌分佈在多臺服務器上;
  2. 多個系統的日誌在多臺機器,可是一次請求,查日誌更是難上加難。

53_b91a8fc194febf0f3d3edf032e6cf78d-dSqBDj

解決方案

  1. 使用 SkyWalking traceid 進行鏈路追蹤;
  2. 使用 Elastic APM 的 trace.id 進行鏈路追蹤;
  3. 本身生成 traceId 並 put 到 MDC 裏面。

MDC

MDC(Mapped Diagnostic Context)是一個映射,用於存儲運行上下文的特定線程的上下文數據。所以,若是使用log4j進行日誌記錄,則每一個線程均可以擁有本身的MDC,該MDC對整個線程是全局的。屬於該線程的任何代碼均可以輕鬆訪問線程的MDC中存在的值。github

如何使用 MDC

  1. log4j2-spring.xml 的日誌格式中添加 %X{traceId} 配置。
<Property name="LOG_PATTERN">
    [%d{yyyy-MM-dd HH:mm:ss.SSS}]-[%t]-[%X{traceId}]-[%-5level]-[%c{36}:%L]-[%m]%n
</Property>
<Property name="LOG_PATTERN_ERROR">
    [%d{yyyy-MM-dd HH:mm:ss.SSS}]-[%t]-[%X{traceId}]-[%-5level]-[%l:%M]-[%m]%n
</Property>

<!-- 省略 -->

<!--這個輸出控制檯的配置-->
<Console name="Console" target="SYSTEM_OUT" follow="true">
    <!--輸出日誌的格式-->
    <PatternLayout charset="UTF-8"  pattern="${LOG_PATTERN}"/>
</Console>
  1. 新增攔截器

攔截全部請求,從 header 中獲取 traceId 而後放到 MDC 中,若是沒有獲取到,則直接用 UUID 生成一個。spring

@Slf4j
@Component
public class LogInterceptor implements HandlerInterceptor {
    
    private static final String TRACE_ID = "traceId";

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception arg3) throws Exception {
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView arg3) throws Exception {
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        String traceId = request.getHeader(TRACE_ID);
        if (StringUtils.isEmpty(traceId)) {
            MDC.put(TRACE_ID, UUID.randomUUID().toString());
        } else {
            MDC.put(TRACE_ID, traceId);
        }


        return true;
    }
    
}
  1. 配置攔截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Resource
    private LogInterceptor logInterceptor;

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

跨服務之間如何傳遞 traceId

  • FeignClient

由於這邊使用的是 FeignClient 進行服務之間的調用,只須要新增請求攔截器便可apache

@Configuration
public class FeignInterceptor implements RequestInterceptor {

    private static final String TRACE_ID = "traceId";

    @Override
    public void apply(RequestTemplate requestTemplate) {

        requestTemplate.header(TRACE_ID, MDC.get(TRACE_ID));

    }
}
  • Dubbo

若是是 Dubbo 能夠經過擴展 Filter 的方式傳遞 traceId服務器

  1. 編寫 filter
@Activate(group = {"provider", "consumer"})
public class TraceIdFilter implements Filter {

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {


        RpcContext rpcContext = RpcContext.getContext();


        String traceId;

        if (rpcContext.isConsumerSide()) {

            traceId = MDC.get("traceId");

            if (traceId == null) {
                traceId = UUID.randomUUID().toString();
            }

            rpcContext.setAttachment("traceId", traceId);

        }

        if (rpcContext.isProviderSide()) {
            traceId = rpcContext.getAttachment("traceId");
            MDC.put("traceId", traceId);
        }

        return invoker.invoke(invocation);
    }
}
  1. 指定 filter
src
 |-main
    |-java
        |-com
            |-xxx
                |-XxxFilter.java (實現Filter接口)
    |-resources
        |-META-INF
            |-dubbo
                |-org.apache.dubbo.rpc.Filter (純文本文件,內容爲:xxx=com.xxx.XxxFilter)

截圖以下:架構

STZ8hr-q8AmQ6

測試結果以下:app

FKroew-oE1qSz

dubbo filter 相關源碼地址在文末
也能夠關注公衆號,發送 traceid 獲取

其餘方式

固然若是小夥伴們有使用 SkyWalking 或者 Elastic APM 也能夠經過如下方式進行注入:dom

  1. SkyWalking
<dependency>
    <groupId>org.apache.skywalking</groupId>
    <artifactId>apm-toolkit-log4j-2.x</artifactId>
    <version>{project.release.version}</version>
</dependency

而後將 [%traceId] 配置在 log4j2.xml 文件的 pattern 中便可

  1. Elastic APM

    1. 在啓動時指定 enable_log_correlation 爲 true
    2. %X{trace.id} 配置在 log4j2.xml 文件的 pattern 中

擴展

統一日誌採集

雖然有了 traceId 能夠進行全鏈路追蹤查詢日誌,可是畢竟也是在多臺服務器上,爲了提升查詢效率,能夠考慮將日誌彙總到一塊兒。

經常使用的使用方法就是基於 ELK 的日誌系統:

  1. 使用 filebeat 採集日誌報送到 logstash
  2. logstash 進行分詞過濾等處理,輸出到 Elasticsearch
  3. 使用 Kinbana 或者本身開發的可視化工具從 Elasticsearch 查詢日誌

aBn5LF-utuTLx

結束語

本文主要記錄近期開發過程當中的遇到的一點問題,但願對小夥伴也有所幫助。不足之處,歡迎指正。若是小夥伴有其餘的建議或者觀點歡迎留言討論,共同進步。

相關資料

  1. Log4j 2 API:https://logging.apache.org/lo...
  2. SkyWalking:https://github.com/apache/sky...
  3. Elastic APM:https://www.elastic.co/guide/...
  4. Dubbo filter:http://dubbo.apache.org/zh-cn...
  5. 本文 Dubbo filter demo:https://github.com/liuzhihang...
相關文章
相關標籤/搜索