提交 63f0213a 编写于 作者: wu-sheng's avatar wu-sheng 提交者: GitHub

Merge pull request #197 from wu-sheng/feature/160

add okhttp plugin(provide synchronous http calls )
......@@ -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";
}
}
}
......@@ -12,31 +12,33 @@ public interface InstanceMethodsAroundInterceptor {
/**
* called before target method invocation.
*
* @param context instance context, a class instance only has one {@link EnhancedClassInstanceContext} instance.
* @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.
* @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.
*
* @param context instance context, a class instance only has one {@link EnhancedClassInstanceContext} instance.
* @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.
* @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.
*
* @param t the exception occur.
* @param context instance context, a class instance only has one {@link EnhancedClassInstanceContext} instance.
* @param t the exception occur.
* @param context instance context, a class instance only has one {@link EnhancedClassInstanceContext} instance.
* @param interceptorContext method context, includes class name, method name, etc.
*/
void handleMethodException(Throwable t, EnhancedClassInstanceContext context,
InstanceMethodInvokeContext interceptorContext);
InstanceMethodInvokeContext interceptorContext);
}
......@@ -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>
......
<?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>
\ No newline at end of file
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);
}
}
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;
}
}
};
}
}
okhttp-3.x=org.skywalking.apm.plugin.okhttp.v3.define.RealCallInstrumentation
\ No newline at end of file
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"));
}
});
}
}
<?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>
\ No newline at end of file
......@@ -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>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册