前言
新項目查日志太麻煩,多台機器之間查來查去,還不知道是不是同一個請求的。打印日志時使用 MDC 在日志上添加一個 traceId,那這個 traceId 如何跨系統傳遞呢?
背景
同樣是新項目開發的筆記,因為使用的是分布式架構,涉及到各個系統之間的交互
這時候就會遇到一個很常見的問題:
- 單個系統是集群部署,日志分布在多台服務器上;
- 多個系統的日志在多台機器,但是一次請求,查日志更是難上加難。
解決方案
- 使用 SkyWalking traceid 進行鏈路追蹤;
- 使用 Elastic APM 的 trace.id 進行鏈路追蹤;
- 自己生成 traceId 並 put 到 MDC 里面。
MDC
MDC(Mapped Diagnostic Context)是一個映射,用於存儲運行上下文的特定線程的上下文數據。因此,如果使用log4j進行日志記錄,則每個線程都可以擁有自己的MDC,該MDC對整個線程是全局的。屬於該線程的任何代碼都可以輕松訪問線程的MDC中存在的值。
如何使用 MDC
- 在 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>
- 新增攔截器
攔截所有請求,從 header 中獲取 traceId 然后放到 MDC 中,如果沒有獲取到,則直接用 UUID 生成一個。
@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;
}
}
- 配置攔截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Resource
private LogInterceptor logInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(logInterceptor)
.addPathPatterns("/**");
}
}
跨服務之間如何傳遞 traceId
- FeignClient
因為這邊使用的是 FeignClient 進行服務之間的調用,只需要新增請求攔截器即可
@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
- 編寫 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);
}
}
- 指定 filter
src
|-main
|-java
|-com
|-xxx
|-XxxFilter.java (實現Filter接口)
|-resources
|-META-INF
|-dubbo
|-org.apache.dubbo.rpc.Filter (純文本文件,內容為:xxx=com.xxx.XxxFilter)
截圖如下:
測試結果如下:
dubbo filter 相關源碼地址在文末
也可以關注公眾號,發送 traceid 獲取
其他方式
當然如果小伙伴們有使用 SkyWalking 或者 Elastic APM 也可以通過以下方式進行注入:
- SkyWalking
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-log4j-2.x</artifactId>
<version>{project.release.version}</version>
</dependency
然后將 [%traceId]
配置在 log4j2.xml 文件的 pattern 中即可
-
Elastic APM
- 在啟動時指定 enable_log_correlation 為 true
- 將
%X{trace.id}
配置在 log4j2.xml 文件的 pattern 中
擴展
統一日志采集
雖然有了 traceId 可以進行全鏈路追蹤查詢日志,但是畢竟也是在多台服務器上,為了提高查詢效率,可以考慮將日志匯總到一起。
常用的使用方法就是基於 ELK 的日志系統:
- 使用 filebeat 采集日志報送到 logstash
- logstash 進行分詞過濾等處理,輸出到 Elasticsearch
- 使用 Kinbana 或者自己開發的可視化工具從 Elasticsearch 查詢日志
結束語
本文主要記錄近期開發過程中的遇到的一點問題,希望對小伙伴也有所幫助。不足之處,歡迎指正。如果小伙伴有其他的建議或者觀點歡迎留言討論,共同進步。
相關資料
- Log4j 2 API:https://logging.apache.org/log4j/2.x/manual/thread-context.html
- SkyWalking:https://github.com/apache/skywalking/tree/master/docs/en/setup/service-agent/java-agent
- Elastic APM:https://www.elastic.co/guide/en/apm/agent/java/current/log-correlation.html
- Dubbo filter:http://dubbo.apache.org/zh-cn/docs/dev/impls/filter.html
- 本文 Dubbo filter demo:https://github.com/liuzhihang/trace