SpringCloudGateway Skywalking 不打印tid

环境

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。

非常细致的描述:Spring cloud gateway project logback cannot print TraceID · Issue #5906 · apache/skywalking (github.com)

解决

搜索了一下发现很多人都遇到了同样的问题,而官方的处理方法比较冷淡!以下方法经过验证是有效的!

  1. 确认添加了插件。从skywalking-agent目录下的optional-plugins中复制apm-spring-cloud-gateway-3.x-plugin-9.2.0.jarapm-spring-webflux-5.x-plugin-9.2.0.jarplugins目录
  2. 添加3个类,其中MdcSubscriberLogHooks是必须的,它们将链路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);
        }
    }

}
  1. 最后一步,调整日志的pattern,输出traceId。e.g:[%X{traceId}]

可能用到的依赖

xml 复制代码
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.27</version>
</dependency>

参考:

  1. SpringCloudGateway使用Skywalking时日志打印traceId - 简书 (jianshu.com)
  2. 解决SkyWalking在gateway中logback链路丢失 | Both Savage
相关推荐
虽千万人 吾往矣6 分钟前
golang gorm
开发语言·数据库·后端·tcp/ip·golang
这孩子叫逆42 分钟前
Spring Boot项目的创建与使用
java·spring boot·后端
coderWangbuer2 小时前
基于springboot的高校招生系统(含源码+sql+视频导入教程+文档+PPT)
spring boot·后端·sql
攸攸太上2 小时前
JMeter学习
java·后端·学习·jmeter·微服务
Kenny.志2 小时前
2、Spring Boot 3.x 集成 Feign
java·spring boot·后端
sky丶Mamba2 小时前
Spring Boot中获取application.yml中属性的几种方式
java·spring boot·后端
千里码aicood3 小时前
【2025】springboot教学评价管理系统(源码+文档+调试+答疑)
java·spring boot·后端·教学管理系统
程序员-珍4 小时前
使用openapi生成前端请求文件报错 ‘Token “Integer“ does not exist.‘
java·前端·spring boot·后端·restful·个人开发
liuxin334455664 小时前
教育技术革新:SpringBoot在线教育系统开发
数据库·spring boot·后端
数字扫地僧4 小时前
HBase与Hive、Spark的集成应用案例
后端