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
相关推荐
2401_8543910816 分钟前
Spring Boot OA:企业数字化转型的利器
java·spring boot·后端
山山而川粤23 分钟前
废品买卖回收管理系统|Java|SSM|Vue| 前后端分离
java·开发语言·后端·学习·mysql
2301_8112743125 分钟前
基于Spring Boot的同城宠物照看系统的设计与实现
spring boot·后端·宠物
2301_811274311 小时前
springboot嗨玩旅游网站
spring boot·后端·旅游
mit6.8242 小时前
[Redis#4] string | 常用命令 | + mysql use:cache | session
数据库·redis·后端·缓存
疯狂学习GIS2 小时前
创建第一个IDEA的Java项目的方法
java·后端·intellij idea
捂月3 小时前
Spring Boot 核心逻辑与工作原理详解
java·spring boot·后端
Nightselfhurt3 小时前
RPC学习
java·spring boot·后端·spring·rpc
Estar.Lee10 小时前
查手机号归属地免费API接口教程
android·网络·后端·网络协议·tcp/ip·oneapi
2401_8576100312 小时前
SpringBoot社团管理:安全与维护
spring boot·后端·安全