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
This commit is contained in:
parent
f2e3359573
commit
77d43ca1b3
|
|
@ -75,6 +75,11 @@
|
|||
<artifactId>apm-okhttp-3.x-plugin</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.skywalking</groupId>
|
||||
<artifactId>apm-feign-default-http-9.x-plugin</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- activation -->
|
||||
<dependency>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
<parent>
|
||||
<artifactId>apm-sdk-plugin</artifactId>
|
||||
<groupId>org.skywalking</groupId>
|
||||
<artifactId>apm-sdk-plugin</artifactId>
|
||||
<version>3.1-2017</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>http-plugin</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<modules>
|
||||
<module>okhttp-3.x-plugin</module>
|
||||
</modules>
|
||||
<artifactId>apm-feign-default-http-9.x-plugin</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<feign.version>9.5.0</feign.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-core</artifactId>
|
||||
<version>${feign.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
@ -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<String> contextCollection = new ArrayList<String>();
|
||||
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<String, Collection<String>> headers = new LinkedHashMap<String, Collection<String>>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<MethodDescription> getMethodsMatcher() {
|
||||
return named("execute");
|
||||
}
|
||||
|
||||
@Override public String getMethodsInterceptor() {
|
||||
return INTERCEPT_CLASS;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
feign-default-http-9.x=org.skywalking.apm.plugin.feign.http.v9.define.DefaultHttpClientInstrumentation
|
||||
|
|
@ -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<String, Collection<String>> headers = new LinkedHashMap<String, Collection<String>>();
|
||||
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"));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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">
|
||||
<parent>
|
||||
<artifactId>http-plugin</artifactId>
|
||||
<groupId>org.skywalking</groupId>
|
||||
<artifactId>apm-sdk-plugin</artifactId>
|
||||
<version>3.1-2017</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
|
@ -24,11 +24,5 @@
|
|||
<version>${okhttp.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>mockwebserver</artifactId>
|
||||
<version>3.7.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
@ -18,7 +18,8 @@
|
|||
<module>tomcat-7.x-8.x-plugin</module>
|
||||
<module>motan-plugin</module>
|
||||
<module>mongodb-3.x-plugin</module>
|
||||
<module>http-plugin</module>
|
||||
<module>feign-default-http-9.x-plugin</module>
|
||||
<module>okhttp-3.x-plugin</module>
|
||||
<module>resin-3.x-plugin</module>
|
||||
<module>resin-4.x-plugin</module>
|
||||
</modules>
|
||||
|
|
|
|||
Loading…
Reference in New Issue