From 921f6a37d4258ba10d6294b0d6ce790369f03b75 Mon Sep 17 00:00:00 2001 From: Felix Barnsteiner Date: Thu, 14 Mar 2019 10:15:21 +0100 Subject: [PATCH 1/3] Support async OkHttp calls closes #425 --- .../OkHttp3ClientAsyncInstrumentation.java | 174 ++++++++++++++++++ ...ic.apm.agent.bci.ElasticApmInstrumentation | 1 + ...OkHttp3ClientAsyncInstrumentationTest.java | 67 +++++++ 3 files changed, 242 insertions(+) create mode 100644 apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientAsyncInstrumentation.java create mode 100644 apm-agent-plugins/apm-okhttp-plugin/src/test/java/co/elastic/apm/agent/okhttp/OkHttp3ClientAsyncInstrumentationTest.java diff --git a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientAsyncInstrumentation.java b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientAsyncInstrumentation.java new file mode 100644 index 0000000000..897445f73a --- /dev/null +++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientAsyncInstrumentation.java @@ -0,0 +1,174 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2019 Elastic and contributors + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package co.elastic.apm.agent.okhttp; + +import co.elastic.apm.agent.bci.ElasticApmInstrumentation; +import co.elastic.apm.agent.bci.HelperClassManager; +import co.elastic.apm.agent.bci.VisibleForAdvice; +import co.elastic.apm.agent.http.client.HttpClientHelper; +import co.elastic.apm.agent.impl.ElasticApmTracer; +import co.elastic.apm.agent.impl.transaction.Span; +import co.elastic.apm.agent.impl.transaction.TraceContext; +import co.elastic.apm.agent.impl.transaction.TraceContextHolder; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.implementation.bytecode.assign.Assigner; +import net.bytebuddy.matcher.ElementMatcher; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.Response; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nullable; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.returns; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +public class OkHttp3ClientAsyncInstrumentation extends ElasticApmInstrumentation { + + @VisibleForAdvice + public static final Logger logger = LoggerFactory.getLogger(OkHttp3ClientAsyncInstrumentation.class); + + @Override + public Class getAdviceClass() { + return OkHttpClient3ExecuteAdvice.class; + } + + @Nullable + @VisibleForAdvice + public static HelperClassManager> helper; + + + @Override + public void init(ElasticApmTracer tracer) { + helper = HelperClassManager.ForAnyClassLoader.of(tracer, + OkHttp3ClientAsyncInstrumentation.class.getName() + "$CallbackWrapperCreator", + OkHttp3ClientAsyncInstrumentation.class.getName() + "$CallbackWrapperCreator$CallbackWrapper"); + } + + @VisibleForAdvice + public static class OkHttpClient3ExecuteAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + private static void onBeforeEnqueue(@Advice.Origin Class clazz, + @Advice.FieldValue(value = "originalRequest", typing = Assigner.Typing.DYNAMIC, readOnly = false) @Nullable okhttp3.Request originalRequest, + @Advice.Argument(value = 0, readOnly = false) @Nullable Callback callback, + @Advice.Local("span") Span span) { + System.out.println("onBeforeEnqueue"); + if (tracer == null || tracer.getActive() == null || helper == null) { + return; + } + + if (originalRequest == null || callback == null) { + return; + } + + final TraceContextHolder parent = tracer.getActive(); + + okhttp3.Request request = originalRequest; + span = HttpClientHelper.startHttpClientSpan(parent, request.method(), request.url().toString(), request.url().host()); + System.out.println(span); + if (span != null) { + final WrapperCreator wrapperCreator = helper.getForClassLoaderOfClass(clazz); + if (wrapperCreator != null) { + System.out.println(wrapperCreator); + span.activate().markLifecycleManagingThreadSwitchExpected(); + originalRequest = originalRequest.newBuilder().addHeader(TraceContext.TRACE_PARENT_HEADER, span.getTraceContext().getOutgoingTraceParentHeader().toString()).build(); + callback = wrapperCreator.wrap(callback, span); + } + } + } + + @Advice.OnMethodExit(suppress = Throwable.class) + private static void onAfterEnqueue(@Advice.Local("span") @Nullable Span span) { + if (span != null) { + span.deactivate(); + } + } + } + + public interface WrapperCreator { + T wrap(T delegate, Span span); + } + + public static class CallbackWrapperCreator implements WrapperCreator { + + @Override + public Callback wrap(final Callback delegate, Span span) { + return new CallbackWrapper(span, delegate); + } + + private static class CallbackWrapper implements Callback { + private final Span span; + private final Callback delegate; + + CallbackWrapper(Span span, Callback delegate) { + this.span = span; + this.delegate = delegate; + } + + @Override + public void onFailure(Call call, IOException e) { + try { + span.captureException(e).end(); + } catch (Throwable t) { + logger.error(t.getMessage(), t); + } finally { + delegate.onFailure(call, e); + } + } + + @Override + public void onResponse(Call call, Response response) throws IOException { + try { + span.getContext().getHttp().withStatusCode(response.code()); + span.end(); + } catch (Throwable t) { + logger.error(t.getMessage(), t); + } finally { + delegate.onResponse(call, response); + } + } + } + } + + @Override + public ElementMatcher getTypeMatcher() { + return named("okhttp3.RealCall"); + } + + @Override + public ElementMatcher getMethodMatcher() { + return named("enqueue").and(takesArguments(1)).and(takesArgument(0, named("okhttp3.Callback"))).and(returns(void.class)); + } + + @Override + public Collection getInstrumentationGroupNames() { + return Arrays.asList("http-client", "okhttp"); + } + +} diff --git a/apm-agent-plugins/apm-okhttp-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.bci.ElasticApmInstrumentation b/apm-agent-plugins/apm-okhttp-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.bci.ElasticApmInstrumentation index c0be07ab00..d589155897 100644 --- a/apm-agent-plugins/apm-okhttp-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.bci.ElasticApmInstrumentation +++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.bci.ElasticApmInstrumentation @@ -1,2 +1,3 @@ co.elastic.apm.agent.okhttp.OkHttpClientInstrumentation co.elastic.apm.agent.okhttp.OkHttp3ClientInstrumentation +co.elastic.apm.agent.okhttp.OkHttp3ClientAsyncInstrumentation diff --git a/apm-agent-plugins/apm-okhttp-plugin/src/test/java/co/elastic/apm/agent/okhttp/OkHttp3ClientAsyncInstrumentationTest.java b/apm-agent-plugins/apm-okhttp-plugin/src/test/java/co/elastic/apm/agent/okhttp/OkHttp3ClientAsyncInstrumentationTest.java new file mode 100644 index 0000000000..0ddef4d0fb --- /dev/null +++ b/apm-agent-plugins/apm-okhttp-plugin/src/test/java/co/elastic/apm/agent/okhttp/OkHttp3ClientAsyncInstrumentationTest.java @@ -0,0 +1,67 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2019 Elastic and contributors + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package co.elastic.apm.agent.okhttp; + +import co.elastic.apm.agent.httpclient.AbstractHttpClientInstrumentationTest; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.junit.Before; + +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +public class OkHttp3ClientAsyncInstrumentationTest extends AbstractHttpClientInstrumentationTest { + + private OkHttpClient client; + + @Before + public void setUp() { + client = new OkHttpClient(); + } + + @Override + protected void performGet(String path) throws Exception { + Request request = new Request.Builder() + .url(path) + .build(); + + final CountDownLatch countDownLatch = new CountDownLatch(1); + client.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(Call call, IOException e) { + countDownLatch.countDown(); + } + + @Override + public void onResponse(Call call, Response response) { + countDownLatch.countDown(); + response.body().close(); + } + }); + countDownLatch.await(1, TimeUnit.SECONDS); + + } + +} + From 0c91db93deb83662053964e7c30d94432a38c4d0 Mon Sep 17 00:00:00 2001 From: Felix Barnsteiner Date: Mon, 18 Mar 2019 11:34:39 +0100 Subject: [PATCH 2/3] OkHttp 2 async support --- CHANGELOG.md | 1 + .../OkHttp3ClientAsyncInstrumentation.java | 15 +- .../OkHttpClientAsyncInstrumentation.java | 166 ++++++++++++++++++ .../apm/agent/okhttp/WrapperCreator.java | 42 +++++ ...ic.apm.agent.bci.ElasticApmInstrumentation | 1 + .../OkHttpClientAsyncInstrumentationTest.java | 66 +++++++ docs/supported-technologies.asciidoc | 6 +- 7 files changed, 283 insertions(+), 14 deletions(-) create mode 100644 apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientAsyncInstrumentation.java create mode 100644 apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/WrapperCreator.java create mode 100644 apm-agent-plugins/apm-okhttp-plugin/src/test/java/co/elastic/apm/agent/okhttp/OkHttpClientAsyncInstrumentationTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 13822bac95..f997955d0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ As of version 7.x of the stack, labels will be stored under `labels` in Elasticsearch. Previously, they were stored under `context.tags`. * Support async queries made by Elasticsearch REST client + * Support async calls made by OkHttp client (`Call#enqueue`) ## Bug Fixes diff --git a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientAsyncInstrumentation.java b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientAsyncInstrumentation.java index 897445f73a..dc9c31d9a0 100644 --- a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientAsyncInstrumentation.java +++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientAsyncInstrumentation.java @@ -60,12 +60,12 @@ public Class getAdviceClass() { @Nullable @VisibleForAdvice - public static HelperClassManager> helper; + public static HelperClassManager> callbackWrapperCreator; @Override public void init(ElasticApmTracer tracer) { - helper = HelperClassManager.ForAnyClassLoader.of(tracer, + callbackWrapperCreator = HelperClassManager.ForAnyClassLoader.of(tracer, OkHttp3ClientAsyncInstrumentation.class.getName() + "$CallbackWrapperCreator", OkHttp3ClientAsyncInstrumentation.class.getName() + "$CallbackWrapperCreator$CallbackWrapper"); } @@ -78,8 +78,7 @@ private static void onBeforeEnqueue(@Advice.Origin Class clazz, @Advice.FieldValue(value = "originalRequest", typing = Assigner.Typing.DYNAMIC, readOnly = false) @Nullable okhttp3.Request originalRequest, @Advice.Argument(value = 0, readOnly = false) @Nullable Callback callback, @Advice.Local("span") Span span) { - System.out.println("onBeforeEnqueue"); - if (tracer == null || tracer.getActive() == null || helper == null) { + if (tracer == null || tracer.getActive() == null || callbackWrapperCreator == null) { return; } @@ -91,11 +90,9 @@ private static void onBeforeEnqueue(@Advice.Origin Class clazz, okhttp3.Request request = originalRequest; span = HttpClientHelper.startHttpClientSpan(parent, request.method(), request.url().toString(), request.url().host()); - System.out.println(span); if (span != null) { - final WrapperCreator wrapperCreator = helper.getForClassLoaderOfClass(clazz); + final WrapperCreator wrapperCreator = callbackWrapperCreator.getForClassLoaderOfClass(clazz); if (wrapperCreator != null) { - System.out.println(wrapperCreator); span.activate().markLifecycleManagingThreadSwitchExpected(); originalRequest = originalRequest.newBuilder().addHeader(TraceContext.TRACE_PARENT_HEADER, span.getTraceContext().getOutgoingTraceParentHeader().toString()).build(); callback = wrapperCreator.wrap(callback, span); @@ -111,10 +108,6 @@ private static void onAfterEnqueue(@Advice.Local("span") @Nullable Span span) { } } - public interface WrapperCreator { - T wrap(T delegate, Span span); - } - public static class CallbackWrapperCreator implements WrapperCreator { @Override diff --git a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientAsyncInstrumentation.java b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientAsyncInstrumentation.java new file mode 100644 index 0000000000..f20fd8bd28 --- /dev/null +++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientAsyncInstrumentation.java @@ -0,0 +1,166 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2019 Elastic and contributors + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package co.elastic.apm.agent.okhttp; + +import co.elastic.apm.agent.bci.ElasticApmInstrumentation; +import co.elastic.apm.agent.bci.HelperClassManager; +import co.elastic.apm.agent.bci.VisibleForAdvice; +import co.elastic.apm.agent.http.client.HttpClientHelper; +import co.elastic.apm.agent.impl.ElasticApmTracer; +import co.elastic.apm.agent.impl.transaction.Span; +import co.elastic.apm.agent.impl.transaction.TraceContext; +import co.elastic.apm.agent.impl.transaction.TraceContextHolder; +import com.squareup.okhttp.Call; +import com.squareup.okhttp.Callback; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.Response; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.implementation.bytecode.assign.Assigner; +import net.bytebuddy.matcher.ElementMatcher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nullable; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.returns; + +public class OkHttpClientAsyncInstrumentation extends ElasticApmInstrumentation { + + @VisibleForAdvice + public static final Logger logger = LoggerFactory.getLogger(OkHttpClientAsyncInstrumentation.class); + + @Override + public Class getAdviceClass() { + return OkHttpClient3ExecuteAdvice.class; + } + + @Nullable + @VisibleForAdvice + public static HelperClassManager> callbackWrapperCreator; + + + @Override + public void init(ElasticApmTracer tracer) { + callbackWrapperCreator = HelperClassManager.ForAnyClassLoader.of(tracer, + OkHttpClientAsyncInstrumentation.class.getName() + "$CallbackWrapperCreator", + OkHttpClientAsyncInstrumentation.class.getName() + "$CallbackWrapperCreator$CallbackWrapper"); + } + + @VisibleForAdvice + public static class OkHttpClient3ExecuteAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + private static void onBeforeEnqueue(@Advice.Origin Class clazz, + @Advice.FieldValue(value = "originalRequest", typing = Assigner.Typing.DYNAMIC, readOnly = false) @Nullable Request originalRequest, + @Advice.Argument(value = 0, readOnly = false) @Nullable Callback callback, + @Advice.Local("span") Span span) { + if (tracer == null || tracer.getActive() == null || callbackWrapperCreator == null) { + return; + } + + if (originalRequest == null || callback == null) { + return; + } + + final TraceContextHolder parent = tracer.getActive(); + + Request request = originalRequest; + span = HttpClientHelper.startHttpClientSpan(parent, request.method(), request.url().toString(), request.url().getHost()); + if (span != null) { + final WrapperCreator wrapperCreator = callbackWrapperCreator.getForClassLoaderOfClass(clazz); + if (wrapperCreator != null) { + span.activate().markLifecycleManagingThreadSwitchExpected(); + originalRequest = originalRequest.newBuilder().addHeader(TraceContext.TRACE_PARENT_HEADER, span.getTraceContext().getOutgoingTraceParentHeader().toString()).build(); + callback = wrapperCreator.wrap(callback, span); + } + } + } + + @Advice.OnMethodExit(suppress = Throwable.class) + private static void onAfterEnqueue(@Advice.Local("span") @Nullable Span span) { + if (span != null) { + span.deactivate(); + } + } + } + + public static class CallbackWrapperCreator implements WrapperCreator { + + @Override + public Callback wrap(final Callback delegate, Span span) { + return new CallbackWrapper(span, delegate); + } + + private static class CallbackWrapper implements Callback { + private final Span span; + private final Callback delegate; + + CallbackWrapper(Span span, Callback delegate) { + this.span = span; + this.delegate = delegate; + } + + @Override + public void onFailure(Request req, IOException e) { + try { + span.captureException(e).end(); + } catch (Throwable t) { + logger.error(t.getMessage(), t); + } finally { + delegate.onFailure(req, e); + } + } + + @Override + public void onResponse(Response response) throws IOException { + try { + span.getContext().getHttp().withStatusCode(response.code()); + span.end(); + } catch (Throwable t) { + logger.error(t.getMessage(), t); + } finally { + delegate.onResponse(response); + } + } + } + } + + @Override + public ElementMatcher getTypeMatcher() { + return named("com.squareup.okhttp.Call"); + } + + @Override + public ElementMatcher getMethodMatcher() { + return named("enqueue").and(returns(void.class)); + } + + @Override + public Collection getInstrumentationGroupNames() { + return Arrays.asList("http-client", "okhttp"); + } + +} diff --git a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/WrapperCreator.java b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/WrapperCreator.java new file mode 100644 index 0000000000..26b7962b65 --- /dev/null +++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/WrapperCreator.java @@ -0,0 +1,42 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2019 Elastic and contributors + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package co.elastic.apm.agent.okhttp; + +import co.elastic.apm.agent.impl.transaction.Span; + +/** + * Used to create a wrapper for a callback or listener + * + * @param the type of the wrapper to create + */ +public interface WrapperCreator { + + /** + * Wraps a callback or listener. + *

