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 小时前
Spring Boot(快速上手)
java·spring boot·后端
墨香幽梦客7 小时前
API集成技术规范:RESTful与GraphQL在企业系统对接中的应用对比
后端·restful·graphql
刀法如飞8 小时前
AI编程时代,为什么35岁以上程序员会更吃香?
人工智能·后端·ai编程
小码哥_常8 小时前
Spring Boot 遇上 HMAC-SHA256,API 安全大升级!
后端
小码哥_常8 小时前
10分钟极速掌握!SpringBoot+Vue3整合SSE实现实时消息推送
后端
大黄说说9 小时前
深入 Go 语言 GMP 调度模型:高并发的秘密武器
后端
云原生指北10 小时前
Omnipub E2E 测试文章 - 自动化验证
后端
IT_陈寒10 小时前
SpringBoot自动配置揭秘:5个让开发效率翻倍的隐藏技巧
前端·人工智能·后端
添尹11 小时前
Go语言基础之数组
后端·golang
luom010212 小时前
SpringBoot - Cookie & Session 用户登录及登录状态保持功能实现
java·spring boot·后端