[Feature] Support Tracing for GlobalFilter and GatewayFilter in Gateway (#736)
This commit is contained in:
parent
26600e76e2
commit
be3d09240a
|
|
@ -16,6 +16,7 @@ Release Notes.
|
|||
* Bump up gRPC to 1.68.1
|
||||
* Bump up netty to 4.1.115.Final
|
||||
* Fix the `CreateAopProxyInterceptor` in the Spring core-patch to prevent it from changing the implementation of the Spring AOP proxy
|
||||
* Support Tracing for GlobalFilter and GatewayFilter in Spring Gateway
|
||||
|
||||
All issues and pull requests are [here](https://github.com/apache/skywalking/milestone/222?closed=1)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.skywalking.apm.plugin.spring.cloud.gateway.v20x;
|
||||
|
||||
import org.apache.skywalking.apm.agent.core.context.ContextManager;
|
||||
import org.apache.skywalking.apm.agent.core.context.ContextSnapshot;
|
||||
import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
|
||||
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
|
||||
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
|
||||
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.ServerWebExchangeDecorator;
|
||||
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.apache.skywalking.apm.network.trace.component.ComponentsDefine.SPRING_CLOUD_GATEWAY;
|
||||
|
||||
public class GatewayFilterInterceptor implements InstanceMethodsAroundInterceptor {
|
||||
|
||||
private static final ThreadLocal<AtomicInteger> STACK_DEEP = ThreadLocal.withInitial(() -> new AtomicInteger(0));
|
||||
|
||||
@Override
|
||||
public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
|
||||
MethodInterceptResult result) throws Throwable {
|
||||
if (isEntry()) {
|
||||
ServerWebExchange exchange = (ServerWebExchange) allArguments[0];
|
||||
|
||||
EnhancedInstance enhancedInstance = getInstance(exchange);
|
||||
|
||||
AbstractSpan span = ContextManager.createLocalSpan("SpringCloudGateway/GatewayFilter");
|
||||
if (enhancedInstance != null && enhancedInstance.getSkyWalkingDynamicField() != null) {
|
||||
ContextManager.continued((ContextSnapshot) enhancedInstance.getSkyWalkingDynamicField());
|
||||
}
|
||||
span.setComponent(SPRING_CLOUD_GATEWAY);
|
||||
}
|
||||
}
|
||||
|
||||
public static EnhancedInstance getInstance(Object o) {
|
||||
EnhancedInstance instance = null;
|
||||
if (o instanceof DefaultServerWebExchange) {
|
||||
instance = (EnhancedInstance) o;
|
||||
} else if (o instanceof ServerWebExchangeDecorator) {
|
||||
ServerWebExchange delegate = ((ServerWebExchangeDecorator) o).getDelegate();
|
||||
return getInstance(delegate);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
|
||||
Object ret) throws Throwable {
|
||||
if (isExit()) {
|
||||
if (ContextManager.isActive()) {
|
||||
ContextManager.stopSpan();
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
|
||||
Class<?>[] argumentsTypes, Throwable t) {
|
||||
ContextManager.activeSpan().log(t);
|
||||
}
|
||||
|
||||
private boolean isEntry() {
|
||||
return STACK_DEEP.get().getAndIncrement() == 0;
|
||||
}
|
||||
|
||||
private boolean isExit() {
|
||||
boolean isExit = STACK_DEEP.get().decrementAndGet() == 0;
|
||||
if (isExit) {
|
||||
STACK_DEEP.remove();
|
||||
}
|
||||
return isExit;
|
||||
}
|
||||
}
|
||||
|
|
@ -29,4 +29,8 @@ public interface Constants {
|
|||
// HttpClientRequest
|
||||
String INTERCEPT_CLASS_HTTP_CLIENT_REQUEST = "reactor.ipc.netty.http.client.HttpClientRequest";
|
||||
String HTTPCLIENT_REQUEST_HEADERS_INTERCEPTOR = "org.apache.skywalking.apm.plugin.spring.cloud.gateway.v20x.HttpclientRequestHeadersInterceptor";
|
||||
|
||||
// GatewayFilter
|
||||
String INTERCEPT_CLASS_GATEWAY_FILTER = "org.springframework.cloud.gateway.filter.GatewayFilter";
|
||||
String GATEWAY_FILTER_INTERCEPTOR = "org.apache.skywalking.apm.plugin.spring.cloud.gateway.v20x.GatewayFilterInterceptor";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.skywalking.apm.plugin.spring.cloud.gateway.v20x.define;
|
||||
|
||||
import net.bytebuddy.description.method.MethodDescription;
|
||||
import net.bytebuddy.matcher.ElementMatcher;
|
||||
import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint;
|
||||
import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint;
|
||||
import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch;
|
||||
import org.apache.skywalking.apm.agent.core.plugin.match.HierarchyMatch;
|
||||
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
import static org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ArgumentTypeNameMatch.takesArgumentWithType;
|
||||
|
||||
public class GatewayFilterInstrumentation extends AbstractGateway200EnhancePluginDefine {
|
||||
|
||||
@Override
|
||||
protected ClassMatch enhanceClass() {
|
||||
return HierarchyMatch.byHierarchyMatch(Constants.INTERCEPT_CLASS_GATEWAY_FILTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
|
||||
return new ConstructorInterceptPoint[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
|
||||
return new InstanceMethodsInterceptPoint[] {
|
||||
new InstanceMethodsInterceptPoint() {
|
||||
@Override
|
||||
public ElementMatcher<MethodDescription> getMethodsMatcher() {
|
||||
return named("filter").and(
|
||||
takesArgumentWithType(0, "org.springframework.web.server.ServerWebExchange"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethodsInterceptor() {
|
||||
return Constants.GATEWAY_FILTER_INTERCEPTOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOverrideArgs() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -18,4 +18,5 @@ spring-cloud-gateway-2.0.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway
|
|||
spring-cloud-gateway-2.0.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v20x.define.HttpClientInstrumentation
|
||||
spring-cloud-gateway-2.0.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v20x.define.HttpClientRequestInstrumentation
|
||||
spring-cloud-gateway-2.0.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v20x.define.ServerWebExchangeInstrumentation
|
||||
spring-cloud-gateway-2.0.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v20x.define.DispatcherHandlerInstrumentation
|
||||
spring-cloud-gateway-2.0.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v20x.define.DispatcherHandlerInstrumentation
|
||||
spring-cloud-gateway-2.0.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v20x.define.GatewayFilterInstrumentation
|
||||
|
|
@ -0,0 +1,333 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.apache.skywalking.apm.plugin.spring.cloud.gateway.v20x;
|
||||
|
||||
import org.apache.skywalking.apm.agent.core.context.ContextManager;
|
||||
import org.apache.skywalking.apm.agent.core.context.ContextSnapshot;
|
||||
import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
|
||||
import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan;
|
||||
import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer;
|
||||
import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment;
|
||||
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
|
||||
import org.apache.skywalking.apm.agent.test.helper.SegmentHelper;
|
||||
|
||||
import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule;
|
||||
import org.apache.skywalking.apm.agent.test.tools.SegmentStorage;
|
||||
import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint;
|
||||
import org.apache.skywalking.apm.agent.test.tools.SpanAssert;
|
||||
import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner;
|
||||
import org.apache.skywalking.apm.network.trace.component.ComponentsDefine;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.i18n.LocaleContext;
|
||||
import org.springframework.http.codec.multipart.Part;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebSession;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
@RunWith(TracingSegmentRunner.class)
|
||||
public class GatewayFilterInterceptorTest {
|
||||
|
||||
private static final String GATEWAY_FILTER_INTERCEPTOR_LOCAL_SPAN_OPERATION_NAME = "SpringCloudGateway/GatewayFilter";
|
||||
|
||||
private static class ServerWebExchangeEnhancedInstance implements ServerWebExchange, EnhancedInstance {
|
||||
private ContextSnapshot snapshot;
|
||||
Map<String, Object> attributes = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public Object getSkyWalkingDynamicField() {
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSkyWalkingDynamicField(Object value) {
|
||||
this.snapshot = (ContextSnapshot) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerHttpRequest getRequest() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerHttpResponse getResponse() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<WebSession> getSession() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Principal> Mono<T> getPrincipal() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<MultiValueMap<String, String>> getFormData() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<MultiValueMap<String, Part>> getMultipartData() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocaleContext getLocaleContext() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApplicationContext getApplicationContext() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNotModified() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkNotModified(Instant instant) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkNotModified(String s) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkNotModified(String s, Instant instant) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String transformUrl(String s) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addUrlTransformer(Function<String, String> function) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLogPrefix() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private final ServerWebExchangeEnhancedInstance enhancedInstance = new ServerWebExchangeEnhancedInstance();
|
||||
private final GatewayFilterInterceptor interceptor = new GatewayFilterInterceptor();
|
||||
@Rule
|
||||
public AgentServiceRule serviceRule = new AgentServiceRule();
|
||||
@Rule
|
||||
public MockitoRule rule = MockitoJUnit.rule();
|
||||
|
||||
@SegmentStoragePoint
|
||||
private SegmentStorage segmentStorage;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
}
|
||||
|
||||
private final ServerWebExchange exchange = new ServerWebExchange() {
|
||||
Map<String, Object> attributes = new HashMap<>();
|
||||
@Override
|
||||
public ServerHttpRequest getRequest() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerHttpResponse getResponse() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<WebSession> getSession() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Principal> Mono<T> getPrincipal() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<MultiValueMap<String, String>> getFormData() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<MultiValueMap<String, Part>> getMultipartData() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocaleContext getLocaleContext() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApplicationContext getApplicationContext() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNotModified() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkNotModified(Instant instant) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkNotModified(String s) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkNotModified(String s, Instant instant) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String transformUrl(String s) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addUrlTransformer(Function<String, String> function) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLogPrefix() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
@Test
|
||||
public void testInterceptOnlyOnce() throws Throwable {
|
||||
interceptor.beforeMethod(null, null, new Object[]{exchange}, null, null);
|
||||
Assert.assertTrue(ContextManager.isActive());
|
||||
AbstractSpan activeSpan = ContextManager.activeSpan();
|
||||
Assert.assertTrue(activeSpan instanceof AbstractTracingSpan);
|
||||
Assert.assertEquals(activeSpan.getOperationName(), GATEWAY_FILTER_INTERCEPTOR_LOCAL_SPAN_OPERATION_NAME);
|
||||
|
||||
interceptor.afterMethod(null, null, null, null, null);
|
||||
Assert.assertFalse(ContextManager.isActive());
|
||||
final List<TraceSegment> traceSegments = segmentStorage.getTraceSegments();
|
||||
Assert.assertEquals(traceSegments.size(), 1);
|
||||
final List<AbstractTracingSpan> spans = SegmentHelper.getSpans(traceSegments.get(0));
|
||||
Assert.assertNotNull(spans);
|
||||
Assert.assertEquals(spans.size(), 1);
|
||||
SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNestedInterception() throws Throwable {
|
||||
interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null);
|
||||
Assert.assertTrue(ContextManager.isActive());
|
||||
|
||||
interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null);
|
||||
Assert.assertTrue(ContextManager.isActive());
|
||||
|
||||
interceptor.afterMethod(null, null, null, null, null);
|
||||
Assert.assertTrue(ContextManager.isActive());
|
||||
|
||||
interceptor.afterMethod(null, null, null, null, null);
|
||||
Assert.assertFalse(ContextManager.isActive());
|
||||
|
||||
final List<TraceSegment> traceSegments = segmentStorage.getTraceSegments();
|
||||
Assert.assertEquals(traceSegments.size(), 1);
|
||||
final List<AbstractTracingSpan> spans = SegmentHelper.getSpans(traceSegments.get(0));
|
||||
Assert.assertNotNull(spans);
|
||||
Assert.assertEquals(spans.size(), 1);
|
||||
SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithNullDynamicField() throws Throwable {
|
||||
interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null);
|
||||
interceptor.afterMethod(null, null, null, null, null);
|
||||
// no more need this, span was stopped at interceptor#afterMethod
|
||||
// ContextManager.stopSpan();
|
||||
final List<TraceSegment> traceSegments = segmentStorage.getTraceSegments();
|
||||
Assert.assertEquals(traceSegments.size(), 1);
|
||||
final List<AbstractTracingSpan> spans = SegmentHelper.getSpans(traceSegments.get(0));
|
||||
Assert.assertNotNull(spans);
|
||||
Assert.assertEquals(spans.size(), 1);
|
||||
SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithContextSnapshot() throws Throwable {
|
||||
final AbstractSpan entrySpan = ContextManager.createEntrySpan("/get", null);
|
||||
SpanLayer.asHttp(entrySpan);
|
||||
entrySpan.setComponent(ComponentsDefine.SPRING_WEBFLUX);
|
||||
enhancedInstance.setSkyWalkingDynamicField(ContextManager.capture());
|
||||
interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null);
|
||||
interceptor.afterMethod(null, null, null, null, null);
|
||||
// no more need this, span was stopped at interceptor#afterMethod
|
||||
// ContextManager.stopSpan();
|
||||
ContextManager.stopSpan(entrySpan);
|
||||
final List<TraceSegment> traceSegments = segmentStorage.getTraceSegments();
|
||||
Assert.assertEquals(traceSegments.size(), 1);
|
||||
final List<AbstractTracingSpan> spans = SegmentHelper.getSpans(traceSegments.get(0));
|
||||
Assert.assertNotNull(spans);
|
||||
Assert.assertEquals(spans.size(), 2);
|
||||
SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY);
|
||||
SpanAssert.assertComponent(spans.get(1), ComponentsDefine.SPRING_WEBFLUX);
|
||||
SpanAssert.assertLayer(spans.get(1), SpanLayer.HTTP);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.skywalking.apm.plugin.spring.cloud.gateway.v21x;
|
||||
|
||||
import org.apache.skywalking.apm.agent.core.context.ContextManager;
|
||||
import org.apache.skywalking.apm.agent.core.context.ContextSnapshot;
|
||||
import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
|
||||
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
|
||||
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
|
||||
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.ServerWebExchangeDecorator;
|
||||
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.apache.skywalking.apm.network.trace.component.ComponentsDefine.SPRING_CLOUD_GATEWAY;
|
||||
|
||||
public class GatewayFilterInterceptor implements InstanceMethodsAroundInterceptor {
|
||||
|
||||
private static final ThreadLocal<AtomicInteger> STACK_DEEP = ThreadLocal.withInitial(() -> new AtomicInteger(0));
|
||||
|
||||
@Override
|
||||
public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
|
||||
MethodInterceptResult result) throws Throwable {
|
||||
if (isEntry()) {
|
||||
ServerWebExchange exchange = (ServerWebExchange) allArguments[0];
|
||||
|
||||
EnhancedInstance enhancedInstance = getInstance(exchange);
|
||||
|
||||
AbstractSpan span = ContextManager.createLocalSpan("SpringCloudGateway/GatewayFilter");
|
||||
if (enhancedInstance != null && enhancedInstance.getSkyWalkingDynamicField() != null) {
|
||||
ContextManager.continued((ContextSnapshot) enhancedInstance.getSkyWalkingDynamicField());
|
||||
}
|
||||
span.setComponent(SPRING_CLOUD_GATEWAY);
|
||||
}
|
||||
}
|
||||
|
||||
public static EnhancedInstance getInstance(Object o) {
|
||||
EnhancedInstance instance = null;
|
||||
if (o instanceof DefaultServerWebExchange) {
|
||||
instance = (EnhancedInstance) o;
|
||||
} else if (o instanceof ServerWebExchangeDecorator) {
|
||||
ServerWebExchange delegate = ((ServerWebExchangeDecorator) o).getDelegate();
|
||||
return getInstance(delegate);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
|
||||
Object ret) throws Throwable {
|
||||
if (isExit()) {
|
||||
if (ContextManager.isActive()) {
|
||||
ContextManager.stopSpan();
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
|
||||
Class<?>[] argumentsTypes, Throwable t) {
|
||||
ContextManager.activeSpan().log(t);
|
||||
}
|
||||
|
||||
private boolean isEntry() {
|
||||
return STACK_DEEP.get().getAndIncrement() == 0;
|
||||
}
|
||||
|
||||
private boolean isExit() {
|
||||
boolean isExit = STACK_DEEP.get().decrementAndGet() == 0;
|
||||
if (isExit) {
|
||||
STACK_DEEP.remove();
|
||||
}
|
||||
return isExit;
|
||||
}
|
||||
}
|
||||
|
|
@ -33,4 +33,7 @@ public interface Constants {
|
|||
String INTERCEPT_CLASS_TCP_CLIENT = "reactor.netty.tcp.TcpClient";
|
||||
String TCP_CLIENT_CONSTRUCTOR_INTERCEPTOR = "org.apache.skywalking.apm.plugin.spring.cloud.gateway.v21x.TcpClientConstructorInterceptor";
|
||||
|
||||
// GatewayFilter
|
||||
String INTERCEPT_CLASS_GATEWAY_FILTER = "org.springframework.cloud.gateway.filter.GatewayFilter";
|
||||
String GATEWAY_FILTER_INTERCEPTOR = "org.apache.skywalking.apm.plugin.spring.cloud.gateway.v21x.GatewayFilterInterceptor";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.skywalking.apm.plugin.spring.cloud.gateway.v21x.define;
|
||||
|
||||
import net.bytebuddy.description.method.MethodDescription;
|
||||
import net.bytebuddy.matcher.ElementMatcher;
|
||||
import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint;
|
||||
import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint;
|
||||
import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch;
|
||||
import org.apache.skywalking.apm.agent.core.plugin.match.HierarchyMatch;
|
||||
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
import static org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ArgumentTypeNameMatch.takesArgumentWithType;
|
||||
|
||||
public class GatewayFilterInstrumentation extends AbstractGateway210EnhancePluginDefine {
|
||||
|
||||
@Override
|
||||
protected ClassMatch enhanceClass() {
|
||||
return HierarchyMatch.byHierarchyMatch(Constants.INTERCEPT_CLASS_GATEWAY_FILTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
|
||||
return new ConstructorInterceptPoint[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
|
||||
return new InstanceMethodsInterceptPoint[] {
|
||||
new InstanceMethodsInterceptPoint() {
|
||||
@Override
|
||||
public ElementMatcher<MethodDescription> getMethodsMatcher() {
|
||||
return named("filter").and(
|
||||
takesArgumentWithType(0, "org.springframework.web.server.ServerWebExchange"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethodsInterceptor() {
|
||||
return Constants.GATEWAY_FILTER_INTERCEPTOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOverrideArgs() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -19,3 +19,4 @@ spring-cloud-gateway-2.1.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway
|
|||
spring-cloud-gateway-2.1.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v21x.define.TcpClientInstrumentation
|
||||
spring-cloud-gateway-2.1.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v21x.define.ServerWebExchangeInstrumentation
|
||||
spring-cloud-gateway-2.1.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v21x.define.DispatcherHandlerInstrumentation
|
||||
spring-cloud-gateway-2.1.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v21x.define.GatewayFilterInstrumentation
|
||||
|
|
|
|||
|
|
@ -0,0 +1,332 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.apache.skywalking.apm.plugin.spring.cloud.gateway.v21x;
|
||||
|
||||
import org.apache.skywalking.apm.agent.core.context.ContextManager;
|
||||
import org.apache.skywalking.apm.agent.core.context.ContextSnapshot;
|
||||
import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
|
||||
import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan;
|
||||
import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer;
|
||||
import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment;
|
||||
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
|
||||
import org.apache.skywalking.apm.agent.test.helper.SegmentHelper;
|
||||
import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule;
|
||||
import org.apache.skywalking.apm.agent.test.tools.SegmentStorage;
|
||||
import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint;
|
||||
import org.apache.skywalking.apm.agent.test.tools.SpanAssert;
|
||||
import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner;
|
||||
import org.apache.skywalking.apm.network.trace.component.ComponentsDefine;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.i18n.LocaleContext;
|
||||
import org.springframework.http.codec.multipart.Part;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebSession;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
@RunWith(TracingSegmentRunner.class)
|
||||
public class GatewayFilterInterceptorTest {
|
||||
|
||||
private static final String GATEWAY_FILTER_INTERCEPTOR_LOCAL_SPAN_OPERATION_NAME = "SpringCloudGateway/GatewayFilter";
|
||||
|
||||
private static class ServerWebExchangeEnhancedInstance implements ServerWebExchange, EnhancedInstance {
|
||||
private ContextSnapshot snapshot;
|
||||
Map<String, Object> attributes = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public Object getSkyWalkingDynamicField() {
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSkyWalkingDynamicField(Object value) {
|
||||
this.snapshot = (ContextSnapshot) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerHttpRequest getRequest() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerHttpResponse getResponse() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<WebSession> getSession() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Principal> Mono<T> getPrincipal() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<MultiValueMap<String, String>> getFormData() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<MultiValueMap<String, Part>> getMultipartData() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocaleContext getLocaleContext() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApplicationContext getApplicationContext() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNotModified() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkNotModified(Instant instant) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkNotModified(String s) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkNotModified(String s, Instant instant) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String transformUrl(String s) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addUrlTransformer(Function<String, String> function) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLogPrefix() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private final ServerWebExchangeEnhancedInstance enhancedInstance = new ServerWebExchangeEnhancedInstance();
|
||||
private final GatewayFilterInterceptor interceptor = new GatewayFilterInterceptor();
|
||||
@Rule
|
||||
public AgentServiceRule serviceRule = new AgentServiceRule();
|
||||
@Rule
|
||||
public MockitoRule rule = MockitoJUnit.rule();
|
||||
|
||||
@SegmentStoragePoint
|
||||
private SegmentStorage segmentStorage;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
}
|
||||
|
||||
private final ServerWebExchange exchange = new ServerWebExchange() {
|
||||
Map<String, Object> attributes = new HashMap<>();
|
||||
@Override
|
||||
public ServerHttpRequest getRequest() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerHttpResponse getResponse() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<WebSession> getSession() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Principal> Mono<T> getPrincipal() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<MultiValueMap<String, String>> getFormData() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<MultiValueMap<String, Part>> getMultipartData() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocaleContext getLocaleContext() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApplicationContext getApplicationContext() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNotModified() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkNotModified(Instant instant) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkNotModified(String s) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkNotModified(String s, Instant instant) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String transformUrl(String s) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addUrlTransformer(Function<String, String> function) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLogPrefix() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
@Test
|
||||
public void testInterceptOnlyOnce() throws Throwable {
|
||||
interceptor.beforeMethod(null, null, new Object[]{exchange}, null, null);
|
||||
Assert.assertTrue(ContextManager.isActive());
|
||||
AbstractSpan activeSpan = ContextManager.activeSpan();
|
||||
Assert.assertTrue(activeSpan instanceof AbstractTracingSpan);
|
||||
Assert.assertEquals(activeSpan.getOperationName(), GATEWAY_FILTER_INTERCEPTOR_LOCAL_SPAN_OPERATION_NAME);
|
||||
|
||||
interceptor.afterMethod(null, null, null, null, null);
|
||||
Assert.assertFalse(ContextManager.isActive());
|
||||
final List<TraceSegment> traceSegments = segmentStorage.getTraceSegments();
|
||||
Assert.assertEquals(traceSegments.size(), 1);
|
||||
final List<AbstractTracingSpan> spans = SegmentHelper.getSpans(traceSegments.get(0));
|
||||
Assert.assertNotNull(spans);
|
||||
Assert.assertEquals(spans.size(), 1);
|
||||
SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNestedInterception() throws Throwable {
|
||||
interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null);
|
||||
Assert.assertTrue(ContextManager.isActive());
|
||||
|
||||
interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null);
|
||||
Assert.assertTrue(ContextManager.isActive());
|
||||
|
||||
interceptor.afterMethod(null, null, null, null, null);
|
||||
Assert.assertTrue(ContextManager.isActive());
|
||||
|
||||
interceptor.afterMethod(null, null, null, null, null);
|
||||
Assert.assertFalse(ContextManager.isActive());
|
||||
|
||||
final List<TraceSegment> traceSegments = segmentStorage.getTraceSegments();
|
||||
Assert.assertEquals(traceSegments.size(), 1);
|
||||
final List<AbstractTracingSpan> spans = SegmentHelper.getSpans(traceSegments.get(0));
|
||||
Assert.assertNotNull(spans);
|
||||
Assert.assertEquals(spans.size(), 1);
|
||||
SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithNullDynamicField() throws Throwable {
|
||||
interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null);
|
||||
interceptor.afterMethod(null, null, null, null, null);
|
||||
// no more need this, span was stopped at interceptor#afterMethod
|
||||
// ContextManager.stopSpan();
|
||||
final List<TraceSegment> traceSegments = segmentStorage.getTraceSegments();
|
||||
Assert.assertEquals(traceSegments.size(), 1);
|
||||
final List<AbstractTracingSpan> spans = SegmentHelper.getSpans(traceSegments.get(0));
|
||||
Assert.assertNotNull(spans);
|
||||
Assert.assertEquals(spans.size(), 1);
|
||||
SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithContextSnapshot() throws Throwable {
|
||||
final AbstractSpan entrySpan = ContextManager.createEntrySpan("/get", null);
|
||||
SpanLayer.asHttp(entrySpan);
|
||||
entrySpan.setComponent(ComponentsDefine.SPRING_WEBFLUX);
|
||||
enhancedInstance.setSkyWalkingDynamicField(ContextManager.capture());
|
||||
interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null);
|
||||
interceptor.afterMethod(null, null, null, null, null);
|
||||
// no more need this, span was stopped at interceptor#afterMethod
|
||||
// ContextManager.stopSpan();
|
||||
ContextManager.stopSpan(entrySpan);
|
||||
final List<TraceSegment> traceSegments = segmentStorage.getTraceSegments();
|
||||
Assert.assertEquals(traceSegments.size(), 1);
|
||||
final List<AbstractTracingSpan> spans = SegmentHelper.getSpans(traceSegments.get(0));
|
||||
Assert.assertNotNull(spans);
|
||||
Assert.assertEquals(spans.size(), 2);
|
||||
SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY);
|
||||
SpanAssert.assertComponent(spans.get(1), ComponentsDefine.SPRING_WEBFLUX);
|
||||
SpanAssert.assertLayer(spans.get(1), SpanLayer.HTTP);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.skywalking.apm.plugin.spring.cloud.gateway.v3x;
|
||||
|
||||
import org.apache.skywalking.apm.agent.core.context.ContextManager;
|
||||
import org.apache.skywalking.apm.agent.core.context.ContextSnapshot;
|
||||
import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
|
||||
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
|
||||
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
|
||||
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.ServerWebExchangeDecorator;
|
||||
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.apache.skywalking.apm.network.trace.component.ComponentsDefine.SPRING_CLOUD_GATEWAY;
|
||||
|
||||
public class GatewayFilterInterceptor implements InstanceMethodsAroundInterceptor {
|
||||
|
||||
private static final ThreadLocal<AtomicInteger> STACK_DEEP = ThreadLocal.withInitial(() -> new AtomicInteger(0));
|
||||
|
||||
@Override
|
||||
public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
|
||||
MethodInterceptResult result) throws Throwable {
|
||||
if (isEntry()) {
|
||||
ServerWebExchange exchange = (ServerWebExchange) allArguments[0];
|
||||
|
||||
EnhancedInstance enhancedInstance = getInstance(exchange);
|
||||
|
||||
AbstractSpan span = ContextManager.createLocalSpan("SpringCloudGateway/GatewayFilter");
|
||||
if (enhancedInstance != null && enhancedInstance.getSkyWalkingDynamicField() != null) {
|
||||
ContextManager.continued((ContextSnapshot) enhancedInstance.getSkyWalkingDynamicField());
|
||||
}
|
||||
span.setComponent(SPRING_CLOUD_GATEWAY);
|
||||
}
|
||||
}
|
||||
|
||||
public static EnhancedInstance getInstance(Object o) {
|
||||
EnhancedInstance instance = null;
|
||||
if (o instanceof DefaultServerWebExchange) {
|
||||
instance = (EnhancedInstance) o;
|
||||
} else if (o instanceof ServerWebExchangeDecorator) {
|
||||
ServerWebExchange delegate = ((ServerWebExchangeDecorator) o).getDelegate();
|
||||
return getInstance(delegate);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
|
||||
Object ret) throws Throwable {
|
||||
if (isExit()) {
|
||||
if (ContextManager.isActive()) {
|
||||
ContextManager.stopSpan();
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
|
||||
Class<?>[] argumentsTypes, Throwable t) {
|
||||
ContextManager.activeSpan().log(t);
|
||||
}
|
||||
|
||||
private boolean isEntry() {
|
||||
return STACK_DEEP.get().getAndIncrement() == 0;
|
||||
}
|
||||
|
||||
private boolean isExit() {
|
||||
boolean isExit = STACK_DEEP.get().decrementAndGet() == 0;
|
||||
if (isExit) {
|
||||
STACK_DEEP.remove();
|
||||
}
|
||||
return isExit;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.skywalking.apm.plugin.spring.cloud.gateway.v3x.define;
|
||||
|
||||
import net.bytebuddy.description.method.MethodDescription;
|
||||
import net.bytebuddy.matcher.ElementMatcher;
|
||||
import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint;
|
||||
import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint;
|
||||
import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch;
|
||||
import org.apache.skywalking.apm.agent.core.plugin.match.HierarchyMatch;
|
||||
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
import static org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ArgumentTypeNameMatch.takesArgumentWithType;
|
||||
|
||||
public class GatewayFilterInstrumentation extends AbstractGatewayV3EnhancePluginDefine {
|
||||
|
||||
private static final String INTERCEPT_CLASS_GATEWAY_FILTER = "org.springframework.cloud.gateway.filter.GatewayFilter";
|
||||
private static final String GATEWAY_FILTER_INTERCEPTOR = "org.apache.skywalking.apm.plugin.spring.cloud.gateway.v3x.GatewayFilterInterceptor";
|
||||
|
||||
@Override
|
||||
protected ClassMatch enhanceClass() {
|
||||
return HierarchyMatch.byHierarchyMatch(INTERCEPT_CLASS_GATEWAY_FILTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
|
||||
return new ConstructorInterceptPoint[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
|
||||
return new InstanceMethodsInterceptPoint[] {
|
||||
new InstanceMethodsInterceptPoint() {
|
||||
@Override
|
||||
public ElementMatcher<MethodDescription> getMethodsMatcher() {
|
||||
return named("filter").and(
|
||||
takesArgumentWithType(0, "org.springframework.web.server.ServerWebExchange"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethodsInterceptor() {
|
||||
return GATEWAY_FILTER_INTERCEPTOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOverrideArgs() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -18,3 +18,4 @@ spring-cloud-gateway-3.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v
|
|||
spring-cloud-gateway-3.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v3x.define.NettyRoutingFilterInstrumentation
|
||||
spring-cloud-gateway-3.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v3x.define.ServerWebExchangeInstrumentation
|
||||
spring-cloud-gateway-3.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v3x.define.DispatcherHandlerInstrumentation
|
||||
spring-cloud-gateway-3.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v3x.define.GatewayFilterInstrumentation
|
||||
|
|
|
|||
|
|
@ -0,0 +1,332 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.apache.skywalking.apm.plugin.spring.cloud.gateway.v3x;
|
||||
|
||||
import org.apache.skywalking.apm.agent.core.context.ContextManager;
|
||||
import org.apache.skywalking.apm.agent.core.context.ContextSnapshot;
|
||||
import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
|
||||
import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan;
|
||||
import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer;
|
||||
import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment;
|
||||
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
|
||||
import org.apache.skywalking.apm.agent.test.helper.SegmentHelper;
|
||||
import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule;
|
||||
import org.apache.skywalking.apm.agent.test.tools.SegmentStorage;
|
||||
import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint;
|
||||
import org.apache.skywalking.apm.agent.test.tools.SpanAssert;
|
||||
import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner;
|
||||
import org.apache.skywalking.apm.network.trace.component.ComponentsDefine;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.i18n.LocaleContext;
|
||||
import org.springframework.http.codec.multipart.Part;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebSession;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
@RunWith(TracingSegmentRunner.class)
|
||||
public class GatewayFilterInterceptorTest {
|
||||
|
||||
private static final String GATEWAY_FILTER_INTERCEPTOR_LOCAL_SPAN_OPERATION_NAME = "SpringCloudGateway/GatewayFilter";
|
||||
|
||||
private static class ServerWebExchangeEnhancedInstance implements ServerWebExchange, EnhancedInstance {
|
||||
private ContextSnapshot snapshot;
|
||||
Map<String, Object> attributes = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public Object getSkyWalkingDynamicField() {
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSkyWalkingDynamicField(Object value) {
|
||||
this.snapshot = (ContextSnapshot) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerHttpRequest getRequest() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerHttpResponse getResponse() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<WebSession> getSession() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Principal> Mono<T> getPrincipal() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<MultiValueMap<String, String>> getFormData() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<MultiValueMap<String, Part>> getMultipartData() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocaleContext getLocaleContext() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApplicationContext getApplicationContext() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNotModified() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkNotModified(Instant instant) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkNotModified(String s) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkNotModified(String s, Instant instant) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String transformUrl(String s) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addUrlTransformer(Function<String, String> function) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLogPrefix() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private final ServerWebExchangeEnhancedInstance enhancedInstance = new ServerWebExchangeEnhancedInstance();
|
||||
private final GatewayFilterInterceptor interceptor = new GatewayFilterInterceptor();
|
||||
@Rule
|
||||
public AgentServiceRule serviceRule = new AgentServiceRule();
|
||||
@Rule
|
||||
public MockitoRule rule = MockitoJUnit.rule();
|
||||
|
||||
@SegmentStoragePoint
|
||||
private SegmentStorage segmentStorage;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
}
|
||||
|
||||
private final ServerWebExchange exchange = new ServerWebExchange() {
|
||||
Map<String, Object> attributes = new HashMap<>();
|
||||
@Override
|
||||
public ServerHttpRequest getRequest() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerHttpResponse getResponse() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<WebSession> getSession() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Principal> Mono<T> getPrincipal() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<MultiValueMap<String, String>> getFormData() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<MultiValueMap<String, Part>> getMultipartData() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocaleContext getLocaleContext() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApplicationContext getApplicationContext() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNotModified() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkNotModified(Instant instant) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkNotModified(String s) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkNotModified(String s, Instant instant) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String transformUrl(String s) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addUrlTransformer(Function<String, String> function) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLogPrefix() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
@Test
|
||||
public void testInterceptOnlyOnce() throws Throwable {
|
||||
interceptor.beforeMethod(null, null, new Object[]{exchange}, null, null);
|
||||
Assert.assertTrue(ContextManager.isActive());
|
||||
AbstractSpan activeSpan = ContextManager.activeSpan();
|
||||
Assert.assertTrue(activeSpan instanceof AbstractTracingSpan);
|
||||
Assert.assertEquals(activeSpan.getOperationName(), GATEWAY_FILTER_INTERCEPTOR_LOCAL_SPAN_OPERATION_NAME);
|
||||
|
||||
interceptor.afterMethod(null, null, null, null, null);
|
||||
Assert.assertFalse(ContextManager.isActive());
|
||||
final List<TraceSegment> traceSegments = segmentStorage.getTraceSegments();
|
||||
Assert.assertEquals(traceSegments.size(), 1);
|
||||
final List<AbstractTracingSpan> spans = SegmentHelper.getSpans(traceSegments.get(0));
|
||||
Assert.assertNotNull(spans);
|
||||
Assert.assertEquals(spans.size(), 1);
|
||||
SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNestedInterception() throws Throwable {
|
||||
interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null);
|
||||
Assert.assertTrue(ContextManager.isActive());
|
||||
|
||||
interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null);
|
||||
Assert.assertTrue(ContextManager.isActive());
|
||||
|
||||
interceptor.afterMethod(null, null, null, null, null);
|
||||
Assert.assertTrue(ContextManager.isActive());
|
||||
|
||||
interceptor.afterMethod(null, null, null, null, null);
|
||||
Assert.assertFalse(ContextManager.isActive());
|
||||
|
||||
final List<TraceSegment> traceSegments = segmentStorage.getTraceSegments();
|
||||
Assert.assertEquals(traceSegments.size(), 1);
|
||||
final List<AbstractTracingSpan> spans = SegmentHelper.getSpans(traceSegments.get(0));
|
||||
Assert.assertNotNull(spans);
|
||||
Assert.assertEquals(spans.size(), 1);
|
||||
SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithNullDynamicField() throws Throwable {
|
||||
interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null);
|
||||
interceptor.afterMethod(null, null, null, null, null);
|
||||
// no more need this, span was stopped at interceptor#afterMethod
|
||||
// ContextManager.stopSpan();
|
||||
final List<TraceSegment> traceSegments = segmentStorage.getTraceSegments();
|
||||
Assert.assertEquals(traceSegments.size(), 1);
|
||||
final List<AbstractTracingSpan> spans = SegmentHelper.getSpans(traceSegments.get(0));
|
||||
Assert.assertNotNull(spans);
|
||||
Assert.assertEquals(spans.size(), 1);
|
||||
SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithContextSnapshot() throws Throwable {
|
||||
final AbstractSpan entrySpan = ContextManager.createEntrySpan("/get", null);
|
||||
SpanLayer.asHttp(entrySpan);
|
||||
entrySpan.setComponent(ComponentsDefine.SPRING_WEBFLUX);
|
||||
enhancedInstance.setSkyWalkingDynamicField(ContextManager.capture());
|
||||
interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null);
|
||||
interceptor.afterMethod(null, null, null, null, null);
|
||||
// no more need this, span was stopped at interceptor#afterMethod
|
||||
// ContextManager.stopSpan();
|
||||
ContextManager.stopSpan(entrySpan);
|
||||
final List<TraceSegment> traceSegments = segmentStorage.getTraceSegments();
|
||||
Assert.assertEquals(traceSegments.size(), 1);
|
||||
final List<AbstractTracingSpan> spans = SegmentHelper.getSpans(traceSegments.get(0));
|
||||
Assert.assertNotNull(spans);
|
||||
Assert.assertEquals(spans.size(), 2);
|
||||
SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY);
|
||||
SpanAssert.assertComponent(spans.get(1), ComponentsDefine.SPRING_WEBFLUX);
|
||||
SpanAssert.assertLayer(spans.get(1), SpanLayer.HTTP);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x;
|
||||
|
||||
import org.apache.skywalking.apm.agent.core.context.ContextManager;
|
||||
import org.apache.skywalking.apm.agent.core.context.ContextSnapshot;
|
||||
import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
|
||||
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
|
||||
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
|
||||
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.ServerWebExchangeDecorator;
|
||||
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.apache.skywalking.apm.network.trace.component.ComponentsDefine.SPRING_CLOUD_GATEWAY;
|
||||
|
||||
public class GatewayFilterInterceptor implements InstanceMethodsAroundInterceptor {
|
||||
|
||||
private static final ThreadLocal<AtomicInteger> STACK_DEEP = ThreadLocal.withInitial(() -> new AtomicInteger(0));
|
||||
|
||||
@Override
|
||||
public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
|
||||
MethodInterceptResult result) throws Throwable {
|
||||
if (isEntry()) {
|
||||
ServerWebExchange exchange = (ServerWebExchange) allArguments[0];
|
||||
|
||||
EnhancedInstance enhancedInstance = getInstance(exchange);
|
||||
|
||||
AbstractSpan span = ContextManager.createLocalSpan("SpringCloudGateway/GatewayFilter");
|
||||
if (enhancedInstance != null && enhancedInstance.getSkyWalkingDynamicField() != null) {
|
||||
ContextManager.continued((ContextSnapshot) enhancedInstance.getSkyWalkingDynamicField());
|
||||
}
|
||||
span.setComponent(SPRING_CLOUD_GATEWAY);
|
||||
}
|
||||
}
|
||||
|
||||
public static EnhancedInstance getInstance(Object o) {
|
||||
EnhancedInstance instance = null;
|
||||
if (o instanceof DefaultServerWebExchange) {
|
||||
instance = (EnhancedInstance) o;
|
||||
} else if (o instanceof ServerWebExchangeDecorator) {
|
||||
ServerWebExchange delegate = ((ServerWebExchangeDecorator) o).getDelegate();
|
||||
return getInstance(delegate);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
|
||||
Object ret) throws Throwable {
|
||||
if (isExit()) {
|
||||
if (ContextManager.isActive()) {
|
||||
ContextManager.stopSpan();
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
|
||||
Class<?>[] argumentsTypes, Throwable t) {
|
||||
ContextManager.activeSpan().log(t);
|
||||
}
|
||||
|
||||
private boolean isEntry() {
|
||||
return STACK_DEEP.get().getAndIncrement() == 0;
|
||||
}
|
||||
|
||||
private boolean isExit() {
|
||||
boolean isExit = STACK_DEEP.get().decrementAndGet() == 0;
|
||||
if (isExit) {
|
||||
STACK_DEEP.remove();
|
||||
}
|
||||
return isExit;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define;
|
||||
|
||||
import net.bytebuddy.description.method.MethodDescription;
|
||||
import net.bytebuddy.matcher.ElementMatcher;
|
||||
import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint;
|
||||
import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint;
|
||||
import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch;
|
||||
import org.apache.skywalking.apm.agent.core.plugin.match.HierarchyMatch;
|
||||
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
import static org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ArgumentTypeNameMatch.takesArgumentWithType;
|
||||
|
||||
public class GatewayFilterInstrumentation extends AbstractGatewayV4EnhancePluginDefine {
|
||||
|
||||
private static final String INTERCEPT_CLASS_GATEWAY_FILTER = "org.springframework.cloud.gateway.filter.GatewayFilter";
|
||||
private static final String GATEWAY_FILTER_INTERCEPTOR = "org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.GatewayFilterInterceptor";
|
||||
|
||||
@Override
|
||||
protected ClassMatch enhanceClass() {
|
||||
return HierarchyMatch.byHierarchyMatch(INTERCEPT_CLASS_GATEWAY_FILTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
|
||||
return new ConstructorInterceptPoint[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
|
||||
return new InstanceMethodsInterceptPoint[] {
|
||||
new InstanceMethodsInterceptPoint() {
|
||||
@Override
|
||||
public ElementMatcher<MethodDescription> getMethodsMatcher() {
|
||||
return named("filter").and(
|
||||
takesArgumentWithType(0, "org.springframework.web.server.ServerWebExchange"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethodsInterceptor() {
|
||||
return GATEWAY_FILTER_INTERCEPTOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOverrideArgs() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -18,3 +18,4 @@ spring-cloud-gateway-4.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v
|
|||
spring-cloud-gateway-4.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define.NettyRoutingFilterInstrumentation
|
||||
spring-cloud-gateway-4.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define.ServerWebExchangeInstrumentation
|
||||
spring-cloud-gateway-4.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define.DispatcherHandlerInstrumentation
|
||||
spring-cloud-gateway-4.x=org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x.define.GatewayFilterInstrumentation
|
||||
|
|
|
|||
|
|
@ -0,0 +1,332 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.apache.skywalking.apm.plugin.spring.cloud.gateway.v4x;
|
||||
|
||||
import org.apache.skywalking.apm.agent.core.context.ContextManager;
|
||||
import org.apache.skywalking.apm.agent.core.context.ContextSnapshot;
|
||||
import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
|
||||
import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan;
|
||||
import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer;
|
||||
import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment;
|
||||
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
|
||||
import org.apache.skywalking.apm.agent.test.helper.SegmentHelper;
|
||||
import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule;
|
||||
import org.apache.skywalking.apm.agent.test.tools.SegmentStorage;
|
||||
import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint;
|
||||
import org.apache.skywalking.apm.agent.test.tools.SpanAssert;
|
||||
import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner;
|
||||
import org.apache.skywalking.apm.network.trace.component.ComponentsDefine;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.i18n.LocaleContext;
|
||||
import org.springframework.http.codec.multipart.Part;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebSession;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
@RunWith(TracingSegmentRunner.class)
|
||||
public class GatewayFilterInterceptorTest {
|
||||
|
||||
private static final String GATEWAY_FILTER_INTERCEPTOR_LOCAL_SPAN_OPERATION_NAME = "SpringCloudGateway/GatewayFilter";
|
||||
|
||||
private static class ServerWebExchangeEnhancedInstance implements ServerWebExchange, EnhancedInstance {
|
||||
private ContextSnapshot snapshot;
|
||||
Map<String, Object> attributes = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public Object getSkyWalkingDynamicField() {
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSkyWalkingDynamicField(Object value) {
|
||||
this.snapshot = (ContextSnapshot) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerHttpRequest getRequest() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerHttpResponse getResponse() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<WebSession> getSession() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Principal> Mono<T> getPrincipal() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<MultiValueMap<String, String>> getFormData() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<MultiValueMap<String, Part>> getMultipartData() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocaleContext getLocaleContext() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApplicationContext getApplicationContext() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNotModified() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkNotModified(Instant instant) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkNotModified(String s) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkNotModified(String s, Instant instant) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String transformUrl(String s) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addUrlTransformer(Function<String, String> function) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLogPrefix() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private final ServerWebExchangeEnhancedInstance enhancedInstance = new ServerWebExchangeEnhancedInstance();
|
||||
private final GatewayFilterInterceptor interceptor = new GatewayFilterInterceptor();
|
||||
@Rule
|
||||
public AgentServiceRule serviceRule = new AgentServiceRule();
|
||||
@Rule
|
||||
public MockitoRule rule = MockitoJUnit.rule();
|
||||
|
||||
@SegmentStoragePoint
|
||||
private SegmentStorage segmentStorage;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
}
|
||||
|
||||
private final ServerWebExchange exchange = new ServerWebExchange() {
|
||||
Map<String, Object> attributes = new HashMap<>();
|
||||
@Override
|
||||
public ServerHttpRequest getRequest() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerHttpResponse getResponse() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<WebSession> getSession() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Principal> Mono<T> getPrincipal() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<MultiValueMap<String, String>> getFormData() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<MultiValueMap<String, Part>> getMultipartData() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocaleContext getLocaleContext() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApplicationContext getApplicationContext() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNotModified() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkNotModified(Instant instant) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkNotModified(String s) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkNotModified(String s, Instant instant) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String transformUrl(String s) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addUrlTransformer(Function<String, String> function) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLogPrefix() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
@Test
|
||||
public void testInterceptOnlyOnce() throws Throwable {
|
||||
interceptor.beforeMethod(null, null, new Object[]{exchange}, null, null);
|
||||
Assert.assertTrue(ContextManager.isActive());
|
||||
AbstractSpan activeSpan = ContextManager.activeSpan();
|
||||
Assert.assertTrue(activeSpan instanceof AbstractTracingSpan);
|
||||
Assert.assertEquals(activeSpan.getOperationName(), GATEWAY_FILTER_INTERCEPTOR_LOCAL_SPAN_OPERATION_NAME);
|
||||
|
||||
interceptor.afterMethod(null, null, null, null, null);
|
||||
Assert.assertFalse(ContextManager.isActive());
|
||||
final List<TraceSegment> traceSegments = segmentStorage.getTraceSegments();
|
||||
Assert.assertEquals(traceSegments.size(), 1);
|
||||
final List<AbstractTracingSpan> spans = SegmentHelper.getSpans(traceSegments.get(0));
|
||||
Assert.assertNotNull(spans);
|
||||
Assert.assertEquals(spans.size(), 1);
|
||||
SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNestedInterception() throws Throwable {
|
||||
interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null);
|
||||
Assert.assertTrue(ContextManager.isActive());
|
||||
|
||||
interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null);
|
||||
Assert.assertTrue(ContextManager.isActive());
|
||||
|
||||
interceptor.afterMethod(null, null, null, null, null);
|
||||
Assert.assertTrue(ContextManager.isActive());
|
||||
|
||||
interceptor.afterMethod(null, null, null, null, null);
|
||||
Assert.assertFalse(ContextManager.isActive());
|
||||
|
||||
final List<TraceSegment> traceSegments = segmentStorage.getTraceSegments();
|
||||
Assert.assertEquals(traceSegments.size(), 1);
|
||||
final List<AbstractTracingSpan> spans = SegmentHelper.getSpans(traceSegments.get(0));
|
||||
Assert.assertNotNull(spans);
|
||||
Assert.assertEquals(spans.size(), 1);
|
||||
SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithNullDynamicField() throws Throwable {
|
||||
interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null);
|
||||
interceptor.afterMethod(null, null, null, null, null);
|
||||
// no more need this, span was stopped at interceptor#afterMethod
|
||||
// ContextManager.stopSpan();
|
||||
final List<TraceSegment> traceSegments = segmentStorage.getTraceSegments();
|
||||
Assert.assertEquals(traceSegments.size(), 1);
|
||||
final List<AbstractTracingSpan> spans = SegmentHelper.getSpans(traceSegments.get(0));
|
||||
Assert.assertNotNull(spans);
|
||||
Assert.assertEquals(spans.size(), 1);
|
||||
SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithContextSnapshot() throws Throwable {
|
||||
final AbstractSpan entrySpan = ContextManager.createEntrySpan("/get", null);
|
||||
SpanLayer.asHttp(entrySpan);
|
||||
entrySpan.setComponent(ComponentsDefine.SPRING_WEBFLUX);
|
||||
enhancedInstance.setSkyWalkingDynamicField(ContextManager.capture());
|
||||
interceptor.beforeMethod(null, null, new Object[]{enhancedInstance}, null, null);
|
||||
interceptor.afterMethod(null, null, null, null, null);
|
||||
// no more need this, span was stopped at interceptor#afterMethod
|
||||
// ContextManager.stopSpan();
|
||||
ContextManager.stopSpan(entrySpan);
|
||||
final List<TraceSegment> traceSegments = segmentStorage.getTraceSegments();
|
||||
Assert.assertEquals(traceSegments.size(), 1);
|
||||
final List<AbstractTracingSpan> spans = SegmentHelper.getSpans(traceSegments.get(0));
|
||||
Assert.assertNotNull(spans);
|
||||
Assert.assertEquals(spans.size(), 2);
|
||||
SpanAssert.assertComponent(spans.get(0), ComponentsDefine.SPRING_CLOUD_GATEWAY);
|
||||
SpanAssert.assertComponent(spans.get(1), ComponentsDefine.SPRING_WEBFLUX);
|
||||
SpanAssert.assertLayer(spans.get(1), SpanLayer.HTTP);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -34,8 +34,8 @@ segmentItems:
|
|||
- {key: http.method, value: GET}
|
||||
- {key: http.status_code, value: '200'}
|
||||
refs:
|
||||
- {parentEndpoint: SpringCloudGateway/RoutingFilter, networkAddress: 'localhost:18070',
|
||||
refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not
|
||||
- {parentEndpoint: SpringCloudGateway/GatewayFilter, networkAddress: 'localhost:18070',
|
||||
refType: CrossProcess, parentSpanId: 2, parentTraceSegmentId: not null, parentServiceInstance: not
|
||||
null, parentService: not null, traceId: not null}
|
||||
skipAnalysis: 'false'
|
||||
- serviceName: gateway-projectA-scenario
|
||||
|
|
@ -61,8 +61,8 @@ segmentItems:
|
|||
- segmentId: not null
|
||||
spans:
|
||||
- operationName: SpringCloudGateway/sendRequest
|
||||
parentSpanId: 0
|
||||
spanId: 1
|
||||
parentSpanId: 1
|
||||
spanId: 2
|
||||
spanLayer: Http
|
||||
startTime: nq 0
|
||||
endTime: nq 0
|
||||
|
|
@ -75,8 +75,8 @@ segmentItems:
|
|||
- {key: http.status_code, value: '200'}
|
||||
skipAnalysis: 'false'
|
||||
- operationName: SpringCloudGateway/RoutingFilter
|
||||
parentSpanId: -1
|
||||
spanId: 0
|
||||
parentSpanId: 0
|
||||
spanId: 1
|
||||
startTime: nq 0
|
||||
endTime: nq 0
|
||||
componentId: 61
|
||||
|
|
@ -88,3 +88,17 @@ segmentItems:
|
|||
parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not
|
||||
null, parentService: not null, traceId: not null}
|
||||
skipAnalysis: 'false'
|
||||
- operationName: SpringCloudGateway/GatewayFilter
|
||||
parentSpanId: -1
|
||||
spanId: 0
|
||||
startTime: nq 0
|
||||
endTime: nq 0
|
||||
componentId: 61
|
||||
isError: false
|
||||
spanType: Local
|
||||
peer: ''
|
||||
refs:
|
||||
- {parentEndpoint: '/provider/b/testcase', networkAddress: '', refType: CrossThread,
|
||||
parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not
|
||||
null, parentService: not null, traceId: not null}
|
||||
skipAnalysis: 'false'
|
||||
|
|
|
|||
|
|
@ -54,8 +54,8 @@ segmentItems:
|
|||
- {key: http.method, value: GET}
|
||||
- {key: http.status_code, value: '200'}
|
||||
refs:
|
||||
- {parentEndpoint: SpringCloudGateway/RoutingFilter, networkAddress: 'localhost:18070',
|
||||
refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not
|
||||
- {parentEndpoint: SpringCloudGateway/GatewayFilter, networkAddress: 'localhost:18070',
|
||||
refType: CrossProcess, parentSpanId: 2, parentTraceSegmentId: not null, parentServiceInstance: not
|
||||
null, parentService: not null, traceId: not null}
|
||||
skipAnalysis: 'false'
|
||||
- serviceName: gateway-projectA-scenario
|
||||
|
|
@ -76,8 +76,8 @@ segmentItems:
|
|||
- segmentId: not null
|
||||
spans:
|
||||
- operationName: SpringCloudGateway/sendRequest
|
||||
parentSpanId: 0
|
||||
spanId: 1
|
||||
parentSpanId: 1
|
||||
spanId: 2
|
||||
spanLayer: Http
|
||||
startTime: nq 0
|
||||
endTime: nq 0
|
||||
|
|
@ -95,8 +95,8 @@ segmentItems:
|
|||
- { key: message, value: not null }
|
||||
- { key: stack, value: not null }
|
||||
- operationName: SpringCloudGateway/RoutingFilter
|
||||
parentSpanId: -1
|
||||
spanId: 0
|
||||
parentSpanId: 0
|
||||
spanId: 1
|
||||
startTime: nq 0
|
||||
endTime: nq 0
|
||||
componentId: 61
|
||||
|
|
@ -106,11 +106,23 @@ segmentItems:
|
|||
parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not
|
||||
null, parentService: not null, traceId: not null }
|
||||
skipAnalysis: 'false'
|
||||
- operationName: SpringCloudGateway/GatewayFilter
|
||||
parentSpanId: -1
|
||||
spanId: 0
|
||||
startTime: nq 0
|
||||
endTime: nq 0
|
||||
componentId: 61
|
||||
spanType: Local
|
||||
refs:
|
||||
- { parentEndpoint: '/provider/timeout/error', networkAddress: '', refType: CrossThread,
|
||||
parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not
|
||||
null, parentService: not null, traceId: not null }
|
||||
skipAnalysis: 'false'
|
||||
- segmentId: not null
|
||||
spans:
|
||||
- operationName: SpringCloudGateway/sendRequest
|
||||
parentSpanId: 0
|
||||
spanId: 1
|
||||
parentSpanId: 1
|
||||
spanId: 2
|
||||
spanLayer: Http
|
||||
startTime: nq 0
|
||||
endTime: nq 0
|
||||
|
|
@ -123,8 +135,8 @@ segmentItems:
|
|||
- {key: http.status_code, value: '200'}
|
||||
skipAnalysis: 'false'
|
||||
- operationName: SpringCloudGateway/RoutingFilter
|
||||
parentSpanId: -1
|
||||
spanId: 0
|
||||
parentSpanId: 0
|
||||
spanId: 1
|
||||
startTime: nq 0
|
||||
endTime: nq 0
|
||||
componentId: 61
|
||||
|
|
@ -136,6 +148,20 @@ segmentItems:
|
|||
parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not
|
||||
null, parentService: not null, traceId: not null}
|
||||
skipAnalysis: 'false'
|
||||
- operationName: SpringCloudGateway/GatewayFilter
|
||||
parentSpanId: -1
|
||||
spanId: 0
|
||||
startTime: nq 0
|
||||
endTime: nq 0
|
||||
componentId: 61
|
||||
isError: false
|
||||
spanType: Local
|
||||
peer: ''
|
||||
refs:
|
||||
- {parentEndpoint: '/provider/b/testcase', networkAddress: '', refType: CrossThread,
|
||||
parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not
|
||||
null, parentService: not null, traceId: not null}
|
||||
skipAnalysis: 'false'
|
||||
- segmentId: not null
|
||||
spans:
|
||||
- operationName: /provider/b/testcase
|
||||
|
|
|
|||
|
|
@ -43,8 +43,8 @@ segmentItems:
|
|||
- {key: http.method, value: GET}
|
||||
- {key: http.status_code, value: '200'}
|
||||
refs:
|
||||
- {parentEndpoint: SpringCloudGateway/RoutingFilter, networkAddress: 'localhost:18070',
|
||||
refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not
|
||||
- {parentEndpoint: SpringCloudGateway/GatewayFilter, networkAddress: 'localhost:18070',
|
||||
refType: CrossProcess, parentSpanId: 2, parentTraceSegmentId: not null, parentServiceInstance: not
|
||||
null, parentService: not null, traceId: not null}
|
||||
skipAnalysis: 'false'
|
||||
- serviceName: gateway-projectA-scenario
|
||||
|
|
|
|||
|
|
@ -54,8 +54,8 @@ segmentItems:
|
|||
- {key: http.method, value: GET}
|
||||
- {key: http.status_code, value: '200'}
|
||||
refs:
|
||||
- {parentEndpoint: SpringCloudGateway/RoutingFilter, networkAddress: 'localhost:18070',
|
||||
refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not
|
||||
- {parentEndpoint: SpringCloudGateway/GatewayFilter, networkAddress: 'localhost:18070',
|
||||
refType: CrossProcess, parentSpanId: 2, parentTraceSegmentId: not null, parentServiceInstance: not
|
||||
null, parentService: not null, traceId: not null}
|
||||
skipAnalysis: 'false'
|
||||
- serviceName: gateway-projectA-scenario
|
||||
|
|
@ -76,8 +76,8 @@ segmentItems:
|
|||
- segmentId: not null
|
||||
spans:
|
||||
- operationName: SpringCloudGateway/sendRequest
|
||||
parentSpanId: 0
|
||||
spanId: 1
|
||||
parentSpanId: 1
|
||||
spanId: 2
|
||||
spanLayer: Http
|
||||
startTime: nq 0
|
||||
endTime: nq 0
|
||||
|
|
@ -95,8 +95,8 @@ segmentItems:
|
|||
- { key: message, value: not null }
|
||||
- { key: stack, value: not null}
|
||||
- operationName: SpringCloudGateway/RoutingFilter
|
||||
parentSpanId: -1
|
||||
spanId: 0
|
||||
parentSpanId: 0
|
||||
spanId: 1
|
||||
startTime: nq 0
|
||||
endTime: nq 0
|
||||
componentId: 61
|
||||
|
|
@ -106,11 +106,23 @@ segmentItems:
|
|||
parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not
|
||||
null, parentService: not null, traceId: not null }
|
||||
skipAnalysis: 'false'
|
||||
- operationName: SpringCloudGateway/GatewayFilter
|
||||
parentSpanId: -1
|
||||
spanId: 0
|
||||
startTime: nq 0
|
||||
endTime: nq 0
|
||||
componentId: 61
|
||||
spanType: Local
|
||||
refs:
|
||||
- { parentEndpoint: '/provider/timeout/error', networkAddress: '', refType: CrossThread,
|
||||
parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not
|
||||
null, parentService: not null, traceId: not null }
|
||||
skipAnalysis: 'false'
|
||||
- segmentId: not null
|
||||
spans:
|
||||
- operationName: SpringCloudGateway/sendRequest
|
||||
parentSpanId: 0
|
||||
spanId: 1
|
||||
parentSpanId: 1
|
||||
spanId: 2
|
||||
spanLayer: Http
|
||||
startTime: nq 0
|
||||
endTime: nq 0
|
||||
|
|
@ -123,8 +135,8 @@ segmentItems:
|
|||
- {key: http.status_code, value: '200'}
|
||||
skipAnalysis: 'false'
|
||||
- operationName: SpringCloudGateway/RoutingFilter
|
||||
parentSpanId: -1
|
||||
spanId: 0
|
||||
parentSpanId: 0
|
||||
spanId: 1
|
||||
startTime: nq 0
|
||||
endTime: nq 0
|
||||
componentId: 61
|
||||
|
|
@ -136,6 +148,20 @@ segmentItems:
|
|||
parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not
|
||||
null, parentService: not null, traceId: not null}
|
||||
skipAnalysis: 'false'
|
||||
- operationName: SpringCloudGateway/GatewayFilter
|
||||
parentSpanId: -1
|
||||
spanId: 0
|
||||
startTime: nq 0
|
||||
endTime: nq 0
|
||||
componentId: 61
|
||||
isError: false
|
||||
spanType: Local
|
||||
peer: ''
|
||||
refs:
|
||||
- {parentEndpoint: '/provider/b/testcase', networkAddress: '', refType: CrossThread,
|
||||
parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not
|
||||
null, parentService: not null, traceId: not null}
|
||||
skipAnalysis: 'false'
|
||||
- segmentId: not null
|
||||
spans:
|
||||
- operationName: /provider/b/testcase
|
||||
|
|
|
|||
|
|
@ -54,8 +54,8 @@ segmentItems:
|
|||
- {key: http.method, value: GET}
|
||||
- {key: http.status_code, value: '200'}
|
||||
refs:
|
||||
- {parentEndpoint: SpringCloudGateway/RoutingFilter, networkAddress: 'localhost:18070',
|
||||
refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not
|
||||
- {parentEndpoint: SpringCloudGateway/GatewayFilter, networkAddress: 'localhost:18070',
|
||||
refType: CrossProcess, parentSpanId: 2, parentTraceSegmentId: not null, parentServiceInstance: not
|
||||
null, parentService: not null, traceId: not null}
|
||||
skipAnalysis: 'false'
|
||||
- serviceName: gateway-projectA-scenario
|
||||
|
|
@ -76,8 +76,8 @@ segmentItems:
|
|||
- segmentId: not null
|
||||
spans:
|
||||
- operationName: SpringCloudGateway/sendRequest
|
||||
parentSpanId: 0
|
||||
spanId: 1
|
||||
parentSpanId: 1
|
||||
spanId: 2
|
||||
spanLayer: Http
|
||||
startTime: nq 0
|
||||
endTime: nq 0
|
||||
|
|
@ -95,8 +95,8 @@ segmentItems:
|
|||
- { key: message, value: not null }
|
||||
- { key: stack, value: not null}
|
||||
- operationName: SpringCloudGateway/RoutingFilter
|
||||
parentSpanId: -1
|
||||
spanId: 0
|
||||
parentSpanId: 0
|
||||
spanId: 1
|
||||
startTime: nq 0
|
||||
endTime: nq 0
|
||||
componentId: 61
|
||||
|
|
@ -106,11 +106,23 @@ segmentItems:
|
|||
parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not
|
||||
null, parentService: not null, traceId: not null }
|
||||
skipAnalysis: 'false'
|
||||
- operationName: SpringCloudGateway/GatewayFilter
|
||||
parentSpanId: -1
|
||||
spanId: 0
|
||||
startTime: nq 0
|
||||
endTime: nq 0
|
||||
componentId: 61
|
||||
spanType: Local
|
||||
refs:
|
||||
- { parentEndpoint: '/provider/timeout/error', networkAddress: '', refType: CrossThread,
|
||||
parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not
|
||||
null, parentService: not null, traceId: not null }
|
||||
skipAnalysis: 'false'
|
||||
- segmentId: not null
|
||||
spans:
|
||||
- operationName: SpringCloudGateway/sendRequest
|
||||
parentSpanId: 0
|
||||
spanId: 1
|
||||
parentSpanId: 1
|
||||
spanId: 2
|
||||
spanLayer: Http
|
||||
startTime: nq 0
|
||||
endTime: nq 0
|
||||
|
|
@ -123,8 +135,8 @@ segmentItems:
|
|||
- {key: http.status_code, value: '200'}
|
||||
skipAnalysis: 'false'
|
||||
- operationName: SpringCloudGateway/RoutingFilter
|
||||
parentSpanId: -1
|
||||
spanId: 0
|
||||
parentSpanId: 0
|
||||
spanId: 1
|
||||
startTime: nq 0
|
||||
endTime: nq 0
|
||||
componentId: 61
|
||||
|
|
@ -136,6 +148,20 @@ segmentItems:
|
|||
parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not
|
||||
null, parentService: not null, traceId: not null}
|
||||
skipAnalysis: 'false'
|
||||
- operationName: SpringCloudGateway/GatewayFilter
|
||||
parentSpanId: -1
|
||||
spanId: 0
|
||||
startTime: nq 0
|
||||
endTime: nq 0
|
||||
componentId: 61
|
||||
isError: false
|
||||
spanType: Local
|
||||
peer: ''
|
||||
refs:
|
||||
- {parentEndpoint: '/provider/b/testcase', networkAddress: '', refType: CrossThread,
|
||||
parentSpanId: 0, parentTraceSegmentId: not null, parentServiceInstance: not
|
||||
null, parentService: not null, traceId: not null}
|
||||
skipAnalysis: 'false'
|
||||
- segmentId: not null
|
||||
spans:
|
||||
- operationName: /provider/b/testcase
|
||||
|
|
|
|||
Loading…
Reference in New Issue