SpringCloudGateway使用Skywalking時日志打印traceId解析
環(huán)境信息
- SpringCloudGateway 3.1.3
- Skywalking Agent 8.10.0
環(huán)境配置
Agent
由于SpringCloudGateway是基于WebFlux來實現(xiàn)的,需要進到skywalking的agent目錄,將optional-plugins目錄底下的以下兩個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啟動參數(shù)
新增啟動參數(shù)
-javaagent:D:\work\skywalking-agent\skywalking-agent.jar=agent.service_name=xxx -Dskywalking.collector.backend_service=xxx:11800
啟動程序后,嘗試通過網(wǎng)關(guān)進行接口調(diào)用,可以在Skywalking-ui上看到鏈路已經(jīng)串起來了

調(diào)用鏈路
但是有個問題,日志里記錄的日志始終不顯示正確的TID
[2022-06-15 14:53:19.958] [TID: N/A]
問題處理過程
查看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的時候,怎么顯示默認值
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
- 注冊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時日志打印traceId解析的詳細內(nèi)容,更多關(guān)于SpringCloudGateway Skywalking的資料請關(guān)注腳本之家其它相關(guān)文章!
- SpringCloud-Gateway轉(zhuǎn)發(fā)WebSocket失敗問題及解決
- spring?cloud?gateway中配置uri三種方式
- spring?cloud?gateway中netty線程池小優(yōu)化
- Spring?Cloud?Gateway中netty線程池優(yōu)化示例詳解
- SpringCloud?Gateway之請求應(yīng)答日志打印方式
- Spring Cloud gateway 網(wǎng)關(guān)如何攔截Post請求日志
- Spring Cloud Gateway 記錄請求應(yīng)答數(shù)據(jù)日志操作
- 基于Spring-cloud-gateway實現(xiàn)全局日志記錄的方法
相關(guān)文章
詳解Java對象轉(zhuǎn)換神器MapStruct庫的使用
在我們?nèi)粘i_發(fā)的程序中,為了各層之間解耦,一般會定義不同的對象用來在不同層之間傳遞數(shù)據(jù)。當(dāng)在不同層之間傳輸數(shù)據(jù)時,不可避免地經(jīng)常需要將這些對象進行相互轉(zhuǎn)換。今天給大家介紹一個對象轉(zhuǎn)換工具MapStruct,代碼簡潔安全、性能高,強烈推薦2022-09-09
springBoot定時任務(wù)處理類的實現(xiàn)代碼
這篇文章主要介紹了springBoot定時任務(wù)處理類,需要的朋友可以參考下2018-06-06
SpringBoot如何使用RateLimiter通過AOP方式進行限流
這篇文章主要介紹了SpringBoot如何使用RateLimiter通過AOP方式進行限流,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-06-06
Java如何接收前端easyui?datagrid傳遞的數(shù)組參數(shù)
這篇文章分享一下怎么在easyui的datagrid刷新表格時,在后端java代碼中接收datagrid傳遞的數(shù)組參數(shù),本文通過實例代碼給大家介紹的非常詳細,需要的朋友參考下吧2023-11-11
Spring AOP如何自定義注解實現(xiàn)審計或日志記錄(完整代碼)
這篇文章主要介紹了Spring AOP如何自定義注解實現(xiàn)審計或日志記錄(完整代碼),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-12-12
springboot項目中application.properties無法變成小樹葉問題解決方案
這篇文章主要介紹了springboot項目中application.properties無法變成小樹葉問題解決,本文通過圖文實例代碼相結(jié)合給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-09-09

