新聞中心
一、前言
在編碼過(guò)程中,常常需要寫(xiě)打印日志語(yǔ)句,我們期望的是同一個(gè)業(yè)務(wù)的日志都在一塊,在出問(wèn)題的時(shí)候好根據(jù)日志來(lái)排查問(wèn)題。而現(xiàn)實(shí)是在應(yīng)用運(yùn)行中,日志的輸出常常來(lái)自不同線(xiàn)程,甚至是在不同微服務(wù)中,各種日志記錄往往彼此穿插,很難串起來(lái)。所以往往在日志中手動(dòng)增加一些關(guān)鍵字,來(lái)對(duì)接口的調(diào)用鏈路來(lái)進(jìn)行跟蹤。但這種手動(dòng)增加關(guān)鍵字或唯一標(biāo)識(shí)的做法在微服務(wù)場(chǎng)景下,很難在上下游應(yīng)用的開(kāi)發(fā)人員的編碼風(fēng)格形成統(tǒng)一的規(guī)范,并且手動(dòng)編寫(xiě)也很難稱(chēng)得上優(yōu)雅。

站在用戶(hù)的角度思考問(wèn)題,與客戶(hù)深入溝通,找到凌云網(wǎng)站設(shè)計(jì)與凌云網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗(yàn),讓設(shè)計(jì)與互聯(lián)網(wǎng)技術(shù)結(jié)合,創(chuàng)造個(gè)性化、用戶(hù)體驗(yàn)好的作品,建站類(lèi)型包括:成都網(wǎng)站設(shè)計(jì)、網(wǎng)站制作、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣、域名申請(qǐng)、虛擬空間、企業(yè)郵箱。業(yè)務(wù)覆蓋凌云地區(qū)。
二、MDC介紹
MDC(Mapped Diagnostic Context,映射調(diào)試上下文)是 log4j 、logback及l(fā)og4j2 提供的一種方便在多線(xiàn)程條件下記錄日志的功能。MDC 可以看成是一個(gè)與當(dāng)前線(xiàn)程綁定的哈希表,MDC 中包含的內(nèi)容可以被同一線(xiàn)程中執(zhí)行的代碼所訪問(wèn)。
MDC中的鍵值對(duì)是可以直接被日志框架所使用(即“打印”)的,只需要配置相應(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");
}
}此時(shí)控制臺(tái)將輸出:
21:16:04.342 [main] [123456789] INFO com.nk.MDCTest - hello world
三、實(shí)現(xiàn)方案
1、基本思路
修改日志pattern,并在業(yè)務(wù)開(kāi)始的時(shí)候?qū)race id放入到MDC,在業(yè)務(wù)結(jié)束時(shí)去除MDC的trace id。這樣的好處便是代碼簡(jiǎn)潔,不需要手動(dòng)寫(xiě)trace id,日志風(fēng)格也能保持統(tǒng)一。
業(yè)務(wù)開(kāi)始的時(shí)機(jī)一般是應(yīng)用收到HTTP請(qǐng)求,所以可以用Filter或SpringMVC的Interceptor來(lái)對(duì)MDC中trace id進(jìn)行初始化和清除。在Dubbo調(diào)用的時(shí)候也可以通過(guò)類(lèi)似功能的Filter來(lái)對(duì)MDC中trace id進(jìn)行操作,從而達(dá)到trace id傳遞的作用。
2、實(shí)現(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重點(diǎn)在于%X{TraceId},其中TraceId需要作為key出現(xiàn)在MDC里。
2.1.1 業(yè)務(wù)開(kāi)始
TraceId工具類(lèi),封裝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.1.2 業(yè)務(wù)中使用
正常使用logger,無(wú)需關(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;
}
}
請(qǐng)求該接口將輸出如下的日志樣式:
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ù)開(kāi)始階段一般通過(guò)攔截器就生成trace id并放入到MDC中,并根據(jù)MDC的相關(guān)特性將trace id投射到日志文本中,從而實(shí)現(xiàn)在同一個(gè)業(yè)務(wù)調(diào)用鏈路中的日志具有唯一標(biāo)識(shí)。
當(dāng)前文章:Java Web中日志跟蹤的簡(jiǎn)單實(shí)現(xiàn)
鏈接分享:http://www.5511xx.com/article/cogoghc.html


咨詢(xún)
建站咨詢(xún)
