日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

發布時間:2024/4/14 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 基于SLF4J MDC机制实现日志的链路追踪 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、打印HTTP request body和response body實現日志跟蹤

request 的inputStream和response 的outputStream默認情況下是只能讀一次, 不可重復讀;這就導致要獲取請求體或者響應體信息時必須要聲明包裹類wrapper; spring為此提供了兩個對應的包裹類ContentCachingRequestWrapper和ContentCachingResponseWrapper使得這兩個流信息可重復讀;

可以利用這兩個類結合spring的WebUtil工具類來實現日志跟蹤:

import lombok.extern.slf4j.Slf4j; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.util.ContentCachingRequestWrapper; import org.springframework.web.util.ContentCachingResponseWrapper; import org.springframework.web.util.WebUtils;import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.Enumeration; import java.util.UUID;/*** @author: liumengbing* @date: 2019/03/12 10:10**/ @Component @Slf4j @WebFilter("/*") public class WebLogFilter extends OncePerRequestFilter {private static Logger log = LoggerFactory.getLogger(WebLogFilter.class);public static final String SPLIT_STRING_M = "=";public static final String SPLIT_STRING_DOT = ", ";@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {//日志tradeMDC.clear();//使用唯一ID標識每次請求MDC.put("trade_id", UUID.randomUUID().toString().replaceAll("-",""));ContentCachingRequestWrapper wrapperRequest = new ContentCachingRequestWrapper(request);ContentCachingResponseWrapper wrapperResponse = new ContentCachingResponseWrapper(response);String urlParams = getRequestParams(request);log.info("request params[{}]", urlParams);filterChain.doFilter(wrapperRequest, wrapperResponse);String requestBodyStr = getRequestBody(wrapperRequest);log.info("request body:{}", requestBodyStr);String responseBodyStr = getResponseBody(wrapperResponse);log.info("response body:{}", responseBodyStr);wrapperResponse.copyBodyToResponse();}/*** 獲取請求地址上的參數* @param request* @return*/public static String getRequestParams(HttpServletRequest request) {StringBuilder sb = new StringBuilder();Enumeration<String> enu = request.getParameterNames();//獲取請求參數while (enu.hasMoreElements()) {String name = enu.nextElement();sb.append(name + SPLIT_STRING_M).append(request.getParameter(name));if(enu.hasMoreElements()) {sb.append(SPLIT_STRING_DOT);}}return sb.toString();}/*** 打印請求參數* @param request*/private String getRequestBody(ContentCachingRequestWrapper request) {ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);if(wrapper != null) {byte[] buf = wrapper.getContentAsByteArray();if(buf.length > 0) {String payload;try {payload = new String(buf, 0, buf.length, wrapper.getCharacterEncoding());} catch (UnsupportedEncodingException e) {payload = "[unknown]";}return payload.replaceAll("\\n","");}}return "";}/*** 打印返回參數* @param response*/private String getResponseBody(ContentCachingResponseWrapper response) {ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);if(wrapper != null) {byte[] buf = wrapper.getContentAsByteArray();if(buf.length > 0) {String payload;try {payload = new String(buf, 0, buf.length, wrapper.getCharacterEncoding());} catch (UnsupportedEncodingException e) {payload = "[unknown]";}return payload;}}return "";}}

在以上代碼中還有一個技術點:使用了slf4j的MDC來跟蹤請求信息,打印完整的請求處理日志;并在日志配置中加上了唯一ID,從而達到日志追蹤的目的。

日志配置文件:

[%X{trade_id}]%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n

輸出的日志信息:

[16fbd9304e23492cab3ccabb6a85d0c0]2019-03-12 10:40:49.127 INFO 13712 --- [nio-8188-exec-1] xxx.xx.xx.filter.WebLogFilter : request params[{}]:account=xxxxx, v_code=056036} [16fbd9304e23492cab3ccabb6a85d0c0]2019-03-12 10:40:49.130 INFO 13712 --- [nio-8188-exec-1] xxx.xx.xx.filter.WebLogFilter : request body:{}:{ "account":"xxxxx", "v_code":"056036"} [16fbd9304e23492cab3ccabb6a85d0c0]2019-03-12 10:40:49.131 INFO 13712 --- [nio-8188-exec-1] xxx.xx.xx.filter.WebLogFilter : response body:{}:{"code":1,"msg":"請輸入正確的驗證碼","data":"","success":false}

二、基于SLF4J MDC機制實現日志的鏈路追蹤

問題引入:

系統上線之后,我們可能經常需要進行線上問題排查,排查問題必須用到的方式就是查看日志,但是在分布式系統中,各種無關日志穿行其中,導致我們沒辦法快速定位用戶在某一次請求中的所有日志。因此,我們可能需要對一個用戶的操作流程進行歸類標記,比如使用線程+時間戳,或者用戶身份標識等方式。這樣的話,我們就可以從大量日志信息中grep出某個用戶的操作流程,或者某個時間的流轉記錄,從而便于我們診斷線上問題。

解決方案:

1.在每次請求的時候,獲取到請求的sessionId,或者自己生成一個偽sessionId,在每次輸出日志的時候將這個sessionId連同日志信息一起輸出。這個方式實現起來非常簡單,但是代碼侵入性強,每次輸出的時候都需要額外多輸出一個參數,重復且工作量大,但是可控粒度高;
2.使用LogBack的MDC機制,在日志模板中加入sessionId,如上面我們使用到的:

[%X{trade_id}]%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n

這種方式工作量小,代碼侵入小,易擴展,但是可控粒度較低。

方案一:

logger.info("sessionId: {}, message: {}", sessionId, "日志信息");

方案二(以springmvc為例):

1、新建一個日志攔截器,攔截所有的請求,在處理請求前將sessionId放到MDC中,處理完請求之后清除MDC中的內容。這就解決了80%的問題;
2、在舊版本中新啟線程時MDC會自動將父線程的MDC內容復制給子線程,因為MDC內部使用的是InheritableThreadLocal,但是因為性能問題在最新的版本中被取消了,所以子線程不會自動獲取父線程MDC的內容。官方建議我們在父線程新建子線程之前調用MDC.getCopyOfContextMap()方法將父線程的MDC內容取出傳給子線程,子線程在執行操作之前先調用MDC.setContextMap()方法將父線程的MDC內容設置到子線程中去。
3、設置日志輸出格式

[%X{trade_id}]%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n

MDC對外提供的方法:

package org.slf4j; public class MDC {// 將一個K-V的鍵值對放到容器,其實是放到當前線程的ThreadLocalMap中public static void put(String key, String val);// 根據key在當前線程的MDC容器中獲取對應的值public static String get(String key);// 根據key移除容器中的值public static void remove(String key);// 清空當前線程的MDC容器public static void clear(); }

MDC實現原理:

Slf4j 的實現原則就是調用底層具體實現類,比如logback,log4j等包;而不會去實現具體的輸出打印等操作。這里使用了裝飾者模式,看源碼就能看出來,所有的方法都是在對mdcAdapter 這個屬性進行操作。所以實現核心是MDCAdapter類。

public interface MDCAdapter {public void put(String key, String val);public String get(String key);public void remove(String key);public void clear();public Map<String, String> getCopyOfContextMap();public void setContextMap(Map<String, String> contextMap); }

MDCAdapter有三個實現類:BasicMDCAdapter、LogbackMDCAdapter,NOPMDCAdapter。其中Logback使用的是LogbackMDCAdapter。通過查看源碼發現它們的底層都是使用ThreadLocal實現的,這里不再具體分析源碼,有興趣的可以自己去看一下。

參考資料:
https://www.jianshu.com/p/afdd31bfbf94
https://www.jianshu.com/p/06b1d35526c2
https://blog.csdn.net/xiaolyuh123/article/details/80560662
https://www.jianshu.com/p/3dca4aeb6edd

總結

以上是生活随笔為你收集整理的基于SLF4J MDC机制实现日志的链路追踪的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。