环境
jar包 | 版本 |
---|---|
jdk | 17 |
spring-cloud-gateway | 3.1.8 |
spring-webflux | 5.3.28 |
apm-toolkit-logback-1.x | 9.2.0 |
logback-classic | 1.2.12 |
skywalking-agent | 9.2.0 |
日志pattern
js
-%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) [%X{tid}] %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}:%line){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}
现象
其他服务正常,只有gateway的日志中不打印tid,但是查看skywalking的后台,可以看到gateway服务已经在链路上了,并且生成了tid。
解决
搜索了一下发现很多人都遇到了同样的问题,而官方的处理方法比较冷淡!以下方法经过验证是有效的!
- 确认添加了插件。从skywalking-agent目录下的
optional-plugins
中复制apm-spring-cloud-gateway-3.x-plugin-9.2.0.jar
和apm-spring-webflux-5.x-plugin-9.2.0.jar
到plugins
目录 - 添加3个类,其中
MdcSubscriber
和LogHooks
是必须的,它们将链路id写进了MDC的traceId
字段中,SkywalkingUtil
通过主动的方式向MDC中传入traceId
,是为了解决自定义日志类AccessLogGlobalFilter
的输出问题,如果你没有这个filter,可以不用第3个类。代码如下:
MdcSubscriber:
java
package tench.supx.infra.gateway;
import cn.hutool.core.bean.BeanUtil;
import org.reactivestreams.Subscription;
import org.slf4j.MDC;
import reactor.core.CoreSubscriber;
import reactor.util.context.Context;
import java.util.Optional;
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();
}
}
LogHooks:
java
package tech.supx.infra.gateway;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Operators;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@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);
}
}
SkywalkingUtil:
java
package tech.supx.infra.gateway;
import cn.hutool.core.util.ObjectUtil;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.web.server.ServerWebExchange;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@Slf4j
public class SkywalkingUtil {
/**
* tid放入MDC
*
* @param exchange
*/
public static void putTidIntoMdc(ServerWebExchange exchange) {
putTidIntoMdc(exchange, "tid");
}
/**
* tid放入MDC
*
* @param exchange
*/
public static void putTidIntoMdc(ServerWebExchange exchange, String key) {
try {
Object entrySpanInstance = exchange.getAttributes().get("SKYWALKING_SPAN");
if (ObjectUtil.isEmpty(entrySpanInstance)) {
return;
}
Class<?> entrySpanClazz = entrySpanInstance.getClass().getSuperclass().getSuperclass();
Field field = entrySpanClazz.getDeclaredField("owner");
field.setAccessible(true);
Object ownerInstance = field.get(entrySpanInstance);
Class<?> ownerClazz = ownerInstance.getClass();
Method getTraceId = ownerClazz.getMethod("getReadablePrimaryTraceId");
String traceId = (String) getTraceId.invoke(ownerInstance);
MDC.put(key, traceId);
} catch (Exception e) {
log.error("gateway追踪码获取失败", e);
}
}
}
- 最后一步,调整日志的pattern,输出traceId。e.g:
[%X{traceId}]
可能用到的依赖
xml
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.27</version>
</dependency>