詳解Java中日志跟蹤的簡單實現(xiàn)
一、前言
在編碼過程中,常常需要寫打印日志語句,我們期望的是同一個業(yè)務(wù)的日志都在一塊,在出問題的時候好根據(jù)日志來排查問題。而現(xiàn)實是在應(yīng)用運行中,日志的輸出常常來自不同線程,甚至是在不同微服務(wù)中,各種日志記錄往往彼此穿插,很難串起來。所以往往在日志中手動增加一些關(guān)鍵字,來對接口的調(diào)用鏈路來進行跟蹤。但這種手動增加關(guān)鍵字或唯一標(biāo)識的做法在微服務(wù)場景下,很難在上下游應(yīng)用的開發(fā)人員的編碼風(fēng)格形成統(tǒng)一的規(guī)范,并且手動編寫也很難稱得上優(yōu)雅。
二、MDC介紹
MDC?(Mapped Diagnostic Context,映射調(diào)試上下文)是 log4j? 、logback及l(fā)og4j2? 提供的一種方便在多線程條件下記錄日志的功能。MDC? 可以看成是一個與當(dāng)前線程綁定的哈希表,MDC 中包含的內(nèi)容可以被同一線程中執(zhí)行的代碼所訪問。
MDC中的鍵值對是可以直接被日志框架所使用(即“打印”)的,只需要配置相應(yīng)日志pattern。例如pattern如下:
%d{HH:mm:ss.SSS} [%thread] [%X{TraceId}] %-5level %logger{50} - %msg%n
代碼如下:
public class MDCTest { private static final Logger log = LoggerFactory.getLogger(MDCTest.class); @Test void test() { MDC.put("TraceId", "123456789"); log.info("hello {}", "world"); } }
此時控制臺將輸出:
21:16:04.342 [main] [123456789] INFO com.nk.MDCTest - hello world
三、實現(xiàn)方案
1、基本思路
修改日志pattern,并在業(yè)務(wù)開始的時候?qū)race id放入到MDC,在業(yè)務(wù)結(jié)束時去除MDC的trace id。這樣的好處便是代碼簡潔,不需要手動寫trace id,日志風(fēng)格也能保持統(tǒng)一。
業(yè)務(wù)開始的時機一般是應(yīng)用收到HTTP請求,所以可以用Filter或SpringMVC的Interceptor來對MDC中trace id進行初始化和清除。在Dubbo調(diào)用的時候也可以通過類似功能的Filter來對MDC中trace id進行操作,從而達(dá)到trace id傳遞的作用。
2、實現(xiàn)(以SpringBoot為例)
2.1 修改log pattern
在SpringBoot中,直接修改application.properties即可:
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{TraceId}] %-5level %logger{50} - %msg%n
重點在于%X{TraceId}?,其中TraceId?需要作為key出現(xiàn)在MDC里。
2.2 業(yè)務(wù)開始
TraceId工具類,封裝MDC關(guān)于trace id的基礎(chǔ)操作:
public final class TraceIdUtil { private static final String TRACE_ID_KEY = "TraceId"; private TraceIdUtil() { } public static void putIfAbsent() { if (StrUtil.isBlank(get())) { put(UUID.randomUUID().toString()); } } public static void remove() { if (get() != null) { MDC.remove(TRACE_ID_KEY); } } public static String get() { return MDC.get(TRACE_ID_KEY); } public static void put(String traceId) { MDC.put(TRACE_ID_KEY, traceId); } }
Filter?方式和Interceptor二選其一既可,其基本思想是一樣的。
Filter方式
@Component public class LogFilter implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { TraceIdUtil.putIfAbsent();//生成trace id放入MDC中 try { filterChain.doFilter(servletRequest, servletResponse); } finally { TraceIdUtil.remove();//移除MDC中的trace id } } }
Interceptor
@Configuration public class LogInterceptor implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new AsyncHandlerInterceptor() { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { TraceIdUtil.putIfAbsent();//生成trace id放入MDC中 return AsyncHandlerInterceptor.super.preHandle(request, response, handler); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { TraceIdUtil.remove();//移除MDC中的trace id AsyncHandlerInterceptor.super.afterCompletion(request, response, handler, ex); } }); WebMvcConfigurer.super.addInterceptors(registry); } }
2.3 業(yè)務(wù)中使用
正常使用logger,無需關(guān)心trace id。例如:
@RestController @RequestMapping("/api/user") @Slf4j public class UserController { @Autowired private UserService userService; @GetMapping("/{userId}") public UserDto queryUser(@PathVariable Long userId) { log.info("query user by id:{}", userId); UserDto user = userService.query(userId); log.info("query user result:{}", user); return user; } }
請求該接口將輸出如下的日志樣式:
2022-04-05 09:40:17.638 [http-nio-8080-exec-1] [a02b13d81c224e49956afd4efbb85ca8] INFO com.nk.webapp.controller.UserController - ready to query user by id:1
2022-04-05 09:40:17.670 [http-nio-8080-exec-1] [a02b13d81c224e49956afd4efbb85ca8] INFO com.nk.webapp.controller.UserController - query result:UserDto(userId=1, username=zhang3, age=23, email=abc@example.com)
四、總結(jié)
日志鏈路的跟蹤核心是使用MDC作為trace id載體,在業(yè)務(wù)開始階段一般通過攔截器就生成trace id并放入到MDC中,并根據(jù)MDC的相關(guān)特性將trace id投射到日志文本中,從而實現(xiàn)在同一個業(yè)務(wù)調(diào)用鏈路中的日志具有唯一標(biāo)識。
以上就是詳解Java中日志跟蹤的簡單實現(xiàn)的詳細(xì)內(nèi)容,更多關(guān)于Java 日志跟蹤的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解spring boot 使用application.properties 進行外部配置
這篇文章主要介紹了詳解spring boot 使用application.properties 進行外部配置,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-03-03Java?Controller實現(xiàn)參數(shù)驗證與統(tǒng)一異常處理流程詳細(xì)講解
Controller是Spring接受并處理網(wǎng)頁請求的組件,是整個應(yīng)用的入口,因此學(xué)會Controller的常用注解對理解一個應(yīng)用是重中之重。SpringBoot的Controller中經(jīng)常會用到注解@Controller、@RestController、@RequestMapping、@RequestBody等2023-01-01SpringBoot集成nacos動態(tài)刷新數(shù)據(jù)源的實現(xiàn)示例
這篇文章主要介紹了SpringBoot集成nacos動態(tài)刷新數(shù)據(jù)源的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12解決mybatis-plus使用jdk8的LocalDateTime 查詢時報錯的方法
這篇文章主要介紹了解決mybatis-plus使用jdk8的LocalDateTime 查詢時報錯的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08SpringBoot?基于?MongoTemplate?的工具類過程詳解
MongoDB是一個高性能,開源,無模式的文檔型數(shù)據(jù)庫,是當(dāng)前NoSql數(shù)據(jù)庫中比較熱門的一種,這篇文章主要介紹了SpringBoot基于MongoTemplate的工具類,需要的朋友可以參考下2023-09-09