微服務(wù)分布式架構(gòu)實(shí)現(xiàn)日志鏈路跟蹤的方法
Logback 背景
Logback是由log4j創(chuàng)始人設(shè)計(jì)的另一個(gè)開源日志組件,官方網(wǎng)站:http://logback.qos.ch。它當(dāng)前分為下面下個(gè)模塊:
- logback-core:其它兩個(gè)模塊的基礎(chǔ)模塊
- logback-classic:它是log4j的一個(gè)改良版本,同時(shí)它完整實(shí)現(xiàn)了slf4j API使你可以很方便地更換成其它日志系統(tǒng)如log4j或JDK14 Logging
- logback-access:訪問模塊與Servlet容器集成提供通過Http來訪問日志的功能
普通debug日志
SQL執(zhí)行日志
Logback 配置案例
日志級(jí)別排序?yàn)椋篢RACE < DEBUG < INFO < WARN < ERROR
- %d:表示日期
- %n:換行
- %thread:表示線程名
- %level:日志級(jí)別
- %msg:日志消息
- %file:表示文件名
- %class:表示文件名
- %logger:Java類名(含包名,這里設(shè)定了36位,若超過36位,包名會(huì)精簡為類似a.b.c.JavaBean)
- %line:Java類的行號(hào)
注意:
%-4relative %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread][%X{TRACE_ID}] %-5level %logger{100}.%M\(%line\) - %msg%n
在logback中,%relative表示自應(yīng)用程序啟動(dòng)以來打印相對(duì)時(shí)間戳(以毫秒為單位). %-4只是元素的對(duì)齊方式.
案例
3452487 2021-08-03 15:19:36.940 [thread-monitor-daemon][] WARN com.xxxx.common.util.MonitorLogger.warn(27) - 發(fā)現(xiàn)超時(shí)線程notify-replay-consumer...
由于案例中是守護(hù)線程thread-monitor-daemon,所以不記錄鏈路ID。
對(duì)在系統(tǒng)設(shè)計(jì)的時(shí)候?qū)τ诰€程的命名規(guī)范也是有約束的
這里就不做詳細(xì)展開后續(xù)有機(jī)會(huì)會(huì)分享。
回歸正題比如下面的例子中記錄了請(qǐng)求的鏈路ID
19006989 2021-08-04 22:35:25.776 [http-nio-0.0.0.0-8010-exec-10][1fc8pebmgwukw863w2p342rp2936a3r157w0:0:] INFO com.xxx.framework.eureka.core.listener.EurekaStateChangeListener.listen(58) - 服務(wù)實(shí)例[XX-PAAS]注冊(cè)成功,當(dāng)前服務(wù)器已注冊(cè)服務(wù)實(shí)例數(shù)量[3]
對(duì)于上圖中顯示的系統(tǒng)啟動(dòng)時(shí)間、當(dāng)前時(shí)間、當(dāng)前線程、對(duì)應(yīng)路徑按照logback官方配置就可以逐步完善對(duì)于的日志信息,但是對(duì)于鏈路ID的生成寫入就需要特殊處理。
鏈路ID設(shè)計(jì)
對(duì)于鏈路追蹤設(shè)計(jì)我個(gè)人比較喜歡兩種方案
第一種
在每一次請(qǐng)求中鏈路編號(hào)(traceId)、單元編號(hào)(spanId)都是通過HttpHeader的方式進(jìn)行傳遞,日志的起始位置會(huì)主動(dòng)生成traceId、spanId,而起始位置的Parent SpanId則是不存在的,值為null。
這樣每次通過restTemplate、Openfeign的形式訪問其他服務(wù)的接口時(shí),就會(huì)攜帶起始位置生成的traceId、spanId到下一個(gè)服務(wù)單元。
第二種
在每一次請(qǐng)求中鏈路編號(hào)(traceId),沒經(jīng)過一次微服務(wù)對(duì)于深度(Deep)加1
public static class ThreadTraceListener implements ThreadListener { @Override public void onThreadBegin(HttpServletRequest request) { String traceToken = ThreadLocalUtil.getTranVar(TRACE_ID); String fromServer = ThreadLocalUtil.getTranVar(FROM_SERVER); int deep; String traceId; if (StringUtils.isBlank(traceToken)) { traceId = IDGenerator.generateID(); deep = 0; traceToken = StringHelper.join(traceId, ":0"); } else { int index = traceToken.lastIndexOf(':'); traceId = traceToken.substring(0, index); deep = Integer.valueOf(traceToken.substring(index + 1)); } ThreadLocalUtil.setLocalVar(TRACE_ID, traceId); ThreadLocalUtil.setLocalVar(TRACE_DEEP, deep); ThreadLocalUtil.setTranVar(TRACE_ID, StringHelper.join(traceId, ":", deep + 1)); ThreadLocalUtil.setLocalVar(FROM_SERVER, fromServer); ThreadLocalUtil.setTranVar(FROM_SERVER, getCurrentServer()); MDC.put(TRACE_ID, StringHelper.join(traceToken, ":", fromServer)); } @Override public void onThreadEnd(HttpServletRequest request) { MDC.remove(TRACE_ID); } }
針對(duì)請(qǐng)求攔截
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { long startTime = System.currentTimeMillis(); // 從Header中裝載傳遞過來的變量 Map<String, Object> tranVar = new HashMap<String, Object>(); Enumeration<String> headers = request.getHeaderNames(); while (headers.hasMoreElements()) { String key = headers.nextElement(); if (!StringUtils.isEmpty(key) && key.startsWith(ThreadLocalUtil.TRAN_PREFIX)) { tranVar.put(key.substring(ThreadLocalUtil.TRAN_PREFIX.length()), request.getHeader(key)); } } ThreadLocalHolder.begin(tranVar, request); try { if (isGateway) { response.addHeader("X-TRACE-ID", TraceUtil.getTraceId()); } // 檢查RPC調(diào)用深度 checkRpcDeep(request, response); // 業(yè)務(wù)處理 chain.doFilter(request, response); // 記錄RPC調(diào)用次數(shù) logRpcCount(request, response); } catch (Throwable ex) { // 錯(cuò)誤處理 Response<?> result = ExceptionUtil.toResponse(ex); Determine determine = ExceptionUtil.determineType(ex); ExceptionUtil.doLog(result, determine.getStatus(), ex); response.setStatus(determine.getStatus().value()); response.setCharacterEncoding("UTF-8"); response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); response.getWriter().write(JsonUtil.toJsonString(result)); } finally { try { doMonitor(request, response, startTime); if (TraceUtil.isTraceLoggerOn()) { log.warn(StringHelper.join( "TRACE-HTTP-", request.getMethod(), " URI:", request.getRequestURI(), ", dt:", System.currentTimeMillis() - startTime, ", rpc:", TraceUtil.getRpcCount(), ", status:", response.getStatus())); } else if (log.isTraceEnabled()) { log.trace(StringHelper.join(request.getMethod(), " URI:", request.getRequestURI(), ", dt:", System.currentTimeMillis() - startTime, ", rpc:", TraceUtil.getRpcCount(), ", status:", response.getStatus())); } } finally { ThreadLocalHolder.end(request); } } }
到此這篇關(guān)于微服務(wù)分布式架構(gòu)實(shí)現(xiàn)日志鏈路跟蹤的方法的文章就介紹到這了,更多相關(guān)微服務(wù)分布式架構(gòu)日志鏈路跟蹤內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java8 實(shí)現(xiàn)提取集合對(duì)象的每個(gè)屬性
這篇文章主要介紹了java8 實(shí)現(xiàn)提取集合對(duì)象的每個(gè)屬性方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-02-02總結(jié)Java中線程的狀態(tài)及多線程的實(shí)現(xiàn)方式
Java中可以通過Thread類和Runnable接口來創(chuàng)建多個(gè)線程,線程擁有五種狀態(tài),下面我們就來簡單總結(jié)Java中線程的狀態(tài)及多線程的實(shí)現(xiàn)方式:2016-07-07Java數(shù)據(jù)結(jié)構(gòu)順序表用法詳解
順序表是計(jì)算機(jī)內(nèi)存中以數(shù)組的形式保存的線性表,線性表的順序存儲(chǔ)是指用一組地址連續(xù)的存儲(chǔ)單元依次存儲(chǔ)線性表中的各個(gè)元素、使得線性表中在邏輯結(jié)構(gòu)上相鄰的數(shù)據(jù)元素存儲(chǔ)在相鄰的物理存儲(chǔ)單元中,即通過數(shù)據(jù)元素物理存儲(chǔ)的相鄰關(guān)系來反映數(shù)據(jù)元素之間邏輯上的相鄰關(guān)系2021-10-10Java比較器實(shí)現(xiàn)方法項(xiàng)目案例
這篇文章主要介紹了Java比較器實(shí)現(xiàn)方法,結(jié)合具體項(xiàng)目案例形式分析了Java比較器相關(guān)排序操作技巧,需要的朋友可以參考下2019-03-03Java模擬有序鏈表數(shù)據(jù)結(jié)構(gòu)的示例
這篇文章主要介紹了Java模擬有序鏈表數(shù)據(jù)結(jié)構(gòu)的示例,包括一個(gè)反序的單鏈表結(jié)構(gòu)的例子,需要的朋友可以參考下2016-04-04spring Retryable注解實(shí)現(xiàn)重試詳解
這篇文章主要介紹了spring Retryable注解實(shí)現(xiàn)重試詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-09-09