SpringCloudGateway使用Skywalking時(shí)日志打印traceId解析
環(huán)境信息
- SpringCloudGateway 3.1.3
- Skywalking Agent 8.10.0
環(huán)境配置
Agent
由于SpringCloudGateway是基于WebFlux來(lái)實(shí)現(xiàn)的,需要進(jìn)到skywalking的agent目錄,將optional-plugins目錄底下的以下兩個(gè)jar包復(fù)制到plugins目錄
- apm-spring-webflux-5.x-plugin-8.10.0.jar
- apm-spring-cloud-gateway-3.x-plugin-8.10.0.jar
Maven依賴配置
<dependency> <groupId>org.apache.skywalking</groupId> <artifactId>apm-toolkit-log4j-2.x</artifactId> <version>${skywalking.version}</version> </dependency> <dependency> <groupId>org.apache.skywalking</groupId> <artifactId>apm-toolkit-trace</artifactId> <version>${skywalking.version}</version> </dependency>
日志pattern配置
[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%traceId] [%logger{36}] [%thread] [%-5level] %msg%n
啟動(dòng)參數(shù)
新增啟動(dòng)參數(shù)
-javaagent:D:\work\skywalking-agent\skywalking-agent.jar=agent.service_name=xxx -Dskywalking.collector.backend_service=xxx:11800
啟動(dòng)程序后,嘗試通過(guò)網(wǎng)關(guān)進(jìn)行接口調(diào)用,可以在Skywalking-ui上看到鏈路已經(jīng)串起來(lái)了
調(diào)用鏈路
但是有個(gè)問(wèn)題,日志里記錄的日志始終不顯示正確的TID
[2022-06-15 14:53:19.958] [TID: N/A]
問(wèn)題處理過(guò)程
查看agent是怎么串聯(lián)鏈路的
查看Skywalking-agent的源碼,可以看到,在apm-spring-webflux-5.x-plugin-8.10.0.jar插件里,攔截了org.springframework.web.reactive.DispatcherHandler的handle方法
攔截器里往reactor的調(diào)用鏈路里,放入 < SKYWALKING_CONTEXT_SNAPSHOT - ContextSnapshot >
image.png
所以traceId可以從reactor的context里獲取到
怎么讓日志獲取到traceId
網(wǎng)上找了下資料,在這里[https://github.com/reactor/reactor-core/blob/main/docs/asciidoc/faq.adoc#context.api]發(fā)現(xiàn)了相關(guān)信息
public static <T> Consumer<Signal<T>> logOnNext(Consumer<T> logStatement) { return signal -> { if (!signal.isOnNext()) return; (1) Optional<String> toPutInMdc = signal.getContext().getOrEmpty("CONTEXT_KEY"); (2) toPutInMdc.ifPresentOrElse(tpim -> { try (MDC.MDCCloseable cMdc = MDC.putCloseable("MDC_KEY", tpim)) { (3) logStatement.accept(signal.get()); (4) } }, () -> logStatement.accept(signal.get())); (5) }; } @GetMapping("/byPrice") public Flux<Restaurant> byPrice(@RequestParam Double maxPrice, @RequestHeader(required = false, name = "X-UserId") String userId) { String apiId = userId == null ? "" : userId; (1) return restaurantService.byPrice(maxPrice)) .doOnEach(logOnNext(r -> LOG.debug("found restaurant {} for ${}", (2) r.getName(), r.getPricePerPerson()))) .contextWrite(Context.of("CONTEXT_KEY", apiId)); (3) }
獲取不到traceId的時(shí)候,怎么顯示默認(rèn)值
https://logging.apache.org/log4j/2.x/manual/layouts.html#PatternLayout
equals{pattern}{test}{substitution} equalsIgnoreCase{pattern}{test}{substitution}
完整例子
- pattern改為 [%d{yyyy-MM-dd HH:mm:ss.SSS}] [TID: %equals{%X{traceId}}{}{N/A}] [%logger{36}] [%thread] [%-5level] %msg%n
- 注冊(cè)onEachOperator的Hooks
@Component public class LogHooks { private static final String KEY = "logMdc"; @PostConstruct @SuppressWarnings("unchecked") public void setHook() { reactor.core.publisher.Hooks.onEachOperator(KEY, Operators.lift((scannable, coreSubscriber) -> new MdcSubscriber(coreSubscriber))); } @PreDestroy public void resetHook() { reactor.core.publisher.Hooks.resetOnEachOperator(KEY); } }
public class MdcSubscriber implements CoreSubscriber { private static final String TRACE_ID = "traceId"; private static final String SKYWALKING_CTX_SNAPSHOT = "SKYWALKING_CONTEXT_SNAPSHOT"; private final CoreSubscriber<Object> actual; public MdcSubscriber(CoreSubscriber<Object> actual) { this.actual = actual; } @Override public void onSubscribe(Subscription s) { actual.onSubscribe(s); } @Override public void onNext(Object o) { Context c = actual.currentContext(); Optional<String> traceIdOptional = Optional.empty(); if (!c.isEmpty() && c.hasKey(SKYWALKING_CTX_SNAPSHOT)) { traceIdOptional = Optional.of(c.get(SKYWALKING_CTX_SNAPSHOT)).map(BeanUtil::beanToMap) .map(t -> t.get(TRACE_ID)).map(BeanUtil::beanToMap).map(t -> t.get("id")).map(Object::toString); } try (MDC.MDCCloseable cMdc = MDC.putCloseable(TRACE_ID, traceIdOptional.orElse("N/A"))) { actual.onNext(o); } } @Override public void onError(Throwable throwable) { actual.onError(throwable); } @Override public void onComplete() { actual.onComplete(); } @Override public Context currentContext() { return actual.currentContext(); } }
以上就是SpringCloudGateway使用Skywalking時(shí)日志打印traceId解析的詳細(xì)內(nèi)容,更多關(guān)于SpringCloudGateway Skywalking的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- SpringCloud-Gateway轉(zhuǎn)發(fā)WebSocket失敗問(wèn)題及解決
- spring?cloud?gateway中配置uri三種方式
- spring?cloud?gateway中netty線程池小優(yōu)化
- Spring?Cloud?Gateway中netty線程池優(yōu)化示例詳解
- SpringCloud?Gateway之請(qǐng)求應(yīng)答日志打印方式
- Spring Cloud gateway 網(wǎng)關(guān)如何攔截Post請(qǐng)求日志
- Spring Cloud Gateway 記錄請(qǐng)求應(yīng)答數(shù)據(jù)日志操作
- 基于Spring-cloud-gateway實(shí)現(xiàn)全局日志記錄的方法
相關(guān)文章
Java maven三種倉(cāng)庫(kù),本地倉(cāng)庫(kù),私服,中央倉(cāng)庫(kù)的配置
今天給大家簡(jiǎn)單介紹Maven三種倉(cāng)庫(kù)的配置,文中有非常詳細(xì)的解釋,對(duì)Java初學(xué)者很有幫助喲,需要的朋友可以參考下,希望能夠給你帶來(lái)幫助2021-09-09springboot整合security和vue的實(shí)踐
本文主要介紹了springboot整合security和vue的實(shí)踐,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09解讀httpclient的validateAfterInactivity連接池狀態(tài)檢測(cè)
這篇文章主要為大家介紹了httpclient的validateAfterInactivity連接池狀態(tài)檢測(cè)解讀*,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11SpringBoot中使用Servlet三大組件的方法(Servlet、Filter、Listener)
這篇文章主要介紹了SpringBoot中使用Servlet三大組件的方法(Servlet、Filter、Listener),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01java DataInputStream和DataOutputStream詳解及實(shí)例代碼
這篇文章主要介紹了java DataInputStream和DataOutputStream詳解及實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-01-01Java中String類的常見(jiàn)方法超詳細(xì)講解
這篇文章主要介紹了Java中String類常見(jiàn)方法的相關(guān)資料,String類是不可變的,字符串常量池用于存儲(chǔ)字符串字面量,常用方法包括字符串查找、轉(zhuǎn)換、比較、替換、拆分和截取,需要的朋友可以參考下2025-04-04Java?Web開(kāi)發(fā)中的分頁(yè)與參數(shù)校驗(yàn)舉例詳解
這篇文章主要介紹了JavaWeb開(kāi)發(fā)中的分頁(yè)設(shè)計(jì)和參數(shù)校驗(yàn),分頁(yè)設(shè)計(jì)通過(guò)分頁(yè)查詢參數(shù)優(yōu)化查詢性能,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-02-02數(shù)據(jù)庫(kù)CURD必備搭檔mybatis?plus詳解
這篇文章主要為大家介紹了數(shù)據(jù)庫(kù)CURD必備搭檔mybatis?plus詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05