提交 77d43ca1 编写于 作者: 彭勇升 Buddha 提交者: wu-sheng

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
上级 f2e33595
...@@ -75,6 +75,11 @@ ...@@ -75,6 +75,11 @@
<artifactId>apm-okhttp-3.x-plugin</artifactId> <artifactId>apm-okhttp-3.x-plugin</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.skywalking</groupId>
<artifactId>apm-feign-default-http-9.x-plugin</artifactId>
<version>${project.version}</version>
</dependency>
<!-- activation --> <!-- activation -->
<dependency> <dependency>
......
...@@ -3,16 +3,24 @@ ...@@ -3,16 +3,24 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent> <parent>
<artifactId>apm-sdk-plugin</artifactId>
<groupId>org.skywalking</groupId> <groupId>org.skywalking</groupId>
<artifactId>apm-sdk-plugin</artifactId>
<version>3.1-2017</version> <version>3.1-2017</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>http-plugin</artifactId> <artifactId>apm-feign-default-http-9.x-plugin</artifactId>
<packaging>pom</packaging> <packaging>jar</packaging>
<modules>
<module>okhttp-3.x-plugin</module> <properties>
</modules> <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> </project>
\ No newline at end of file
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);
}
}
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;
}
}
};
}
}
feign-default-http-9.x=org.skywalking.apm.plugin.feign.http.v9.define.DefaultHttpClientInstrumentation
\ No newline at end of file
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 @@ ...@@ -3,8 +3,8 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent> <parent>
<artifactId>http-plugin</artifactId>
<groupId>org.skywalking</groupId> <groupId>org.skywalking</groupId>
<artifactId>apm-sdk-plugin</artifactId>
<version>3.1-2017</version> <version>3.1-2017</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
...@@ -24,11 +24,5 @@ ...@@ -24,11 +24,5 @@
<version>${okhttp.version}</version> <version>${okhttp.version}</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>mockwebserver</artifactId>
<version>3.7.0</version>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>
\ No newline at end of file
...@@ -18,7 +18,8 @@ ...@@ -18,7 +18,8 @@
<module>tomcat-7.x-8.x-plugin</module> <module>tomcat-7.x-8.x-plugin</module>
<module>motan-plugin</module> <module>motan-plugin</module>
<module>mongodb-3.x-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-3.x-plugin</module>
<module>resin-4.x-plugin</module> <module>resin-4.x-plugin</module>
</modules> </modules>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册