diff --git a/apm-commons/apm-trace/src/main/java/org/skywalking/apm/trace/tag/Tags.java b/apm-commons/apm-trace/src/main/java/org/skywalking/apm/trace/tag/Tags.java
index 77c65bfafe950e235c162c717e508e312fc32d43..6e4d0247f0d62fcfae72cf78511ff4af625c04e0 100644
--- a/apm-commons/apm-trace/src/main/java/org/skywalking/apm/trace/tag/Tags.java
+++ b/apm-commons/apm-trace/src/main/java/org/skywalking/apm/trace/tag/Tags.java
@@ -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");
+ }
}
diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/conf/Config.java b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/conf/Config.java
index 898c16327950d03a6d56458c2f343dbb73b21bff..a8789cda389c3eed568b3a87a3e11d896ebcabfe 100644
--- a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/conf/Config.java
+++ b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/conf/Config.java
@@ -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";
+ }
}
}
diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/plugin/interceptor/enhance/InstanceMethodsAroundInterceptor.java b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/plugin/interceptor/enhance/InstanceMethodsAroundInterceptor.java
index bf2f7989546e7803647cf2597a8ceb70cff43ff2..5e8b445b53a546809e6de4fc78204fb3e3a9f791 100644
--- a/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/plugin/interceptor/enhance/InstanceMethodsAroundInterceptor.java
+++ b/apm-sniffer/apm-agent-core/src/main/java/org/skywalking/apm/agent/core/plugin/interceptor/enhance/InstanceMethodsAroundInterceptor.java
@@ -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);
}
diff --git a/apm-sniffer/apm-agent/pom.xml b/apm-sniffer/apm-agent/pom.xml
index b33a84f8b92d001100860319e4d1a6e81b8c5410..4299ac757d87b0de2d41c49ecac43d23a13f242a 100644
--- a/apm-sniffer/apm-agent/pom.xml
+++ b/apm-sniffer/apm-agent/pom.xml
@@ -70,6 +70,11 @@
apm-resin-4.x-plugin
${project.version}
+
+ org.skywalking
+ apm-okhttp-3.x-plugin
+ ${project.version}
+
diff --git a/apm-sniffer/apm-sdk-plugin/http-plugin/okhttp-3.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/http-plugin/okhttp-3.x-plugin/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..bfe0848a7b87fd7f2c19c2b443b438c00346b478
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/http-plugin/okhttp-3.x-plugin/pom.xml
@@ -0,0 +1,34 @@
+
+
+
+ http-plugin
+ org.skywalking
+ 3.1-2017
+
+ 4.0.0
+
+ apm-okhttp-3.x-plugin
+ okhttp-3.x-plugin
+ jar
+
+
+ 3.7.0
+
+
+
+
+ com.squareup.okhttp3
+ okhttp
+ ${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/http-plugin/okhttp-3.x-plugin/src/main/java/org/skywalking/apm/plugin/okhttp/v3/RealCallInterceptor.java
new file mode 100644
index 0000000000000000000000000000000000000000..f5c16f6691178aacb3dfc5922d7a04e9fb764f22
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/http-plugin/okhttp-3.x-plugin/src/main/java/org/skywalking/apm/plugin/okhttp/v3/RealCallInterceptor.java
@@ -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);
+ }
+}
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/http-plugin/okhttp-3.x-plugin/src/main/java/org/skywalking/apm/plugin/okhttp/v3/define/RealCallInstrumentation.java
new file mode 100644
index 0000000000000000000000000000000000000000..59a526143760423847a2980bc43e5f199e605d15
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/http-plugin/okhttp-3.x-plugin/src/main/java/org/skywalking/apm/plugin/okhttp/v3/define/RealCallInstrumentation.java
@@ -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 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 getMethodsMatcher() {
+ return named("execute");
+ }
+
+ @Override public String getMethodsInterceptor() {
+ return INTERCEPT_CLASS;
+ }
+ }
+ };
+ }
+}
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/http-plugin/okhttp-3.x-plugin/src/main/resources/skywalking-plugin.def
new file mode 100644
index 0000000000000000000000000000000000000000..8ee8f54ce5c5db095ed6e8cb83e2ed6da9bcb171
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/http-plugin/okhttp-3.x-plugin/src/main/resources/skywalking-plugin.def
@@ -0,0 +1 @@
+okhttp-3.x=org.skywalking.apm.plugin.okhttp.v3.define.RealCallInstrumentation
\ No newline at end of file
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/http-plugin/okhttp-3.x-plugin/src/test/java/org/skywalking/apm/plugin/okhttp/v3/RealCallInterceptorTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..46a3b774c8871217a74ab579c5d72db557798309
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/http-plugin/okhttp-3.x-plugin/src/test/java/org/skywalking/apm/plugin/okhttp/v3/RealCallInterceptorTest.java
@@ -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"));
+ }
+ });
+ }
+}
diff --git a/apm-sniffer/apm-sdk-plugin/http-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/http-plugin/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..38a019e78c50e7ae08b00697961fac7bd834b32c
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/http-plugin/pom.xml
@@ -0,0 +1,18 @@
+
+
+
+ apm-sdk-plugin
+ org.skywalking
+ 3.1-2017
+
+ 4.0.0
+
+ http-plugin
+ pom
+
+ okhttp-3.x-plugin
+
+
+
\ No newline at end of file
diff --git a/apm-sniffer/apm-sdk-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/pom.xml
index 75d35f783c861fe39b80703e36305ccef69cffbb..2795f85f0f3d8bc3f581d9ca4473b42746ea4024 100644
--- a/apm-sniffer/apm-sdk-plugin/pom.xml
+++ b/apm-sniffer/apm-sdk-plugin/pom.xml
@@ -18,6 +18,7 @@
tomcat-7.x-8.x-plugin
motan-plugin
mongodb-3.x-plugin
+ http-plugin
resin-3.x-plugin
resin-4.x-plugin