diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/AbstractApacheHttpClientAdvice.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/AbstractApacheHttpClientAdvice.java index 1998180c3b..10e2d2727b 100644 --- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/AbstractApacheHttpClientAdvice.java +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/AbstractApacheHttpClientAdvice.java @@ -20,9 +20,9 @@ import co.elastic.apm.agent.httpclient.HttpClientHelper; -import co.elastic.apm.agent.tracer.TraceState; import co.elastic.apm.agent.tracer.Outcome; import co.elastic.apm.agent.tracer.Span; +import co.elastic.apm.agent.tracer.TraceState; import co.elastic.apm.agent.tracer.Tracer; import co.elastic.apm.agent.tracer.dispatch.TextHeaderGetter; import co.elastic.apm.agent.tracer.dispatch.TextHeaderSetter; @@ -34,11 +34,11 @@ public abstract class AbstractApacheHttpClientAdvice { public static & - TextHeaderGetter> Span startSpan(final Tracer tracer, - final ApacheHttpClientApiAdapter adapter, - final WRAPPER request, - @Nullable final HTTPHOST httpHost, - final HeaderAccessor headerAccessor) throws URISyntaxException { + TextHeaderGetter, HTTPENTITY> Span startSpan(final Tracer tracer, + final ApacheHttpClientApiAdapter adapter, + final WRAPPER request, + @Nullable final HTTPHOST httpHost, + final HeaderAccessor headerAccessor) throws URISyntaxException { TraceState traceState = tracer.currentContext(); Span span = null; if (traceState.getSpan() != null) { @@ -51,10 +51,11 @@ TextHeaderGetter> Span startSpan(final Tracer tracer, return span; } - public static void endSpan(ApacheHttpClientApiAdapter adapter, - Object spanObj, - Throwable t, - RESPONSE response) { + public static + void endSpan(ApacheHttpClientApiAdapter adapter, + Object spanObj, + Throwable t, + RESPONSE response) { Span span = (Span) spanObj; if (span == null) { return; diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/AbstractApacheHttpRequestBodyCaptureAdvice.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/AbstractApacheHttpRequestBodyCaptureAdvice.java new file mode 100644 index 0000000000..b96bae4582 --- /dev/null +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/AbstractApacheHttpRequestBodyCaptureAdvice.java @@ -0,0 +1,58 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + */ +package co.elastic.apm.agent.httpclient.common; + + +import co.elastic.apm.agent.httpclient.RequestBodyRecordingInputStream; +import co.elastic.apm.agent.httpclient.RequestBodyRecordingOutputStream; +import co.elastic.apm.agent.sdk.logging.Logger; +import co.elastic.apm.agent.sdk.logging.LoggerFactory; +import co.elastic.apm.agent.tracer.Span; + +import java.io.InputStream; +import java.io.OutputStream; + +public abstract class AbstractApacheHttpRequestBodyCaptureAdvice { + + private static final Logger logger = LoggerFactory.getLogger(AbstractApacheHttpRequestBodyCaptureAdvice.class); + + public static InputStream maybeCaptureRequestBodyInputStream(HTTPENTITY thiz, InputStream requestBody) { + Span clientSpan = RequestBodyCaptureRegistry.removeSpanFor(thiz); + if (clientSpan != null) { + logger.debug("Wrapping input stream for request body capture for HttpEntity {} ({}) for span {}", thiz.getClass().getName(), System.identityHashCode(thiz), clientSpan); + return new RequestBodyRecordingInputStream(requestBody, clientSpan); + } + return requestBody; + } + + public static OutputStream maybeCaptureRequestBodyOutputStream(HTTPENTITY thiz, OutputStream requestBody) { + Span clientSpan = RequestBodyCaptureRegistry.removeSpanFor(thiz); + if (clientSpan != null) { + logger.debug("Wrapping output stream for request body capture for HttpEntity {} ({}) for span {}", thiz.getClass().getName(), System.identityHashCode(thiz), clientSpan); + return new RequestBodyRecordingOutputStream(requestBody, clientSpan); + } + return requestBody; + } + + public static void releaseRequestBodyOutputStream(OutputStream maybeWrapped) { + if (maybeWrapped instanceof RequestBodyRecordingOutputStream) { + ((RequestBodyRecordingOutputStream) maybeWrapped).releaseSpan(); + } + } +} diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/ApacheHttpClientApiAdapter.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/ApacheHttpClientApiAdapter.java index a3834035cd..ad36f5665c 100644 --- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/ApacheHttpClientApiAdapter.java +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/ApacheHttpClientApiAdapter.java @@ -23,7 +23,7 @@ import java.net.URI; import java.net.URISyntaxException; -public interface ApacheHttpClientApiAdapter { +public interface ApacheHttpClientApiAdapter extends ApacheHttpClientEntityAccessor { String getMethod(WRAPPER request); URI getUri(WRAPPER request) throws URISyntaxException; diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/ApacheHttpClientEntityAccessor.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/ApacheHttpClientEntityAccessor.java new file mode 100644 index 0000000000..0f1abfc3b6 --- /dev/null +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/ApacheHttpClientEntityAccessor.java @@ -0,0 +1,29 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + */ +package co.elastic.apm.agent.httpclient.common; + +import javax.annotation.Nullable; + +public interface ApacheHttpClientEntityAccessor { + @Nullable + HTTPENTITY getRequestEntity(REQUEST request); + + @Nullable + byte[] getSimpleBodyBytes(REQUEST request); +} diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/helper/RequestBodyCaptureRegistry.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/RequestBodyCaptureRegistry.java similarity index 60% rename from apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/helper/RequestBodyCaptureRegistry.java rename to apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/RequestBodyCaptureRegistry.java index 009cc1fe9b..052591ba6e 100644 --- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/helper/RequestBodyCaptureRegistry.java +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/RequestBodyCaptureRegistry.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package co.elastic.apm.agent.httpclient.v4.helper; +package co.elastic.apm.agent.httpclient.common; import co.elastic.apm.agent.httpclient.HttpClientHelper; import co.elastic.apm.agent.sdk.logging.Logger; @@ -25,10 +25,8 @@ import co.elastic.apm.agent.tracer.AbstractSpan; import co.elastic.apm.agent.tracer.GlobalTracer; import co.elastic.apm.agent.tracer.Span; +import co.elastic.apm.agent.tracer.dispatch.TextHeaderGetter; import co.elastic.apm.agent.tracer.reference.ReferenceCountedMap; -import org.apache.http.HttpEntity; -import org.apache.http.HttpEntityEnclosingRequest; -import org.apache.http.HttpRequest; import javax.annotation.Nullable; @@ -52,25 +50,33 @@ public static Span removeSpanFor(Object entity) { } - public static void potentiallyCaptureRequestBody(HttpRequest request, @Nullable AbstractSpan span) { - if (HttpClientHelper.startRequestBodyCapture(span, request, RequestHeaderAccessor.INSTANCE)) { - if (request instanceof HttpEntityEnclosingRequest) { - HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity(); - if (entity != null) { - logger.debug("Enabling request capture for entity {}() for span {}", entity.getClass().getName(), System.identityHashCode(entity), span); - MapHolder.captureBodyFor(entity, (Span) span); + public static void potentiallyCaptureRequestBody( + REQUEST request, + @Nullable AbstractSpan abstractSpan, + ApacheHttpClientEntityAccessor adapter, + TextHeaderGetter headerGetter + ) { + if (HttpClientHelper.startRequestBodyCapture(abstractSpan, request, headerGetter)) { + Span span = (Span) abstractSpan; + byte[] simpleBytes = adapter.getSimpleBodyBytes(request); + if (simpleBytes != null) { + logger.debug("Captured simple request body for span {}", abstractSpan); + span.getContext().getHttp().getRequestBody().append(simpleBytes, 0, simpleBytes.length); + } else { + HTTPENTITY httpEntity = adapter.getRequestEntity(request); + if (httpEntity != null) { + logger.debug("Enabling request capture for entity {}() for span {}", httpEntity.getClass().getName(), System.identityHashCode(httpEntity), abstractSpan); + MapHolder.captureBodyFor(httpEntity, span); } else { - logger.debug("HttpEntity is null for span {}", span); + logger.debug("Not capturing request body because HttpEntity is null for span {}", abstractSpan); } - } else { - logger.debug("Not capturing request body because {} is not an HttpEntityEnclosingRequest", request.getClass().getName()); } } } @Nullable - public static Span removeSpanFor(HttpEntity entity) { - return MapHolder.removeSpanFor(entity); + public static Span removeSpanFor(Object httpEntity) { + return MapHolder.removeSpanFor(httpEntity); } } diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/ApacheHttpClientInstrumentation.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/ApacheHttpClientInstrumentation.java index 2bc725c95b..02033d2c98 100644 --- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/ApacheHttpClientInstrumentation.java +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/ApacheHttpClientInstrumentation.java @@ -19,8 +19,8 @@ package co.elastic.apm.agent.httpclient.v4; import co.elastic.apm.agent.httpclient.common.AbstractApacheHttpClientAdvice; +import co.elastic.apm.agent.httpclient.common.RequestBodyCaptureRegistry; import co.elastic.apm.agent.httpclient.v4.helper.ApacheHttpClient4ApiAdapter; -import co.elastic.apm.agent.httpclient.v4.helper.RequestBodyCaptureRegistry; import co.elastic.apm.agent.httpclient.v4.helper.RequestHeaderAccessor; import co.elastic.apm.agent.sdk.logging.Logger; import co.elastic.apm.agent.sdk.logging.LoggerFactory; @@ -60,7 +60,7 @@ public static class ApacheHttpClient4Advice extends AbstractApacheHttpClientAdvi public static Object onBeforeExecute(@Advice.Argument(0) HttpRoute route, @Advice.Argument(1) HttpRequestWrapper request) throws URISyntaxException { Span span = startSpan(tracer, adapter, request, route.getTargetHost(), RequestHeaderAccessor.INSTANCE); - RequestBodyCaptureRegistry.potentiallyCaptureRequestBody(request, tracer.getActive()); + RequestBodyCaptureRegistry.potentiallyCaptureRequestBody(request, tracer.getActive(), adapter, RequestHeaderAccessor.INSTANCE); return span; } diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/ApacheHttpEntityGetContentInstrumentation.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/ApacheHttpEntityGetContentInstrumentation.java index b08ea36da9..ceae3061c2 100644 --- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/ApacheHttpEntityGetContentInstrumentation.java +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/ApacheHttpEntityGetContentInstrumentation.java @@ -18,49 +18,25 @@ */ package co.elastic.apm.agent.httpclient.v4; -import co.elastic.apm.agent.httpclient.RequestBodyRecordingInputStream; -import co.elastic.apm.agent.httpclient.RequestBodyRecordingOutputStream; -import co.elastic.apm.agent.httpclient.v4.helper.RequestBodyCaptureRegistry; -import co.elastic.apm.agent.sdk.logging.Logger; -import co.elastic.apm.agent.sdk.logging.LoggerFactory; -import co.elastic.apm.agent.tracer.Span; +import co.elastic.apm.agent.httpclient.common.AbstractApacheHttpRequestBodyCaptureAdvice; import net.bytebuddy.asm.Advice; -import net.bytebuddy.description.NamedElement; import net.bytebuddy.description.method.MethodDescription; -import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; import org.apache.http.HttpEntity; import java.io.InputStream; -import java.io.OutputStream; -import java.net.URISyntaxException; -import static co.elastic.apm.agent.sdk.bytebuddy.CustomElementMatchers.classLoaderCanLoadClass; -import static net.bytebuddy.matcher.ElementMatchers.hasSuperType; -import static net.bytebuddy.matcher.ElementMatchers.isBootstrapClassLoader; -import static net.bytebuddy.matcher.ElementMatchers.nameContains; -import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; import static net.bytebuddy.matcher.ElementMatchers.named; -import static net.bytebuddy.matcher.ElementMatchers.not; -import static net.bytebuddy.matcher.ElementMatchers.returns; -import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import static net.bytebuddy.matcher.ElementMatchers.takesArguments; -public class ApacheHttpEntityGetContentInstrumentation extends BaseApacheHttpClientInstrumentation { +public class ApacheHttpEntityGetContentInstrumentation extends BaseApacheHttpEntityInstrumentation { - public static class ApacheHttpEntityGetContentAdvice { - - private static final Logger logger = LoggerFactory.getLogger(ApacheHttpEntityGetContentAdvice.class); + public static class ApacheHttpEntityGetContentAdvice extends AbstractApacheHttpRequestBodyCaptureAdvice { @Advice.OnMethodExit(suppress = Throwable.class, inline = false) @Advice.AssignReturned.ToReturned - public static InputStream onExit(@Advice.This HttpEntity thiz, @Advice.Return InputStream content) throws URISyntaxException { - Span clientSpan = RequestBodyCaptureRegistry.removeSpanFor(thiz); - if (clientSpan != null) { - logger.debug("Wrapping input stream for request body capture for HttpEntity {} ({}) for span {}", thiz.getClass().getName(), System.identityHashCode(thiz), clientSpan); - return new RequestBodyRecordingInputStream(content, clientSpan); - } - return content; + public static InputStream onExit(@Advice.This HttpEntity thiz, @Advice.Return InputStream content) { + return maybeCaptureRequestBodyInputStream(thiz, content); } } @@ -69,22 +45,6 @@ public String getAdviceClassName() { return "co.elastic.apm.agent.httpclient.v4.ApacheHttpEntityGetContentInstrumentation$ApacheHttpEntityGetContentAdvice"; } - @Override - public ElementMatcher.Junction getClassLoaderMatcher() { - return not(isBootstrapClassLoader()) - .and(classLoaderCanLoadClass("org.apache.http.HttpEntity")); - } - - @Override - public ElementMatcher getTypeMatcherPreFilter() { - return nameStartsWith("org.apache.http").and(nameContains("Entity")); - } - - @Override - public ElementMatcher getTypeMatcher() { - return hasSuperType(named("org.apache.http.HttpEntity")); - } - @Override public ElementMatcher getMethodMatcher() { return named("getContent") diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/ApacheHttpEntityWriteToInstrumentation.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/ApacheHttpEntityWriteToInstrumentation.java index 0b06c8b9fc..0f6b657dfb 100644 --- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/ApacheHttpEntityWriteToInstrumentation.java +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/ApacheHttpEntityWriteToInstrumentation.java @@ -18,53 +18,31 @@ */ package co.elastic.apm.agent.httpclient.v4; -import co.elastic.apm.agent.httpclient.RequestBodyRecordingOutputStream; -import co.elastic.apm.agent.httpclient.v4.helper.RequestBodyCaptureRegistry; -import co.elastic.apm.agent.sdk.logging.Logger; -import co.elastic.apm.agent.sdk.logging.LoggerFactory; -import co.elastic.apm.agent.tracer.Span; +import co.elastic.apm.agent.httpclient.common.AbstractApacheHttpRequestBodyCaptureAdvice; import net.bytebuddy.asm.Advice; -import net.bytebuddy.description.NamedElement; import net.bytebuddy.description.method.MethodDescription; -import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; import org.apache.http.HttpEntity; import java.io.OutputStream; -import java.net.URISyntaxException; -import static co.elastic.apm.agent.sdk.bytebuddy.CustomElementMatchers.classLoaderCanLoadClass; -import static net.bytebuddy.matcher.ElementMatchers.hasSuperType; -import static net.bytebuddy.matcher.ElementMatchers.isBootstrapClassLoader; -import static net.bytebuddy.matcher.ElementMatchers.nameContains; -import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; import static net.bytebuddy.matcher.ElementMatchers.named; -import static net.bytebuddy.matcher.ElementMatchers.not; import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import static net.bytebuddy.matcher.ElementMatchers.takesArguments; -public class ApacheHttpEntityWriteToInstrumentation extends BaseApacheHttpClientInstrumentation { +public class ApacheHttpEntityWriteToInstrumentation extends BaseApacheHttpEntityInstrumentation { - public static class ApacheHttpEntityWriteToAdvice { - - private static final Logger logger = LoggerFactory.getLogger(ApacheHttpEntityWriteToAdvice.class); + public static class ApacheHttpEntityWriteToAdvice extends AbstractApacheHttpRequestBodyCaptureAdvice { @Advice.OnMethodEnter(suppress = Throwable.class, inline = false) @Advice.AssignReturned.ToArguments(@Advice.AssignReturned.ToArguments.ToArgument(0)) - public static OutputStream onEnter(@Advice.This HttpEntity thiz, @Advice.Argument(0) OutputStream drain) throws URISyntaxException { - Span clientSpan = RequestBodyCaptureRegistry.removeSpanFor(thiz); - if (clientSpan != null) { - logger.debug("Wrapping output stream for request body capture for HttpEntity {} ({}) for span {}", thiz.getClass().getName(), System.identityHashCode(thiz), clientSpan); - return new RequestBodyRecordingOutputStream(drain, clientSpan); - } - return drain; + public static OutputStream onEnter(@Advice.This HttpEntity thiz, @Advice.Argument(0) OutputStream drain) { + return maybeCaptureRequestBodyOutputStream(thiz, drain); } @Advice.OnMethodExit(suppress = Throwable.class, inline = false) - public static void onExit(@Advice.Enter OutputStream potentiallyWrappedStream) throws URISyntaxException { - if (potentiallyWrappedStream instanceof RequestBodyRecordingOutputStream) { - ((RequestBodyRecordingOutputStream) potentiallyWrappedStream).releaseSpan(); - } + public static void onExit(@Advice.Enter OutputStream potentiallyWrappedStream) { + releaseRequestBodyOutputStream(potentiallyWrappedStream); } } @@ -73,22 +51,6 @@ public String getAdviceClassName() { return "co.elastic.apm.agent.httpclient.v4.ApacheHttpEntityWriteToInstrumentation$ApacheHttpEntityWriteToAdvice"; } - @Override - public ElementMatcher.Junction getClassLoaderMatcher() { - return not(isBootstrapClassLoader()) - .and(classLoaderCanLoadClass("org.apache.http.HttpEntity")); - } - - @Override - public ElementMatcher getTypeMatcherPreFilter() { - return nameStartsWith("org.apache.http").and(nameContains("Entity")); - } - - @Override - public ElementMatcher getTypeMatcher() { - return hasSuperType(named("org.apache.http.HttpEntity")); - } - @Override public ElementMatcher getMethodMatcher() { return named("writeTo") diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/BaseApacheHttpEntityInstrumentation.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/BaseApacheHttpEntityInstrumentation.java new file mode 100644 index 0000000000..64e5f17e9a --- /dev/null +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/BaseApacheHttpEntityInstrumentation.java @@ -0,0 +1,34 @@ +package co.elastic.apm.agent.httpclient.v4; + +import net.bytebuddy.description.NamedElement; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +import static co.elastic.apm.agent.sdk.bytebuddy.CustomElementMatchers.classLoaderCanLoadClass; +import static net.bytebuddy.matcher.ElementMatchers.hasSuperType; +import static net.bytebuddy.matcher.ElementMatchers.isBootstrapClassLoader; +import static net.bytebuddy.matcher.ElementMatchers.nameContains; +import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.not; + +public abstract class BaseApacheHttpEntityInstrumentation extends BaseApacheHttpClientInstrumentation { + @Override + public ElementMatcher.Junction getClassLoaderMatcher() { + return not(isBootstrapClassLoader()) + .and(classLoaderCanLoadClass("org.apache.http.HttpEntity")); + } + + @Override + public ElementMatcher getTypeMatcherPreFilter() { + return nameContains("Entity").and( + nameStartsWith("org.apache.http") + .or(nameStartsWith("org.springframework.http")) //spring implements its own HttpEntities + ); + } + + @Override + public ElementMatcher getTypeMatcher() { + return hasSuperType(named("org.apache.http.HttpEntity")); + } +} diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/LegacyApacheHttpClientInstrumentation.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/LegacyApacheHttpClientInstrumentation.java index 21fe4ad545..0698839563 100644 --- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/LegacyApacheHttpClientInstrumentation.java +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/LegacyApacheHttpClientInstrumentation.java @@ -19,7 +19,8 @@ package co.elastic.apm.agent.httpclient.v4; import co.elastic.apm.agent.httpclient.HttpClientHelper; -import co.elastic.apm.agent.httpclient.v4.helper.RequestBodyCaptureRegistry; +import co.elastic.apm.agent.httpclient.common.RequestBodyCaptureRegistry; +import co.elastic.apm.agent.httpclient.v4.helper.ApacheHttpClient4ApiAdapter; import co.elastic.apm.agent.httpclient.v4.helper.RequestHeaderAccessor; import co.elastic.apm.agent.tracer.Outcome; import co.elastic.apm.agent.tracer.Span; @@ -106,7 +107,7 @@ public static Object onBeforeExecute(@Advice.Argument(0) @Nullable HttpHost host } } - RequestBodyCaptureRegistry.potentiallyCaptureRequestBody(request, tracer.getActive()); + RequestBodyCaptureRegistry.potentiallyCaptureRequestBody(request, tracer.getActive(), ApacheHttpClient4ApiAdapter.get(), RequestHeaderAccessor.INSTANCE); tracer.currentContext().propagateContext(request, RequestHeaderAccessor.INSTANCE, RequestHeaderAccessor.INSTANCE); return span; } diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/helper/ApacheHttpClient4ApiAdapter.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/helper/ApacheHttpClient4ApiAdapter.java index fd763f23d5..7d8b834e0f 100644 --- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/helper/ApacheHttpClient4ApiAdapter.java +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/helper/ApacheHttpClient4ApiAdapter.java @@ -23,6 +23,8 @@ import co.elastic.apm.agent.tracer.GlobalTracer; import co.elastic.apm.agent.tracer.Tracer; import co.elastic.apm.agent.tracer.configuration.CoreConfiguration; +import org.apache.http.HttpEntity; +import org.apache.http.HttpEntityEnclosingRequest; import org.apache.http.HttpHost; import org.apache.http.HttpRequest; import org.apache.http.StatusLine; @@ -30,9 +32,10 @@ import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpRequestWrapper; +import javax.annotation.Nullable; import java.net.URI; -public class ApacheHttpClient4ApiAdapter implements ApacheHttpClientApiAdapter { +public class ApacheHttpClient4ApiAdapter implements ApacheHttpClientApiAdapter { private static final ApacheHttpClient4ApiAdapter INSTANCE = new ApacheHttpClient4ApiAdapter(); private final Tracer tracer = GlobalTracer.get(); @@ -59,6 +62,20 @@ public CharSequence getHostName(HttpHost httpHost, HttpRequestWrapper request) { return httpHost.getHostName(); } + @Override + public HttpEntity getRequestEntity(HttpRequest request) { + if (request instanceof HttpEntityEnclosingRequest) { + return ((HttpEntityEnclosingRequest) request).getEntity(); + } + return null; + } + + @Override + @Nullable + public byte[] getSimpleBodyBytes(HttpRequest request) { + return null; //Apache v4 client only provides body via HttpEntity + } + @Override public int getResponseCode(CloseableHttpResponse closeableHttpResponse) { final StatusLine statusLine = closeableHttpResponse.getStatusLine(); diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/helper/HttpAsyncRequestProducerWrapper.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/helper/HttpAsyncRequestProducerWrapper.java index a8648894ba..918fd06f07 100644 --- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/helper/HttpAsyncRequestProducerWrapper.java +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient4-plugin/src/main/java/co/elastic/apm/agent/httpclient/v4/helper/HttpAsyncRequestProducerWrapper.java @@ -19,8 +19,9 @@ package co.elastic.apm.agent.httpclient.v4.helper; import co.elastic.apm.agent.httpclient.HttpClientHelper; -import co.elastic.apm.agent.tracer.TraceState; +import co.elastic.apm.agent.httpclient.common.RequestBodyCaptureRegistry; import co.elastic.apm.agent.tracer.Span; +import co.elastic.apm.agent.tracer.TraceState; import co.elastic.apm.agent.tracer.pooling.Recyclable; import org.apache.http.HttpException; import org.apache.http.HttpHost; @@ -85,7 +86,7 @@ public HttpRequest generateRequest() throws IOException, HttpException { // trace context propagation if (request != null) { if (span != null) { - RequestBodyCaptureRegistry.potentiallyCaptureRequestBody(request, span); + RequestBodyCaptureRegistry.potentiallyCaptureRequestBody(request, span, ApacheHttpClient4ApiAdapter.get(), RequestHeaderAccessor.INSTANCE); RequestLine requestLine = request.getRequestLine(); if (requestLine != null) { String method = requestLine.getMethod(); diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/ApacheHttp5EntityGetContentInstrumentation.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/ApacheHttp5EntityGetContentInstrumentation.java new file mode 100644 index 0000000000..d51d5c016c --- /dev/null +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/ApacheHttp5EntityGetContentInstrumentation.java @@ -0,0 +1,54 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + */ +package co.elastic.apm.agent.httpclient.v5; + +import co.elastic.apm.agent.httpclient.common.AbstractApacheHttpRequestBodyCaptureAdvice; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.hc.core5.http.HttpEntity; + +import java.io.InputStream; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +public class ApacheHttp5EntityGetContentInstrumentation extends BaseApacheHttp5EntityInstrumentation { + + public static class ApacheHttpEntityGetContentAdvice extends AbstractApacheHttpRequestBodyCaptureAdvice { + + @Advice.OnMethodExit(suppress = Throwable.class, inline = false) + @Advice.AssignReturned.ToReturned + public static InputStream onExit(@Advice.This HttpEntity thiz, @Advice.Return InputStream content) { + return maybeCaptureRequestBodyInputStream(thiz, content); + } + } + + @Override + public String getAdviceClassName() { + return "co.elastic.apm.agent.httpclient.v5.ApacheHttp5EntityGetContentInstrumentation$ApacheHttpEntityGetContentAdvice"; + } + + @Override + public ElementMatcher getMethodMatcher() { + return named("getContent") + .and(takesArguments(0)); + } + +} diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/ApacheHttp5EntityWriteToInstrumentation.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/ApacheHttp5EntityWriteToInstrumentation.java new file mode 100644 index 0000000000..994ac3f26f --- /dev/null +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/ApacheHttp5EntityWriteToInstrumentation.java @@ -0,0 +1,61 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you 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. + */ +package co.elastic.apm.agent.httpclient.v5; + +import co.elastic.apm.agent.httpclient.common.AbstractApacheHttpRequestBodyCaptureAdvice; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.hc.core5.http.HttpEntity; + +import java.io.OutputStream; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +public class ApacheHttp5EntityWriteToInstrumentation extends BaseApacheHttp5EntityInstrumentation { + + public static class ApacheHttpEntityWriteToAdvice extends AbstractApacheHttpRequestBodyCaptureAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class, inline = false) + @Advice.AssignReturned.ToArguments(@Advice.AssignReturned.ToArguments.ToArgument(0)) + public static OutputStream onEnter(@Advice.This HttpEntity thiz, @Advice.Argument(0) OutputStream drain) { + return maybeCaptureRequestBodyOutputStream(thiz, drain); + } + + @Advice.OnMethodExit(suppress = Throwable.class, inline = false) + public static void onExit(@Advice.Enter OutputStream potentiallyWrappedStream) { + releaseRequestBodyOutputStream(potentiallyWrappedStream); + } + } + + @Override + public String getAdviceClassName() { + return "co.elastic.apm.agent.httpclient.v5.ApacheHttp5EntityWriteToInstrumentation$ApacheHttpEntityWriteToAdvice"; + } + + @Override + public ElementMatcher getMethodMatcher() { + return named("writeTo") + .and(takesArguments(1)) + .and(takesArgument(0, OutputStream.class)); + } + +} diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/ApacheHttpClient5Instrumentation.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/ApacheHttpClient5Instrumentation.java index 5170a37f3e..5fe23bf705 100644 --- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/ApacheHttpClient5Instrumentation.java +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/ApacheHttpClient5Instrumentation.java @@ -20,8 +20,10 @@ import co.elastic.apm.agent.httpclient.common.AbstractApacheHttpClientAdvice; +import co.elastic.apm.agent.httpclient.common.RequestBodyCaptureRegistry; import co.elastic.apm.agent.httpclient.v5.helper.ApacheHttpClient5ApiAdapter; import co.elastic.apm.agent.httpclient.v5.helper.RequestHeaderAccessor; +import co.elastic.apm.agent.tracer.Span; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.NamedElement; import net.bytebuddy.description.method.MethodDescription; @@ -50,7 +52,9 @@ public static class ApacheHttpClient5Advice extends AbstractApacheHttpClientAdvi @Advice.OnMethodEnter(suppress = Throwable.class, inline = false) public static Object onBeforeExecute(@Advice.Argument(0) @Nullable HttpHost httpHost, @Advice.Argument(1) ClassicHttpRequest request) throws URISyntaxException { - return startSpan(tracer, adapter, request, httpHost, RequestHeaderAccessor.INSTANCE); + Span resultSpan = startSpan(tracer, adapter, request, httpHost, RequestHeaderAccessor.INSTANCE); + RequestBodyCaptureRegistry.potentiallyCaptureRequestBody(request, tracer.getActive(), adapter, RequestHeaderAccessor.INSTANCE); + return resultSpan; } @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false) diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/BaseApacheHttp5EntityInstrumentation.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/BaseApacheHttp5EntityInstrumentation.java new file mode 100644 index 0000000000..6628a50dce --- /dev/null +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/BaseApacheHttp5EntityInstrumentation.java @@ -0,0 +1,33 @@ +package co.elastic.apm.agent.httpclient.v5; + +import net.bytebuddy.description.NamedElement; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +import static co.elastic.apm.agent.sdk.bytebuddy.CustomElementMatchers.classLoaderCanLoadClass; +import static net.bytebuddy.matcher.ElementMatchers.hasSuperType; +import static net.bytebuddy.matcher.ElementMatchers.isBootstrapClassLoader; +import static net.bytebuddy.matcher.ElementMatchers.nameContains; +import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.not; + +public abstract class BaseApacheHttp5EntityInstrumentation extends BaseApacheHttpClient5Instrumentation { + @Override + public ElementMatcher.Junction getClassLoaderMatcher() { + return not(isBootstrapClassLoader()).and(classLoaderCanLoadClass("org.apache.hc.core5.http.HttpEntity")); + } + + @Override + public ElementMatcher getTypeMatcherPreFilter() { + return nameContains("Entity").and( + nameStartsWith("org.apache.hc") + .or(nameStartsWith("org.springframework.http")) //spring implements its own HttpEntities + ); + } + + @Override + public ElementMatcher getTypeMatcher() { + return hasSuperType(named("org.apache.hc.core5.http.HttpEntity")); + } +} diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/helper/ApacheHttpClient5ApiAdapter.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/helper/ApacheHttpClient5ApiAdapter.java index 233a92188a..46d63539b9 100644 --- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/helper/ApacheHttpClient5ApiAdapter.java +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/helper/ApacheHttpClient5ApiAdapter.java @@ -25,9 +25,12 @@ import co.elastic.apm.agent.tracer.Tracer; import co.elastic.apm.agent.tracer.configuration.CoreConfiguration; import org.apache.hc.client5.http.CircularRedirectException; +import org.apache.hc.client5.http.async.methods.SimpleHttpRequest; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; import org.apache.hc.client5.http.routing.RoutingSupport; import org.apache.hc.core5.http.ClassicHttpRequest; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpEntityContainer; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; @@ -36,7 +39,7 @@ import java.net.URI; import java.net.URISyntaxException; -public class ApacheHttpClient5ApiAdapter implements ApacheHttpClientApiAdapter { +public class ApacheHttpClient5ApiAdapter implements ApacheHttpClientApiAdapter { private static final ApacheHttpClient5ApiAdapter INSTANCE = new ApacheHttpClient5ApiAdapter(); private static final Logger logger = LoggerFactory.getLogger(ApacheHttpClient5ApiAdapter.class); @@ -76,6 +79,15 @@ public CharSequence getHostName(@Nullable HttpHost httpHost, ClassicHttpRequest } } + @Nullable + @Override + public byte[] getSimpleBodyBytes(HttpRequest httpRequest) { + if (httpRequest instanceof SimpleHttpRequest) { + return ((SimpleHttpRequest) httpRequest).getBodyBytes(); + } + return null; + } + @Override public int getResponseCode(CloseableHttpResponse closeableHttpResponse) { return closeableHttpResponse.getCode(); @@ -94,4 +106,13 @@ public boolean isNotNullStatusLine(CloseableHttpResponse closeableHttpResponse) // HTTP response messages in HttpClient 5.x no longer have a status line. return true; } + + @Nullable + @Override + public HttpEntity getRequestEntity(HttpRequest httpRequest) { + if (httpRequest instanceof HttpEntityContainer) { + return ((HttpEntityContainer) httpRequest).getEntity(); + } + return null; + } } diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/helper/RequestChannelWrapper.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/helper/RequestChannelWrapper.java index 0edd80e85e..51722905ab 100644 --- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/helper/RequestChannelWrapper.java +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/java/co/elastic/apm/agent/httpclient/v5/helper/RequestChannelWrapper.java @@ -20,10 +20,11 @@ import co.elastic.apm.agent.httpclient.HttpClientHelper; +import co.elastic.apm.agent.httpclient.common.RequestBodyCaptureRegistry; import co.elastic.apm.agent.sdk.logging.Logger; import co.elastic.apm.agent.sdk.logging.LoggerFactory; -import co.elastic.apm.agent.tracer.TraceState; import co.elastic.apm.agent.tracer.Span; +import co.elastic.apm.agent.tracer.TraceState; import co.elastic.apm.agent.tracer.pooling.Recyclable; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.HttpException; @@ -78,6 +79,7 @@ public void sendRequest(HttpRequest httpRequest, EntityDetails entityDetails, Ht if (httpRequest != null) { if (span != null) { + RequestBodyCaptureRegistry.potentiallyCaptureRequestBody(httpRequest, span, ApacheHttpClient5ApiAdapter.get(), RequestHeaderAccessor.INSTANCE); String host = null; String protocol = null; int port = -1; diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation index 04dd6ced97..a684cb014f 100644 --- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/main/resources/META-INF/services/co.elastic.apm.agent.sdk.ElasticApmInstrumentation @@ -1,2 +1,4 @@ co.elastic.apm.agent.httpclient.v5.ApacheHttpClient5Instrumentation co.elastic.apm.agent.httpclient.v5.ApacheHttpAsyncClient5Instrumentation +co.elastic.apm.agent.httpclient.v5.ApacheHttp5EntityWriteToInstrumentation +co.elastic.apm.agent.httpclient.v5.ApacheHttp5EntityGetContentInstrumentation diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/test/java/co/elastic/apm/agent/httpclient/v5/ApacheHttpAsyncClientInstrumentationTest.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/test/java/co/elastic/apm/agent/httpclient/v5/ApacheHttpAsyncClientInstrumentationTest.java index ed117da025..e709e71ebe 100644 --- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/test/java/co/elastic/apm/agent/httpclient/v5/ApacheHttpAsyncClientInstrumentationTest.java +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/test/java/co/elastic/apm/agent/httpclient/v5/ApacheHttpAsyncClientInstrumentationTest.java @@ -29,6 +29,8 @@ import org.apache.hc.client5.http.impl.async.HttpAsyncClients; import org.apache.hc.client5.http.protocol.HttpClientContext; import org.apache.hc.core5.concurrent.FutureCallback; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.net.URIAuthority; import org.junit.AfterClass; @@ -86,6 +88,44 @@ public void cancelled() { responseFuture.get(); } + @Override + protected boolean isBodyCapturingSupported() { + return true; + } + + @Override + public void testPostBodyCaptureForExistingSpan() throws Exception { + //TODO: async http client instrumentation does not support capturing bodies for existing spans yet + } + + @Override + protected void performPost(String path, byte[] data, String contentTypeHeader) throws Exception { + final CompletableFuture responseFuture = new CompletableFuture<>(); + SimpleHttpRequest req = SimpleRequestBuilder.get().setPath(path) + .addHeader("Content-Type", contentTypeHeader) + .setBody(data, ContentType.parse(contentTypeHeader)) + .build(); + HttpClientContext httpClientContext = HttpClientContext.create(); + client.execute(req, httpClientContext, new FutureCallback() { + @Override + public void completed(SimpleHttpResponse simpleHttpResponse) { + responseFuture.complete(simpleHttpResponse); + } + + @Override + public void failed(Exception e) { + responseFuture.completeExceptionally(e); + } + + @Override + public void cancelled() { + responseFuture.cancel(true); + } + }); + + responseFuture.get(); + } + @Test public void testSpanFinishOnEarlyException() throws Exception { diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/test/java/co/elastic/apm/agent/httpclient/v5/ApacheHttpClientExecuteOpenInstrumentationTest.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/test/java/co/elastic/apm/agent/httpclient/v5/ApacheHttpClientExecuteOpenInstrumentationTest.java index ef04c9f59e..0ec0db4f15 100644 --- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/test/java/co/elastic/apm/agent/httpclient/v5/ApacheHttpClientExecuteOpenInstrumentationTest.java +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/test/java/co/elastic/apm/agent/httpclient/v5/ApacheHttpClientExecuteOpenInstrumentationTest.java @@ -22,15 +22,19 @@ import co.elastic.apm.agent.httpclient.AbstractHttpClientInstrumentationTest; import org.apache.hc.client5.http.ClientProtocolException; import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.io.HttpClientResponseHandler; import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.http.io.entity.InputStreamEntity; import org.junit.AfterClass; import org.junit.BeforeClass; +import java.io.ByteArrayInputStream; import java.io.IOException; public class ApacheHttpClientExecuteOpenInstrumentationTest extends AbstractHttpClientInstrumentationTest { @@ -59,20 +63,34 @@ public boolean isTestHttpCallWithUserInfoEnabled() { return false; } + private final HttpClientResponseHandler responseHandler = response -> { + int status = response.getCode(); + if (status >= 200 && status < 300) { + HttpEntity entity = response.getEntity(); + return entity != null ? EntityUtils.toString(entity) : null; + } else { + throw new ClientProtocolException("Unexpected response status: " + status); + } + }; + @Override protected void performGet(String path) throws Exception { - HttpClientResponseHandler responseHandler = response -> { - int status = response.getCode(); - if (status >= 200 && status < 300) { - HttpEntity entity = response.getEntity(); - String res = entity != null ? EntityUtils.toString(entity) : null; - return res; - } else { - throw new ClientProtocolException("Unexpected response status: " + status); - } - }; - ClassicHttpResponse response = client.executeOpen(null, new HttpGet(path), null); responseHandler.handleResponse(response); } + + @Override + protected boolean isBodyCapturingSupported() { + return true; + } + + @Override + protected void performPost(String path, byte[] content, String contentTypeHeader) throws Exception { + HttpPost request = new HttpPost(path); + request.setEntity(new InputStreamEntity(new ByteArrayInputStream(content), ContentType.parse(contentTypeHeader))); + request.setHeader("Content-Type", contentTypeHeader); + + ClassicHttpResponse response = client.executeOpen(null, request, null); + responseHandler.handleResponse(response); + } } diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/test/java/co/elastic/apm/agent/httpclient/v5/ApacheHttpClientInstrumentationTest.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/test/java/co/elastic/apm/agent/httpclient/v5/ApacheHttpClientInstrumentationTest.java index 7ed5a2163c..0bb2a70457 100644 --- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/test/java/co/elastic/apm/agent/httpclient/v5/ApacheHttpClientInstrumentationTest.java +++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/test/java/co/elastic/apm/agent/httpclient/v5/ApacheHttpClientInstrumentationTest.java @@ -22,14 +22,18 @@ import co.elastic.apm.agent.httpclient.AbstractHttpClientInstrumentationTest; import org.apache.hc.client5.http.ClientProtocolException; import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.io.HttpClientResponseHandler; import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.http.io.entity.InputStreamEntity; import org.junit.AfterClass; import org.junit.BeforeClass; +import java.io.ByteArrayInputStream; import java.io.IOException; public class ApacheHttpClientInstrumentationTest extends AbstractHttpClientInstrumentationTest { @@ -58,18 +62,33 @@ public boolean isTestHttpCallWithUserInfoEnabled() { return false; } + private final HttpClientResponseHandler responseHandler = response -> { + int status = response.getCode(); + if (status >= 200 && status < 300) { + HttpEntity entity = response.getEntity(); + String res = entity != null ? EntityUtils.toString(entity) : null; + return res; + } else { + throw new ClientProtocolException("Unexpected response status: " + status); + } + }; + @Override protected void performGet(String path) throws Exception { - HttpClientResponseHandler responseHandler = response -> { - int status = response.getCode(); - if (status >= 200 && status < 300) { - HttpEntity entity = response.getEntity(); - String res = entity != null ? EntityUtils.toString(entity) : null; - return res; - } else { - throw new ClientProtocolException("Unexpected response status: " + status); - } - }; - String response = client.execute(new HttpGet(path), responseHandler); + client.execute(new HttpGet(path), responseHandler); + } + + @Override + protected boolean isBodyCapturingSupported() { + return true; + } + + @Override + protected void performPost(String path, byte[] content, String contentTypeHeader) throws Exception { + HttpPost request = new HttpPost(path); + request.setEntity(new InputStreamEntity(new ByteArrayInputStream(content), ContentType.parse(contentTypeHeader))); + request.setHeader("Content-Type", contentTypeHeader); + + client.execute(request, responseHandler); } } diff --git a/apm-agent-plugins/apm-spring-resttemplate/apm-spring-resttemplate-plugin/src/test/java/co/elastic/apm/agent/resttemplate/SpringRestTemplateInstrumentationTest.java b/apm-agent-plugins/apm-spring-resttemplate/apm-spring-resttemplate-plugin/src/test/java/co/elastic/apm/agent/resttemplate/SpringRestTemplateInstrumentationTest.java index 307e2f815c..d822e255b3 100644 --- a/apm-agent-plugins/apm-spring-resttemplate/apm-spring-resttemplate-plugin/src/test/java/co/elastic/apm/agent/resttemplate/SpringRestTemplateInstrumentationTest.java +++ b/apm-agent-plugins/apm-spring-resttemplate/apm-spring-resttemplate-plugin/src/test/java/co/elastic/apm/agent/resttemplate/SpringRestTemplateInstrumentationTest.java @@ -103,10 +103,6 @@ public static boolean isBodyCapturingSupported(Object restTemplateObj) { // We do not support body capturing for OkHttp yet return false; } - if (restTemplate.getRequestFactory() instanceof HttpComponentsClientHttpRequestFactory) { - //apache http client v5 is also not supported yet - return false; - } return true; }