前言新項目查日誌太麻煩,多臺機器之間查來查去,還不知道是否是同一個請求的。打印日誌時使用 MDC 在日誌上添加一個 traceId,那這個 traceId 如何跨系統傳遞呢?html
一樣是新項目開發的筆記,由於使用的是分佈式架構,涉及到各個系統之間的交互java
這時候就會遇到一個很常見的問題:git
MDC(Mapped Diagnostic Context)是一個映射,用於存儲運行上下文的特定線程的上下文數據。所以,若是使用log4j進行日誌記錄,則每一個線程均可以擁有本身的MDC,該MDC對整個線程是全局的。屬於該線程的任何代碼均可以輕鬆訪問線程的MDC中存在的值。github
%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 生成一個。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; } }
@Configuration public class WebConfig implements WebMvcConfigurer { @Resource private LogInterceptor logInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(logInterceptor) .addPathPatterns("/**"); } }
由於這邊使用的是 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 能夠經過擴展 Filter 的方式傳遞 traceId服務器
@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); } }
src |-main |-java |-com |-xxx |-XxxFilter.java (實現Filter接口) |-resources |-META-INF |-dubbo |-org.apache.dubbo.rpc.Filter (純文本文件,內容爲:xxx=com.xxx.XxxFilter)
截圖以下:架構
測試結果以下:app
dubbo filter 相關源碼地址在文末
也能夠關注公衆號,發送 traceid 獲取
固然若是小夥伴們有使用 SkyWalking 或者 Elastic APM 也能夠經過如下方式進行注入:dom
<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
%X{trace.id}
配置在 log4j2.xml 文件的 pattern 中雖然有了 traceId 能夠進行全鏈路追蹤查詢日誌,可是畢竟也是在多臺服務器上,爲了提升查詢效率,能夠考慮將日誌彙總到一塊兒。
經常使用的使用方法就是基於 ELK 的日誌系統:
本文主要記錄近期開發過程當中的遇到的一點問題,但願對小夥伴也有所幫助。不足之處,歡迎指正。若是小夥伴有其餘的建議或者觀點歡迎留言討論,共同進步。