From 77d43ca1b3f04c11fe45dfbcf29744aeb99859a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BD=AD=E5=8B=87=E5=8D=87=20Buddha?= <8082209@qq.com> Date: Sat, 3 Jun 2017 17:39:49 +0800 Subject: [PATCH] add feign default http plugin (#205) * add feign default http plugin add test case for feign default http interceptor * move feign and okhttp plugins to the parent moudle --- apm-sniffer/apm-agent/pom.xml | 5 + .../pom.xml | 20 ++- .../http/v9/DefaultHttpClientInterceptor.java | 110 ++++++++++++++ .../DefaultHttpClientInstrumentation.java | 53 +++++++ .../src/main/resources/skywalking-plugin.def | 1 + .../v9/DefaultHttpClientInterceptorTest.java | 135 ++++++++++++++++++ .../okhttp-3.x-plugin/pom.xml | 8 +- .../plugin/okhttp/v3/RealCallInterceptor.java | 0 .../v3/define/RealCallInstrumentation.java | 0 .../src/main/resources/skywalking-plugin.def | 0 .../okhttp/v3/RealCallInterceptorTest.java | 0 apm-sniffer/apm-sdk-plugin/pom.xml | 3 +- 12 files changed, 321 insertions(+), 14 deletions(-) rename apm-sniffer/apm-sdk-plugin/{http-plugin => feign-default-http-9.x-plugin}/pom.xml (52%) create mode 100644 apm-sniffer/apm-sdk-plugin/feign-default-http-9.x-plugin/src/main/java/org/skywalking/apm/plugin/feign/http/v9/DefaultHttpClientInterceptor.java create mode 100644 apm-sniffer/apm-sdk-plugin/feign-default-http-9.x-plugin/src/main/java/org/skywalking/apm/plugin/feign/http/v9/define/DefaultHttpClientInstrumentation.java create mode 100644 apm-sniffer/apm-sdk-plugin/feign-default-http-9.x-plugin/src/main/resources/skywalking-plugin.def create mode 100644 apm-sniffer/apm-sdk-plugin/feign-default-http-9.x-plugin/src/test/java/org/skywalking/apm/plugin/feign/http/v9/DefaultHttpClientInterceptorTest.java rename apm-sniffer/apm-sdk-plugin/{http-plugin => }/okhttp-3.x-plugin/pom.xml (76%) rename apm-sniffer/apm-sdk-plugin/{http-plugin => }/okhttp-3.x-plugin/src/main/java/org/skywalking/apm/plugin/okhttp/v3/RealCallInterceptor.java (100%) rename apm-sniffer/apm-sdk-plugin/{http-plugin => }/okhttp-3.x-plugin/src/main/java/org/skywalking/apm/plugin/okhttp/v3/define/RealCallInstrumentation.java (100%) rename apm-sniffer/apm-sdk-plugin/{http-plugin => }/okhttp-3.x-plugin/src/main/resources/skywalking-plugin.def (100%) rename apm-sniffer/apm-sdk-plugin/{http-plugin => }/okhttp-3.x-plugin/src/test/java/org/skywalking/apm/plugin/okhttp/v3/RealCallInterceptorTest.java (100%) diff --git a/apm-sniffer/apm-agent/pom.xml b/apm-sniffer/apm-agent/pom.xml index 4299ac757d..4d1875c17f 100644 --- a/apm-sniffer/apm-agent/pom.xml +++ b/apm-sniffer/apm-agent/pom.xml @@ -75,6 +75,11 @@ apm-okhttp-3.x-plugin ${project.version} + + org.skywalking + apm-feign-default-http-9.x-plugin + ${project.version} + diff --git a/apm-sniffer/apm-sdk-plugin/http-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/feign-default-http-9.x-plugin/pom.xml similarity index 52% rename from apm-sniffer/apm-sdk-plugin/http-plugin/pom.xml rename to apm-sniffer/apm-sdk-plugin/feign-default-http-9.x-plugin/pom.xml index 38a019e78c..43503165fb 100644 --- a/apm-sniffer/apm-sdk-plugin/http-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/feign-default-http-9.x-plugin/pom.xml @@ -3,16 +3,24 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - apm-sdk-plugin org.skywalking + apm-sdk-plugin 3.1-2017 4.0.0 - http-plugin - pom - - okhttp-3.x-plugin - + apm-feign-default-http-9.x-plugin + jar + + + 9.5.0 + + + + io.github.openfeign + feign-core + ${feign.version} + + \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/feign-default-http-9.x-plugin/src/main/java/org/skywalking/apm/plugin/feign/http/v9/DefaultHttpClientInterceptor.java b/apm-sniffer/apm-sdk-plugin/feign-default-http-9.x-plugin/src/main/java/org/skywalking/apm/plugin/feign/http/v9/DefaultHttpClientInterceptor.java new file mode 100644 index 0000000000..ea65ecce24 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/feign-default-http-9.x-plugin/src/main/java/org/skywalking/apm/plugin/feign/http/v9/DefaultHttpClientInterceptor.java @@ -0,0 +1,110 @@ +package org.skywalking.apm.plugin.feign.http.v9; + +import feign.Request; +import feign.Response; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.skywalking.apm.agent.core.conf.Config; +import org.skywalking.apm.agent.core.context.ContextCarrier; +import org.skywalking.apm.agent.core.context.ContextManager; +import org.skywalking.apm.agent.core.plugin.interceptor.EnhancedClassInstanceContext; +import org.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodInvokeContext; +import org.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.skywalking.apm.trace.Span; +import org.skywalking.apm.trace.tag.Tags; + +/** + * {@link DefaultHttpClientInterceptor} intercept the default implementation of http calls by the Feign. + * + * @author pengys5 + */ +public class DefaultHttpClientInterceptor implements InstanceMethodsAroundInterceptor { + + private static final String COMPONENT_NAME = "FeignDefaultHttp"; + + /** + * Get the {@link feign.Request} from {@link EnhancedClassInstanceContext}, then create {@link Span} and set host, + * port, kind, component, url from {@link feign.Request}. + * Through the reflection of the way, set the http header of context data into {@link feign.Request#headers}. + * + * @param context instance context, a class instance only has one {@link EnhancedClassInstanceContext} instance. + * @param interceptorContext method context, includes class name, method name, etc. + * @param result change this result, if you want to truncate the method. + * @throws Throwable + */ + @Override + public void beforeMethod(EnhancedClassInstanceContext context, InstanceMethodInvokeContext interceptorContext, + MethodInterceptResult result) throws Throwable { + Request request = (Request)interceptorContext.allArguments()[0]; + + URL url = new URL(request.url()); + Span span = ContextManager.createSpan(request.url()); + Tags.PEER_PORT.set(span, url.getPort()); + Tags.PEER_HOST.set(span, url.getHost()); + Tags.SPAN_KIND.set(span, Tags.SPAN_KIND_CLIENT); + Tags.COMPONENT.set(span, COMPONENT_NAME); + Tags.HTTP.METHOD.set(span, request.method()); + Tags.URL.set(span, url.getPath()); + Tags.SPAN_LAYER.asHttp(span); + + ContextCarrier contextCarrier = new ContextCarrier(); + ContextManager.inject(contextCarrier); + + List contextCollection = new ArrayList(); + contextCollection.add(contextCarrier.serialize()); + + Field headersField = Request.class.getDeclaredField("headers"); + Field modifiersField = Field.class.getDeclaredField("modifiers"); + modifiersField.setAccessible(true); + modifiersField.setInt(headersField, headersField.getModifiers() & ~Modifier.FINAL); + + headersField.setAccessible(true); + Map> headers = new LinkedHashMap>(); + headers.put(Config.Plugin.Http.HEADER_NAME_OF_CONTEXT_DATA, contextCollection); + headers.putAll(request.headers()); + + headersField.set(request, Collections.unmodifiableMap(headers)); + } + + /** + * Get the status code from {@link Response}, when status code greater than 400, it means there was some errors in + * the server. + * Finish the {@link Span}. + * + * @param context instance context, a class instance only has one {@link EnhancedClassInstanceContext} instance. + * @param interceptorContext method context, includes class name, method name, etc. + * @param ret the method's original return value. + * @return + * @throws Throwable + */ + @Override + public Object afterMethod(EnhancedClassInstanceContext context, InstanceMethodInvokeContext interceptorContext, + Object ret) throws Throwable { + Response response = (Response)ret; + int statusCode = response.status(); + + Span span = ContextManager.activeSpan(); + if (statusCode >= 400) { + Tags.ERROR.set(span, true); + } + + Tags.STATUS_CODE.set(span, statusCode); + ContextManager.stopSpan(); + + return ret; + } + + @Override public void handleMethodException(Throwable t, EnhancedClassInstanceContext context, + InstanceMethodInvokeContext interceptorContext) { + Tags.ERROR.set(ContextManager.activeSpan(), true); + ContextManager.activeSpan().log(t); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/feign-default-http-9.x-plugin/src/main/java/org/skywalking/apm/plugin/feign/http/v9/define/DefaultHttpClientInstrumentation.java b/apm-sniffer/apm-sdk-plugin/feign-default-http-9.x-plugin/src/main/java/org/skywalking/apm/plugin/feign/http/v9/define/DefaultHttpClientInstrumentation.java new file mode 100644 index 0000000000..ed675756eb --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/feign-default-http-9.x-plugin/src/main/java/org/skywalking/apm/plugin/feign/http/v9/define/DefaultHttpClientInstrumentation.java @@ -0,0 +1,53 @@ +package org.skywalking.apm.plugin.feign.http.v9.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.skywalking.apm.plugin.feign.http.v9.DefaultHttpClientInterceptor; + +import static net.bytebuddy.matcher.ElementMatchers.named; + +/** + * {@link DefaultHttpClientInstrumentation} presents that skywalking intercepts {@link + * feign.Client.Default#execute(feign.Request, feign.Request.Options)} by using {@link DefaultHttpClientInterceptor}. + * If feign did't run in default mode, the instrumentation depend on the http client implementation. + * e.g. okhttp client implementation depend on okhttp-plugin. + * + * @author pengys5 + */ +public class DefaultHttpClientInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + /** + * Enhance class. + */ + private static final String ENHANCE_CLASS = "feign.Client$Default"; + + /** + * Intercept class. + */ + private static final String INTERCEPT_CLASS = "org.skywalking.apm.plugin.feign.http.v9.DefaultHttpClientInterceptor"; + + @Override protected String enhanceClassName() { + return ENHANCE_CLASS; + } + + @Override protected ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override protected InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[] { + new InstanceMethodsInterceptPoint() { + @Override public ElementMatcher getMethodsMatcher() { + return named("execute"); + } + + @Override public String getMethodsInterceptor() { + return INTERCEPT_CLASS; + } + } + }; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/feign-default-http-9.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/feign-default-http-9.x-plugin/src/main/resources/skywalking-plugin.def new file mode 100644 index 0000000000..060bcdc6fa --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/feign-default-http-9.x-plugin/src/main/resources/skywalking-plugin.def @@ -0,0 +1 @@ +feign-default-http-9.x=org.skywalking.apm.plugin.feign.http.v9.define.DefaultHttpClientInstrumentation \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/feign-default-http-9.x-plugin/src/test/java/org/skywalking/apm/plugin/feign/http/v9/DefaultHttpClientInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/feign-default-http-9.x-plugin/src/test/java/org/skywalking/apm/plugin/feign/http/v9/DefaultHttpClientInterceptorTest.java new file mode 100644 index 0000000000..3d9200e4ec --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/feign-default-http-9.x-plugin/src/test/java/org/skywalking/apm/plugin/feign/http/v9/DefaultHttpClientInterceptorTest.java @@ -0,0 +1,135 @@ +package org.skywalking.apm.plugin.feign.http.v9; + +import feign.Request; +import feign.Response; +import java.nio.charset.Charset; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.skywalking.apm.agent.core.boot.ServiceManager; +import org.skywalking.apm.agent.core.context.TracerContext; +import org.skywalking.apm.agent.core.plugin.interceptor.EnhancedClassInstanceContext; +import org.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodInvokeContext; +import org.skywalking.apm.sniffer.mock.context.MockTracerContextListener; +import org.skywalking.apm.sniffer.mock.context.SegmentAssert; +import org.skywalking.apm.trace.Span; +import org.skywalking.apm.trace.TraceSegment; +import org.skywalking.apm.trace.tag.Tags; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * @author pengys5 + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({Response.class}) +public class DefaultHttpClientInterceptorTest { + + private DefaultHttpClientInterceptor defaultHttpClientInterceptor; + private MockTracerContextListener mockTracerContextListener; + + private EnhancedClassInstanceContext classInstanceContext; + + @Mock + private InstanceMethodInvokeContext instanceMethodInvokeContext; + + private Request request; + + @Before + public void setUp() throws Exception { + mockTracerContextListener = new MockTracerContextListener(); + + classInstanceContext = new EnhancedClassInstanceContext(); + + Map> headers = new LinkedHashMap>(); + request = Request.create("GET", "http://skywalking.org", headers, "Test".getBytes(), Charset.forName("UTF-8")); + + Request.Options options = new Request.Options(); + + Object[] allArguments = {request, options}; + when(instanceMethodInvokeContext.allArguments()).thenReturn(allArguments); + + ServiceManager.INSTANCE.boot(); + defaultHttpClientInterceptor = new DefaultHttpClientInterceptor(); + + TracerContext.ListenerManager.add(mockTracerContextListener); + } + + @Test + public void testMethodsAround() throws Throwable { + defaultHttpClientInterceptor.beforeMethod(classInstanceContext, instanceMethodInvokeContext, null); + + Response response = mock(Response.class); + when(response.status()).thenReturn(200); + defaultHttpClientInterceptor.afterMethod(classInstanceContext, instanceMethodInvokeContext, response); + + mockTracerContextListener.assertSize(1); + mockTracerContextListener.assertTraceSegment(0, new SegmentAssert() { + @Override public void call(TraceSegment finishedSegment) { + Assert.assertEquals(1, finishedSegment.getSpans().size()); + assertSpan(finishedSegment.getSpans().get(0)); + Assert.assertEquals(false, Tags.ERROR.get(finishedSegment.getSpans().get(0))); + } + }); + } + + @Test + public void testMethodsAroundError() throws Throwable { + defaultHttpClientInterceptor.beforeMethod(classInstanceContext, instanceMethodInvokeContext, null); + + Response response = mock(Response.class); + when(response.status()).thenReturn(404); + defaultHttpClientInterceptor.afterMethod(classInstanceContext, instanceMethodInvokeContext, response); + + mockTracerContextListener.assertSize(1); + mockTracerContextListener.assertTraceSegment(0, new SegmentAssert() { + @Override public void call(TraceSegment finishedSegment) { + Assert.assertEquals(1, finishedSegment.getSpans().size()); + assertSpan(finishedSegment.getSpans().get(0)); + Assert.assertEquals(true, Tags.ERROR.get(finishedSegment.getSpans().get(0))); + } + }); + } + + private void assertSpan(Span span) { + Assert.assertEquals("http", Tags.SPAN_LAYER.get(span)); + Assert.assertEquals("GET", Tags.HTTP.METHOD.get(span)); + Assert.assertEquals("skywalking.org", Tags.PEER_HOST.get(span)); + Assert.assertEquals(-1, Tags.PEER_PORT.get(span).intValue()); + Assert.assertEquals("FeignDefaultHttp", Tags.COMPONENT.get(span)); + Assert.assertEquals("client", Tags.SPAN_KIND.get(span)); + Assert.assertEquals("", Tags.URL.get(span)); + } + + @Test + public void testException() throws Throwable { + defaultHttpClientInterceptor.beforeMethod(classInstanceContext, instanceMethodInvokeContext, null); + + defaultHttpClientInterceptor.handleMethodException(new NullPointerException("testException"), classInstanceContext, null); + + Response response = mock(Response.class); + when(response.status()).thenReturn(200); + defaultHttpClientInterceptor.afterMethod(classInstanceContext, instanceMethodInvokeContext, response); + + mockTracerContextListener.assertSize(1); + mockTracerContextListener.assertTraceSegment(0, new SegmentAssert() { + @Override public void call(TraceSegment finishedSegment) { + Assert.assertEquals(1, finishedSegment.getSpans().size()); + assertSpan(finishedSegment.getSpans().get(0)); + Assert.assertEquals(true, Tags.ERROR.get(finishedSegment.getSpans().get(0))); + + Assert.assertEquals(1, finishedSegment.getSpans().get(0).getLogs().size()); + Assert.assertEquals(true, finishedSegment.getSpans().get(0).getLogs().get(0).getFields().containsKey("stack")); + Assert.assertEquals("testException", finishedSegment.getSpans().get(0).getLogs().get(0).getFields().get("message")); + } + }); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/http-plugin/okhttp-3.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/okhttp-3.x-plugin/pom.xml similarity index 76% rename from apm-sniffer/apm-sdk-plugin/http-plugin/okhttp-3.x-plugin/pom.xml rename to apm-sniffer/apm-sdk-plugin/okhttp-3.x-plugin/pom.xml index bfe0848a7b..1831a098b3 100644 --- a/apm-sniffer/apm-sdk-plugin/http-plugin/okhttp-3.x-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/okhttp-3.x-plugin/pom.xml @@ -3,8 +3,8 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - http-plugin org.skywalking + apm-sdk-plugin 3.1-2017 4.0.0 @@ -24,11 +24,5 @@ ${okhttp.version} provided - - com.squareup.okhttp3 - mockwebserver - 3.7.0 - test - \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/http-plugin/okhttp-3.x-plugin/src/main/java/org/skywalking/apm/plugin/okhttp/v3/RealCallInterceptor.java b/apm-sniffer/apm-sdk-plugin/okhttp-3.x-plugin/src/main/java/org/skywalking/apm/plugin/okhttp/v3/RealCallInterceptor.java similarity index 100% rename from apm-sniffer/apm-sdk-plugin/http-plugin/okhttp-3.x-plugin/src/main/java/org/skywalking/apm/plugin/okhttp/v3/RealCallInterceptor.java rename to apm-sniffer/apm-sdk-plugin/okhttp-3.x-plugin/src/main/java/org/skywalking/apm/plugin/okhttp/v3/RealCallInterceptor.java diff --git a/apm-sniffer/apm-sdk-plugin/http-plugin/okhttp-3.x-plugin/src/main/java/org/skywalking/apm/plugin/okhttp/v3/define/RealCallInstrumentation.java b/apm-sniffer/apm-sdk-plugin/okhttp-3.x-plugin/src/main/java/org/skywalking/apm/plugin/okhttp/v3/define/RealCallInstrumentation.java similarity index 100% rename from apm-sniffer/apm-sdk-plugin/http-plugin/okhttp-3.x-plugin/src/main/java/org/skywalking/apm/plugin/okhttp/v3/define/RealCallInstrumentation.java rename to apm-sniffer/apm-sdk-plugin/okhttp-3.x-plugin/src/main/java/org/skywalking/apm/plugin/okhttp/v3/define/RealCallInstrumentation.java diff --git a/apm-sniffer/apm-sdk-plugin/http-plugin/okhttp-3.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/okhttp-3.x-plugin/src/main/resources/skywalking-plugin.def similarity index 100% rename from apm-sniffer/apm-sdk-plugin/http-plugin/okhttp-3.x-plugin/src/main/resources/skywalking-plugin.def rename to apm-sniffer/apm-sdk-plugin/okhttp-3.x-plugin/src/main/resources/skywalking-plugin.def diff --git a/apm-sniffer/apm-sdk-plugin/http-plugin/okhttp-3.x-plugin/src/test/java/org/skywalking/apm/plugin/okhttp/v3/RealCallInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/okhttp-3.x-plugin/src/test/java/org/skywalking/apm/plugin/okhttp/v3/RealCallInterceptorTest.java similarity index 100% rename from apm-sniffer/apm-sdk-plugin/http-plugin/okhttp-3.x-plugin/src/test/java/org/skywalking/apm/plugin/okhttp/v3/RealCallInterceptorTest.java rename to apm-sniffer/apm-sdk-plugin/okhttp-3.x-plugin/src/test/java/org/skywalking/apm/plugin/okhttp/v3/RealCallInterceptorTest.java diff --git a/apm-sniffer/apm-sdk-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/pom.xml index 2795f85f0f..439254ff68 100644 --- a/apm-sniffer/apm-sdk-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/pom.xml @@ -18,7 +18,8 @@ tomcat-7.x-8.x-plugin motan-plugin mongodb-3.x-plugin - http-plugin + feign-default-http-9.x-plugin + okhttp-3.x-plugin resin-3.x-plugin resin-4.x-plugin -- GitLab