+ * The implementation is supposed to create the actual wrapper which manages the lifecycle of the provided {@link Span}. + *

+ * + * @param delegate the actual callback which should be wrapped + * @param span the currently active span + * @return the wrapped callback + */ + T wrap(T delegate, Span span); +} diff --git a/apm-agent-plugins/apm-okhttp-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.bci.ElasticApmInstrumentation b/apm-agent-plugins/apm-okhttp-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.bci.ElasticApmInstrumentation index d589155897..a695111ae3 100644 --- a/apm-agent-plugins/apm-okhttp-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.bci.ElasticApmInstrumentation +++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.bci.ElasticApmInstrumentation @@ -1,3 +1,4 @@ co.elastic.apm.agent.okhttp.OkHttpClientInstrumentation co.elastic.apm.agent.okhttp.OkHttp3ClientInstrumentation +co.elastic.apm.agent.okhttp.OkHttpClientAsyncInstrumentation co.elastic.apm.agent.okhttp.OkHttp3ClientAsyncInstrumentation diff --git a/apm-agent-plugins/apm-okhttp-plugin/src/test/java/co/elastic/apm/agent/okhttp/OkHttpClientAsyncInstrumentationTest.java b/apm-agent-plugins/apm-okhttp-plugin/src/test/java/co/elastic/apm/agent/okhttp/OkHttpClientAsyncInstrumentationTest.java new file mode 100644 index 0000000000..f4ff72eba1 --- /dev/null +++ b/apm-agent-plugins/apm-okhttp-plugin/src/test/java/co/elastic/apm/agent/okhttp/OkHttpClientAsyncInstrumentationTest.java @@ -0,0 +1,66 @@ +/*- + * #%L + * Elastic APM Java agent + * %% + * Copyright (C) 2018 - 2019 Elastic and contributors + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package co.elastic.apm.agent.okhttp; + +import co.elastic.apm.agent.httpclient.AbstractHttpClientInstrumentationTest; +import com.squareup.okhttp.Callback; +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.Response; +import org.junit.Before; + +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +public class OkHttpClientAsyncInstrumentationTest extends AbstractHttpClientInstrumentationTest { + + private OkHttpClient client; + + @Before + public void setUp() { + client = new OkHttpClient(); + } + + @Override + protected void performGet(String path) throws Exception { + Request request = new Request.Builder() + .url(path) + .build(); + + final CountDownLatch countDownLatch = new CountDownLatch(1); + client.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(Request req, IOException e) { + countDownLatch.countDown(); + } + + @Override + public void onResponse(Response response) throws IOException { + countDownLatch.countDown(); + response.body().close(); + } + }); + countDownLatch.await(1, TimeUnit.SECONDS); + + } + +} + diff --git a/docs/supported-technologies.asciidoc b/docs/supported-technologies.asciidoc index da749d8489..56e870ac4b 100644 --- a/docs/supported-technologies.asciidoc +++ b/docs/supported-technologies.asciidoc @@ -174,9 +174,9 @@ The spans are named after the schema ` `, for example `GET elastic |OkHttp |2, 3 -|Currently, only synchronous calls via `Call#execute()` are supported. - `Call#enquene(Callback)` is not yet supported. -| 1.4.0 +| +|1.4.0 (synchronous calls via `Call#execute()`) + 1.5.0 (async calls via `Call#enquene(Callback)`) |HttpUrlConnection | From a89ff18172206a04a1d8845de6300e66381382b2 Mon Sep 17 00:00:00 2001 From: Felix Barnsteiner Date: Mon, 18 Mar 2019 14:34:20 +0100 Subject: [PATCH 3/3] Don't create spans if injection fails --- .../okhttp/OkHttp3ClientAsyncInstrumentation.java | 12 +++++------- .../okhttp/OkHttpClientAsyncInstrumentation.java | 12 +++++------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientAsyncInstrumentation.java b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientAsyncInstrumentation.java index dc9c31d9a0..aa7b440b4d 100644 --- a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientAsyncInstrumentation.java +++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientAsyncInstrumentation.java @@ -82,7 +82,8 @@ private static void onBeforeEnqueue(@Advice.Origin Class clazz, return; } - if (originalRequest == null || callback == null) { + final WrapperCreator wrapperCreator = callbackWrapperCreator.getForClassLoaderOfClass(clazz); + if (originalRequest == null || callback == null || wrapperCreator == null) { return; } @@ -91,12 +92,9 @@ private static void onBeforeEnqueue(@Advice.Origin Class clazz, okhttp3.Request request = originalRequest; span = HttpClientHelper.startHttpClientSpan(parent, request.method(), request.url().toString(), request.url().host()); if (span != null) { - final WrapperCreator wrapperCreator = callbackWrapperCreator.getForClassLoaderOfClass(clazz); - if (wrapperCreator != null) { - span.activate().markLifecycleManagingThreadSwitchExpected(); - originalRequest = originalRequest.newBuilder().addHeader(TraceContext.TRACE_PARENT_HEADER, span.getTraceContext().getOutgoingTraceParentHeader().toString()).build(); - callback = wrapperCreator.wrap(callback, span); - } + span.activate().markLifecycleManagingThreadSwitchExpected(); + originalRequest = originalRequest.newBuilder().addHeader(TraceContext.TRACE_PARENT_HEADER, span.getTraceContext().getOutgoingTraceParentHeader().toString()).build(); + callback = wrapperCreator.wrap(callback, span); } } diff --git a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientAsyncInstrumentation.java b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientAsyncInstrumentation.java index f20fd8bd28..bf1ba19bcc 100644 --- a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientAsyncInstrumentation.java +++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientAsyncInstrumentation.java @@ -81,7 +81,8 @@ private static void onBeforeEnqueue(@Advice.Origin Class clazz, return; } - if (originalRequest == null || callback == null) { + final WrapperCreator wrapperCreator = callbackWrapperCreator.getForClassLoaderOfClass(clazz); + if (originalRequest == null || callback == null || wrapperCreator == null) { return; } @@ -90,12 +91,9 @@ private static void onBeforeEnqueue(@Advice.Origin Class clazz, Request request = originalRequest; span = HttpClientHelper.startHttpClientSpan(parent, request.method(), request.url().toString(), request.url().getHost()); if (span != null) { - final WrapperCreator wrapperCreator = callbackWrapperCreator.getForClassLoaderOfClass(clazz); - if (wrapperCreator != null) { - span.activate().markLifecycleManagingThreadSwitchExpected(); - originalRequest = originalRequest.newBuilder().addHeader(TraceContext.TRACE_PARENT_HEADER, span.getTraceContext().getOutgoingTraceParentHeader().toString()).build(); - callback = wrapperCreator.wrap(callback, span); - } + span.activate().markLifecycleManagingThreadSwitchExpected(); + originalRequest = originalRequest.newBuilder().addHeader(TraceContext.TRACE_PARENT_HEADER, span.getTraceContext().getOutgoingTraceParentHeader().toString()).build(); + callback = wrapperCreator.wrap(callback, span); } }