使用方式
连续使用两次反射 (1)在已经得到具体实例的情况下,使用反射获取其类型,然后获取其指定字段(该字段为引用类型,指向另一个类) (2)根据上一步得到的字段,继续通过反射调用其指定方法代码
package com.example.gatewaydemo.filter; import lombok.extern.slf4j.Slf4j; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.lang.reflect.Field; import java.lang.reflect.Method; /** * @description 获取skywalking的traceId,并将其传递给nginx */ @Slf4j @Component public class TraceFilter implements GlobalFilter, Ordered { static final Logger logger = LogManager.getLogger(TraceFilter.class); /** * @description 设置优先级为最低 */ @Override public int getOrder() { return LOWEST_PRECEDENCE; } /** * @description 获取traceId并将其添加到响应头中 */ @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { try { // 获取skywalking设置到exchange中的span实例,并使用反射获取其owner字段(该字段从父类的父类继承而来,类型为TracingContext) Object entrySpanInstance = exchange.getAttributes().get("SKYWALING_SPAN"); Class<?> entrySpanClazz = entrySpanInstance.getClass().getSuperclass().getSuperclass(); Field field = entrySpanClazz.getDeclaredField("owner"); // 利用反射调用owner的getReadablePrimaryTraceId()方法,以获取traceId field.setAccessible(true); Object ownerInstance = field.get(entrySpanInstance); Class<?> ownerClazz = ownerInstance.getClass(); Method getTraceId = ownerClazz.getMethod("getReadablePrimaryTraceId"); String traceId = (String) getTraceId.invoke(ownerInstance); // 将traceId添加到响应头中 exchange.getResponse().getHeaders().add("tid", traceId); } catch (Exception exception) { logger.warn("an exception occurred while fetching skywalking traceId", exception); } return chain.filter(exchange); } }反射获取的对象说明: exchange.getAttributes().get("SKYWALING_SPAN"); 得到的实例类型是 EntrySpan,EntrySpan 的父类是 StackBasedTracingSpan,StackBasedTracingSpan 的父类是 AbstractTracingSpan AbstractTracingSpan 有一个字段 owner,这个字段是 TracingContext 类型的,TracingContext 类有一个成员方法 getReadablePrimaryTraceId(),可以获取到我们需要的 traceId
任务简述
任务背景
使用了 Skywalking 作为调用轨迹跟踪工具,每一个轨迹都有一个轨迹 id,可以根据该 id 在 Skywalking 的轨迹页面搜索具体的轨迹。 使用的框架是 SpringCloud,使用 SpringCloudGateway 作为网关,然后使用了 Nginx 作为 gateway 的上层,即,外部请求最先打到Nginx,然后由 Nginx 转发到 gateway,最后由 gateway 转发给具体的服务。 把 gateway 作为 Skywalking 的监控入口,即,Nginx 不包含在内。 做了一个 Nginx 的请求超时报警功能,即对于耗时过长的请求,会将 Nginx 的 access 日志推送出来。任务内容
把每个请求的轨迹 id,由 gateway 反向传送至 Nginx,并且配置到 Nginx 的 access 日志中。 基本实现思路是,在 gateway 中获取到轨迹 id,然后使用过滤器将其设置到响应头中,传送给 Nginx。Skywalking(8.4版本)相关源码
怎么找到的相关源码? 首先,在了解 Skywalking 的 agent 原理的时候,知道是使用了java的探针技术,这种技术涉及到一种类,即 *Instrumentation.java 类,然后,了解到 SpringcloudGateway 又是基于 Webflux,于是下载 Skywalking 的源码,找到相关模块。打开 DispatcherHandlerInstrumentation 类,根据以下代码可知,使用了 DispatcherHandlerHandleMethodInterceptor 这个拦截器对被拦截(或者说增强)的类进行了增强
@Override public String getMethodsInterceptor() { return "org.apache.skywalking.apm.plugin.spring.webflux.v5.DispatcherHandlerHandleMethodInterceptor"; }
打开 DispatcherHandlerHandleMethodInterceptor 类,看到以下代码,发现把 span (注:在 Skywalking 的轨迹中,span 是最小数据单元)注入到了 exchange 的 attributes 中(键为 SKYWALING_SPAN)
exchange.getAttributes().put("SKYWALING_SPAN", span);
而 exchange 在 gateway 的过滤器方法中是第一个参数
filter(ServerWebExchange exchange, GatewayFilterChain chain)
于是,在把环境准备好之后,使用 debug 模式探寻 exchange 中的数据,发现了我们需要的 traceId
同时,我们可以看到键 SKYWALING_SPAN 对应的值的类型是 EntrySpan,以及,最终的数据在 EntrySpan 的 owner 字段中,并且该 owner 字段是 TracingContext 类型的
接着,回到 Skywalking 的源码,找到 EntrySpan,发现其有一个构造器中包含 owner 字段public EntrySpan(int spanId, int parentSpanId, String operationName, TracingContext owner) { super(spanId, parentSpanId, operationName, owner); this.currentMaxDepth = 0; }
于是点击该 super 方法进入其父类,同样看到了一个构造器
protected StackBasedTracingSpan(int spanId, int parentSpanId, String operationName, TracingContext owner) { super(spanId, parentSpanId, operationName, owner); this.stackDepth = 0; this.peer = null; }
继续点击 super 方法,可以看到 EntrySpan 的 owner 属性原来是从其父类的父类 AbstractTracingSpan 中继承而来
protected AbstractTracingSpan(int spanId, int parentSpanId, String operationName, TracingContext owner) { this.operationName = operationName; this.spanId = spanId; this.parentSpanId = parentSpanId; this.owner = owner; }
进入 owner 所属的类 TracingContext 中,发现其中有一个方法可能跟我们需要的 traceId 有关
于是尝试在代码中使用反射调用此方法,结果发现该方法获取的就是 traceId