Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -34,11 +34,11 @@ public abstract class AbstractApacheHttpClientAdvice {

public static <REQUEST, WRAPPER extends REQUEST, HTTPHOST, RESPONSE,
HeaderAccessor extends TextHeaderSetter<REQUEST> &
TextHeaderGetter<REQUEST>> Span<?> startSpan(final Tracer tracer,
final ApacheHttpClientApiAdapter<REQUEST, WRAPPER, HTTPHOST, RESPONSE> adapter,
final WRAPPER request,
@Nullable final HTTPHOST httpHost,
final HeaderAccessor headerAccessor) throws URISyntaxException {
TextHeaderGetter<REQUEST>, HTTPENTITY> Span<?> startSpan(final Tracer tracer,
final ApacheHttpClientApiAdapter<REQUEST, WRAPPER, HTTPHOST, RESPONSE, HTTPENTITY> adapter,
final WRAPPER request,
@Nullable final HTTPHOST httpHost,
final HeaderAccessor headerAccessor) throws URISyntaxException {
TraceState<?> traceState = tracer.currentContext();
Span<?> span = null;
if (traceState.getSpan() != null) {
Expand All @@ -51,10 +51,11 @@ TextHeaderGetter<REQUEST>> Span<?> startSpan(final Tracer tracer,
return span;
}

public static <REQUEST, WRAPPER extends REQUEST, HTTPHOST, RESPONSE> void endSpan(ApacheHttpClientApiAdapter<REQUEST, WRAPPER, HTTPHOST, RESPONSE> adapter,
Object spanObj,
Throwable t,
RESPONSE response) {
public static <REQUEST, WRAPPER extends REQUEST, HTTPHOST, RESPONSE, HTTPENTITY>
void endSpan(ApacheHttpClientApiAdapter<REQUEST, WRAPPER, HTTPHOST, RESPONSE, HTTPENTITY> adapter,
Object spanObj,
Throwable t,
RESPONSE response) {
Span<?> span = (Span<?>) spanObj;
if (span == null) {
return;
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <HTTPENTITY> 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 <HTTPENTITY> 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();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import java.net.URI;
import java.net.URISyntaxException;

public interface ApacheHttpClientApiAdapter<REQUEST, WRAPPER extends REQUEST, HTTPHOST, RESPONSE> {
public interface ApacheHttpClientApiAdapter<REQUEST, WRAPPER extends REQUEST, HTTPHOST, RESPONSE, HTTPENTITY> extends ApacheHttpClientEntityAccessor<REQUEST, HTTPENTITY> {
String getMethod(WRAPPER request);

URI getUri(WRAPPER request) throws URISyntaxException;
Expand Down
Original file line number Diff line number Diff line change
@@ -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<REQUEST, HTTPENTITY> {
@Nullable
HTTPENTITY getRequestEntity(REQUEST request);

@Nullable
byte[] getSimpleBodyBytes(REQUEST request);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -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 <REQUEST, HTTPENTITY> void potentiallyCaptureRequestBody(
REQUEST request,
@Nullable AbstractSpan<?> abstractSpan,
ApacheHttpClientEntityAccessor<REQUEST, HTTPENTITY> adapter,
TextHeaderGetter<REQUEST> 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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand All @@ -69,22 +45,6 @@ public String getAdviceClassName() {
return "co.elastic.apm.agent.httpclient.v4.ApacheHttpEntityGetContentInstrumentation$ApacheHttpEntityGetContentAdvice";
}

@Override
public ElementMatcher.Junction<ClassLoader> getClassLoaderMatcher() {
return not(isBootstrapClassLoader())
.and(classLoaderCanLoadClass("org.apache.http.HttpEntity"));
}

@Override
public ElementMatcher<? super NamedElement> getTypeMatcherPreFilter() {
return nameStartsWith("org.apache.http").and(nameContains("Entity"));
}

@Override
public ElementMatcher<? super TypeDescription> getTypeMatcher() {
return hasSuperType(named("org.apache.http.HttpEntity"));
}

@Override
public ElementMatcher<? super MethodDescription> getMethodMatcher() {
return named("getContent")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand All @@ -73,22 +51,6 @@ public String getAdviceClassName() {
return "co.elastic.apm.agent.httpclient.v4.ApacheHttpEntityWriteToInstrumentation$ApacheHttpEntityWriteToAdvice";
}

@Override
public ElementMatcher.Junction<ClassLoader> getClassLoaderMatcher() {
return not(isBootstrapClassLoader())
.and(classLoaderCanLoadClass("org.apache.http.HttpEntity"));
}

@Override
public ElementMatcher<? super NamedElement> getTypeMatcherPreFilter() {
return nameStartsWith("org.apache.http").and(nameContains("Entity"));
}

@Override
public ElementMatcher<? super TypeDescription> getTypeMatcher() {
return hasSuperType(named("org.apache.http.HttpEntity"));
}

@Override
public ElementMatcher<? super MethodDescription> getMethodMatcher() {
return named("writeTo")
Expand Down
Loading