Merge pull request #197 from wu-sheng/feature/160
add okhttp plugin(provide synchronous http calls )
This commit is contained in:
commit
63f0213a8c
|
|
@ -121,4 +121,8 @@ public final class Tags {
|
|||
* DB_STATEMENT records the sql statement of the database access.
|
||||
*/
|
||||
public static final StringTag DB_STATEMENT = new StringTag("db.statement");
|
||||
|
||||
public static final class HTTP {
|
||||
public static final StringTag METHOD = new StringTag("http.method");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,5 +103,12 @@ public class Config {
|
|||
public static boolean TRACE_PARAM = false;
|
||||
}
|
||||
|
||||
public static class Http {
|
||||
|
||||
/**
|
||||
* The header name of context data.
|
||||
*/
|
||||
public static String HEADER_NAME_OF_CONTEXT_DATA = "SWTraceContext";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,9 +15,10 @@ public interface InstanceMethodsAroundInterceptor {
|
|||
* @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
|
||||
*/
|
||||
void beforeMethod(EnhancedClassInstanceContext context, InstanceMethodInvokeContext interceptorContext,
|
||||
MethodInterceptResult result);
|
||||
MethodInterceptResult result) throws Throwable;
|
||||
|
||||
/**
|
||||
* called after target method invocation. Even method's invocation triggers an exception.
|
||||
|
|
@ -26,9 +27,10 @@ public interface InstanceMethodsAroundInterceptor {
|
|||
* @param interceptorContext method context, includes class name, method name, etc.
|
||||
* @param ret the method's original return value.
|
||||
* @return the method's actual return value.
|
||||
* @throws Throwable
|
||||
*/
|
||||
Object afterMethod(EnhancedClassInstanceContext context, InstanceMethodInvokeContext interceptorContext,
|
||||
Object ret);
|
||||
Object ret) throws Throwable;
|
||||
|
||||
/**
|
||||
* called when occur exception.
|
||||
|
|
|
|||
|
|
@ -70,6 +70,11 @@
|
|||
<artifactId>apm-resin-4.x-plugin</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.skywalking</groupId>
|
||||
<artifactId>apm-okhttp-3.x-plugin</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- activation -->
|
||||
<dependency>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
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>
|
||||
<version>3.1-2017</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>apm-okhttp-3.x-plugin</artifactId>
|
||||
<name>okhttp-3.x-plugin</name>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<okhttp.version>3.7.0</okhttp.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
<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>
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
package org.skywalking.apm.plugin.okhttp.v3;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
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.ConstructorInvokeContext;
|
||||
import org.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor;
|
||||
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 RealCallInterceptor} intercept the synchronous http calls by the client of okhttp.
|
||||
*
|
||||
* @author pengys5
|
||||
*/
|
||||
public class RealCallInterceptor implements InstanceMethodsAroundInterceptor, InstanceConstructorInterceptor {
|
||||
|
||||
private static final String COMPONENT_NAME = "OKHttp";
|
||||
|
||||
private static final String REQUEST_CONTEXT_KEY = "SWRequestContextKey";
|
||||
|
||||
/**
|
||||
* Intercept the {@link okhttp3.RealCall#RealCall(OkHttpClient, Request, boolean)}, then put the second argument of
|
||||
* {@link okhttp3.Request} into {@link EnhancedClassInstanceContext} with the key of {@link
|
||||
* RealCallInterceptor#REQUEST_CONTEXT_KEY}.
|
||||
*
|
||||
* @param context a new added instance field
|
||||
* @param interceptorContext constructor invocation context.
|
||||
*/
|
||||
@Override
|
||||
public void onConstruct(EnhancedClassInstanceContext context, ConstructorInvokeContext interceptorContext) {
|
||||
context.set(REQUEST_CONTEXT_KEY, interceptorContext.allArguments()[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link okhttp3.Request} from {@link EnhancedClassInstanceContext}, then create {@link Span} and set host,
|
||||
* port, kind, component, url from {@link okhttp3.Request}.
|
||||
* Through the reflection of the way, set the http header of context data into {@link okhttp3.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)context.get(REQUEST_CONTEXT_KEY);
|
||||
|
||||
Span span = ContextManager.createSpan(request.url().uri().toString());
|
||||
Tags.PEER_PORT.set(span, request.url().port());
|
||||
Tags.PEER_HOST.set(span, request.url().host());
|
||||
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, request.url().url().getPath());
|
||||
Tags.SPAN_LAYER.asHttp(span);
|
||||
|
||||
ContextCarrier contextCarrier = new ContextCarrier();
|
||||
ContextManager.inject(contextCarrier);
|
||||
|
||||
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);
|
||||
Headers headers = request.headers().newBuilder().add(Config.Plugin.Http.HEADER_NAME_OF_CONTEXT_DATA, contextCarrier.serialize()).build();
|
||||
headersField.set(request, 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.code();
|
||||
|
||||
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,64 @@
|
|||
package org.skywalking.apm.plugin.okhttp.v3.define;
|
||||
|
||||
import net.bytebuddy.description.method.MethodDescription;
|
||||
import net.bytebuddy.matcher.ElementMatcher;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
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.okhttp.v3.RealCallInterceptor;
|
||||
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
|
||||
|
||||
/**
|
||||
* {@link RealCallInstrumentation} presents that skywalking intercepts {@link okhttp3.RealCall#RealCall(OkHttpClient,
|
||||
* Request, boolean)}, {@link okhttp3.RealCall#execute()} by using {@link RealCallInterceptor}.
|
||||
*
|
||||
* @author pengys5
|
||||
*/
|
||||
public class RealCallInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
|
||||
|
||||
/**
|
||||
* Enhance class.
|
||||
*/
|
||||
private static final String ENHANCE_CLASS = "okhttp3.RealCall";
|
||||
|
||||
/**
|
||||
* Intercept class.
|
||||
*/
|
||||
private static final String INTERCEPT_CLASS = "org.skywalking.apm.plugin.okhttp.v3.RealCallInterceptor";
|
||||
|
||||
@Override protected String enhanceClassName() {
|
||||
return ENHANCE_CLASS;
|
||||
}
|
||||
|
||||
@Override protected ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
|
||||
return new ConstructorInterceptPoint[] {
|
||||
new ConstructorInterceptPoint() {
|
||||
@Override public ElementMatcher<MethodDescription> getConstructorMatcher() {
|
||||
return takesArguments(OkHttpClient.class, Request.class, boolean.class);
|
||||
}
|
||||
|
||||
@Override public String getConstructorInterceptor() {
|
||||
return INTERCEPT_CLASS;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@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 @@
|
|||
okhttp-3.x=org.skywalking.apm.plugin.okhttp.v3.define.RealCallInstrumentation
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
package org.skywalking.apm.plugin.okhttp.v3;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
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.ConstructorInvokeContext;
|
||||
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 RealCallInterceptorTest {
|
||||
|
||||
private RealCallInterceptor realCallInterceptor;
|
||||
private MockTracerContextListener mockTracerContextListener;
|
||||
|
||||
private EnhancedClassInstanceContext classInstanceContext;
|
||||
|
||||
@Mock
|
||||
private ConstructorInvokeContext constructorInvokeContext;
|
||||
|
||||
@Mock
|
||||
private InstanceMethodInvokeContext instanceMethodInvokeContext;
|
||||
|
||||
@Mock
|
||||
private OkHttpClient client;
|
||||
|
||||
private Request request;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mockTracerContextListener = new MockTracerContextListener();
|
||||
|
||||
classInstanceContext = new EnhancedClassInstanceContext();
|
||||
|
||||
request = new Request.Builder().url("http://skywalking.org").build();
|
||||
Object[] allArguments = {client, request, false};
|
||||
when(constructorInvokeContext.allArguments()).thenReturn(allArguments);
|
||||
|
||||
ServiceManager.INSTANCE.boot();
|
||||
realCallInterceptor = new RealCallInterceptor();
|
||||
|
||||
TracerContext.ListenerManager.add(mockTracerContextListener);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnConstruct() {
|
||||
realCallInterceptor.onConstruct(classInstanceContext, constructorInvokeContext);
|
||||
Assert.assertEquals(request, classInstanceContext.get("SWRequestContextKey"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMethodsAround() throws Throwable {
|
||||
realCallInterceptor.onConstruct(classInstanceContext, constructorInvokeContext);
|
||||
realCallInterceptor.beforeMethod(classInstanceContext, instanceMethodInvokeContext, null);
|
||||
|
||||
Response response = mock(Response.class);
|
||||
when(response.code()).thenReturn(200);
|
||||
realCallInterceptor.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 {
|
||||
realCallInterceptor.onConstruct(classInstanceContext, constructorInvokeContext);
|
||||
realCallInterceptor.beforeMethod(classInstanceContext, instanceMethodInvokeContext, null);
|
||||
|
||||
Response response = mock(Response.class);
|
||||
when(response.code()).thenReturn(404);
|
||||
realCallInterceptor.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(80, Tags.PEER_PORT.get(span).intValue());
|
||||
Assert.assertEquals("OKHttp", Tags.COMPONENT.get(span));
|
||||
Assert.assertEquals("client", Tags.SPAN_KIND.get(span));
|
||||
Assert.assertEquals("/", Tags.URL.get(span));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testException() throws Throwable {
|
||||
realCallInterceptor.onConstruct(classInstanceContext, constructorInvokeContext);
|
||||
realCallInterceptor.beforeMethod(classInstanceContext, instanceMethodInvokeContext, null);
|
||||
|
||||
realCallInterceptor.handleMethodException(new NullPointerException("testException"), classInstanceContext, null);
|
||||
|
||||
Response response = mock(Response.class);
|
||||
when(response.code()).thenReturn(200);
|
||||
realCallInterceptor.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"));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
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>
|
||||
<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>
|
||||
|
||||
</project>
|
||||
|
|
@ -18,6 +18,7 @@
|
|||
<module>tomcat-7.x-8.x-plugin</module>
|
||||
<module>motan-plugin</module>
|
||||
<module>mongodb-3.x-plugin</module>
|
||||
<module>http-plugin</module>
|
||||
<module>resin-3.x-plugin</module>
|
||||
<module>resin-4.x-plugin</module>
|
||||
</modules>
|
||||
|
|
|
|||
Loading…
Reference in New Issue