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
相关推荐
苦瓜小生1 天前
【黑马点评学习笔记 | 实战篇 】| 6-Redis消息队列
redis·笔记·后端
yhole1 天前
springboot 修复 Spring Framework 特定条件下目录遍历漏洞(CVE-2024-38819)
spring boot·后端·spring
BingoGo1 天前
Laravel 13 正式发布 使用 Laravel AI 无缝平滑升级
后端·php
l软件定制开发工作室1 天前
Spring开发系列教程(34)——打包Spring Boot应用
java·spring boot·后端·spring·springboot
随风,奔跑1 天前
Spring MVC
java·后端·spring
美团技术团队1 天前
美团 BI 在指标平台和分析引擎上的探索和实践
后端
JimmtButler1 天前
我用 Claude Code 给 Claude Code 做了一个 DevTools
后端·claude
Java水解1 天前
Java 中实现多租户架构:数据隔离策略与实践指南
java·后端
Master_Azur1 天前
Java面向对象之多态与重写
后端
ywf12151 天前
Spring Integration + MQTT
java·后端·spring