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
相关推荐
百锦再2 分钟前
选择Rust的理由:从内存管理到抛弃抽象
android·java·开发语言·后端·python·rust·go
小羊失眠啦.5 分钟前
深入解析Rust的所有权系统:告别空指针和数据竞争
开发语言·后端·rust
q***718542 分钟前
Spring Boot 集成 MyBatis 全面讲解
spring boot·后端·mybatis
大象席地抽烟1 小时前
使用 Ollama 本地模型与 Spring AI Alibaba
后端
程序员小假1 小时前
SQL 语句左连接右连接内连接如何使用,区别是什么?
java·后端
小坏讲微服务1 小时前
Spring Cloud Alibaba Gateway 集成 Redis 限流的完整配置
数据库·redis·分布式·后端·spring cloud·架构·gateway
方圆想当图灵2 小时前
Nacos 源码深度畅游:Nacos 配置同步详解(下)
分布式·后端·github
方圆想当图灵2 小时前
Nacos 源码深度畅游:Nacos 配置同步详解(上)
分布式·后端·github
小羊失眠啦.2 小时前
用 Rust 实现高性能并发下载器:从原理到实战
开发语言·后端·rust
Filotimo_3 小时前
SpringBoot3入门
java·spring boot·后端