Add support for apache httpclient5 (#2254)
* Add support for apache httpclient5 * review fixes * copy commnet from httpclient-4 instrumentation * rebase
This commit is contained in:
parent
3fc1765446
commit
50cc8ddc8f
|
@ -0,0 +1,13 @@
|
||||||
|
apply from: "$rootDir/gradle/instrumentation.gradle"
|
||||||
|
|
||||||
|
muzzle {
|
||||||
|
pass {
|
||||||
|
group = "org.apache.httpcomponents.client5"
|
||||||
|
module = "httpclient5"
|
||||||
|
versions = "[5.0,)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
library group: 'org.apache.httpcomponents.client5', name: 'httpclient5', version: '5.0'
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0;
|
||||||
|
|
||||||
|
import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0.ApacheHttpClientTracer.tracer;
|
||||||
|
|
||||||
|
import io.opentelemetry.context.Context;
|
||||||
|
import org.apache.hc.core5.http.HttpResponse;
|
||||||
|
|
||||||
|
public class ApacheHttpClientHelper {
|
||||||
|
|
||||||
|
public static void doMethodExit(Context context, Object result, Throwable throwable) {
|
||||||
|
if (throwable != null) {
|
||||||
|
tracer().endExceptionally(context, throwable);
|
||||||
|
} else if (result instanceof HttpResponse) {
|
||||||
|
tracer().end(context, (HttpResponse) result);
|
||||||
|
} else {
|
||||||
|
// ended in WrappingStatusSettingResponseHandler
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,363 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0;
|
||||||
|
|
||||||
|
import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0.ApacheHttpClientTracer.tracer;
|
||||||
|
import static io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge.currentContext;
|
||||||
|
import static io.opentelemetry.javaagent.tooling.bytebuddy.matcher.AgentElementMatchers.implementsInterface;
|
||||||
|
import static io.opentelemetry.javaagent.tooling.bytebuddy.matcher.ClassLoaderMatcher.hasClassesNamed;
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.isAbstract;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
||||||
|
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;
|
||||||
|
|
||||||
|
import com.google.auto.service.AutoService;
|
||||||
|
import io.opentelemetry.context.Context;
|
||||||
|
import io.opentelemetry.context.Scope;
|
||||||
|
import io.opentelemetry.javaagent.tooling.InstrumentationModule;
|
||||||
|
import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import net.bytebuddy.asm.Advice;
|
||||||
|
import net.bytebuddy.description.method.MethodDescription;
|
||||||
|
import net.bytebuddy.description.type.TypeDescription;
|
||||||
|
import net.bytebuddy.matcher.ElementMatcher;
|
||||||
|
import org.apache.hc.core5.http.ClassicHttpRequest;
|
||||||
|
import org.apache.hc.core5.http.HttpHost;
|
||||||
|
import org.apache.hc.core5.http.io.HttpClientResponseHandler;
|
||||||
|
|
||||||
|
@AutoService(InstrumentationModule.class)
|
||||||
|
public class ApacheHttpClientInstrumentationModule extends InstrumentationModule {
|
||||||
|
|
||||||
|
public ApacheHttpClientInstrumentationModule() {
|
||||||
|
super("apache-httpclient", "apache-httpclient-5.0");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<TypeInstrumentation> typeInstrumentations() {
|
||||||
|
return singletonList(new HttpClientInstrumentation());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class HttpClientInstrumentation implements TypeInstrumentation {
|
||||||
|
@Override
|
||||||
|
public ElementMatcher<ClassLoader> classLoaderOptimization() {
|
||||||
|
return hasClassesNamed("org.apache.hc.client5.http.classic.HttpClient");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ElementMatcher<TypeDescription> typeMatcher() {
|
||||||
|
return implementsInterface(named("org.apache.hc.client5.http.classic.HttpClient"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
|
||||||
|
Map<ElementMatcher<? super MethodDescription>, String> transformers = new HashMap<>();
|
||||||
|
// There are 8 execute(...) methods. Depending on the version, they may or may not delegate
|
||||||
|
// to each other. Thus, all methods need to be instrumented. Because of argument position and
|
||||||
|
// type, some methods can share the same advice class. The call depth tracking ensures only 1
|
||||||
|
// span is created
|
||||||
|
transformers.put(
|
||||||
|
isMethod()
|
||||||
|
.and(named("execute"))
|
||||||
|
.and(not(isAbstract()))
|
||||||
|
.and(takesArguments(1))
|
||||||
|
.and(takesArgument(0, named("org.apache.hc.core5.http.ClassicHttpRequest"))),
|
||||||
|
ApacheHttpClientInstrumentationModule.class.getName() + "$RequestAdvice");
|
||||||
|
|
||||||
|
transformers.put(
|
||||||
|
isMethod()
|
||||||
|
.and(named("execute"))
|
||||||
|
.and(not(isAbstract()))
|
||||||
|
.and(takesArguments(2))
|
||||||
|
.and(takesArgument(0, named("org.apache.hc.core5.http.ClassicHttpRequest")))
|
||||||
|
.and(takesArgument(1, named("org.apache.hc.core5.http.protocol.HttpContext"))),
|
||||||
|
ApacheHttpClientInstrumentationModule.class.getName() + "$RequestAdvice");
|
||||||
|
|
||||||
|
transformers.put(
|
||||||
|
isMethod()
|
||||||
|
.and(named("execute"))
|
||||||
|
.and(not(isAbstract()))
|
||||||
|
.and(takesArguments(2))
|
||||||
|
.and(takesArgument(0, named("org.apache.hc.core5.http.HttpHost")))
|
||||||
|
.and(takesArgument(1, named("org.apache.hc.core5.http.ClassicHttpRequest"))),
|
||||||
|
ApacheHttpClientInstrumentationModule.class.getName() + "$RequestWithHostAdvice");
|
||||||
|
|
||||||
|
transformers.put(
|
||||||
|
isMethod()
|
||||||
|
.and(named("execute"))
|
||||||
|
.and(not(isAbstract()))
|
||||||
|
.and(takesArguments(3))
|
||||||
|
.and(takesArgument(0, named("org.apache.hc.core5.http.HttpHost")))
|
||||||
|
.and(takesArgument(1, named("org.apache.hc.core5.http.ClassicHttpRequest")))
|
||||||
|
.and(takesArgument(2, named("org.apache.hc.core5.http.protocol.HttpContext"))),
|
||||||
|
ApacheHttpClientInstrumentationModule.class.getName() + "$RequestWithHostAdvice");
|
||||||
|
|
||||||
|
transformers.put(
|
||||||
|
isMethod()
|
||||||
|
.and(named("execute"))
|
||||||
|
.and(not(isAbstract()))
|
||||||
|
.and(takesArguments(2))
|
||||||
|
.and(takesArgument(0, named("org.apache.hc.core5.http.ClassicHttpRequest")))
|
||||||
|
.and(
|
||||||
|
takesArgument(1, named("org.apache.hc.core5.http.io.HttpClientResponseHandler"))),
|
||||||
|
ApacheHttpClientInstrumentationModule.class.getName() + "$RequestWithHandlerAdvice");
|
||||||
|
|
||||||
|
transformers.put(
|
||||||
|
isMethod()
|
||||||
|
.and(named("execute"))
|
||||||
|
.and(not(isAbstract()))
|
||||||
|
.and(takesArguments(3))
|
||||||
|
.and(takesArgument(0, named("org.apache.hc.core5.http.ClassicHttpRequest")))
|
||||||
|
.and(takesArgument(1, named("org.apache.hc.core5.http.protocol.HttpContext")))
|
||||||
|
.and(
|
||||||
|
takesArgument(2, named("org.apache.hc.core5.http.io.HttpClientResponseHandler"))),
|
||||||
|
ApacheHttpClientInstrumentationModule.class.getName()
|
||||||
|
+ "$RequestWithContextAndHandlerAdvice");
|
||||||
|
|
||||||
|
transformers.put(
|
||||||
|
isMethod()
|
||||||
|
.and(named("execute"))
|
||||||
|
.and(not(isAbstract()))
|
||||||
|
.and(takesArguments(3))
|
||||||
|
.and(takesArgument(0, named("org.apache.hc.core5.http.HttpHost")))
|
||||||
|
.and(takesArgument(1, named("org.apache.hc.core5.http.ClassicHttpRequest")))
|
||||||
|
.and(
|
||||||
|
takesArgument(2, named("org.apache.hc.core5.http.io.HttpClientResponseHandler"))),
|
||||||
|
ApacheHttpClientInstrumentationModule.class.getName()
|
||||||
|
+ "$RequestWithHostAndHandlerAdvice");
|
||||||
|
|
||||||
|
transformers.put(
|
||||||
|
isMethod()
|
||||||
|
.and(named("execute"))
|
||||||
|
.and(not(isAbstract()))
|
||||||
|
.and(takesArguments(4))
|
||||||
|
.and(takesArgument(0, named("org.apache.hc.core5.http.HttpHost")))
|
||||||
|
.and(takesArgument(1, named("org.apache.hc.core5.http.ClassicHttpRequest")))
|
||||||
|
.and(takesArgument(2, named("org.apache.hc.core5.http.protocol.HttpContext")))
|
||||||
|
.and(
|
||||||
|
takesArgument(3, named("org.apache.hc.core5.http.io.HttpClientResponseHandler"))),
|
||||||
|
ApacheHttpClientInstrumentationModule.class.getName()
|
||||||
|
+ "$RequestWithHostAndContextAndHandlerAdvice");
|
||||||
|
|
||||||
|
return transformers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RequestAdvice {
|
||||||
|
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||||
|
public static void methodEnter(
|
||||||
|
@Advice.Argument(0) ClassicHttpRequest request,
|
||||||
|
@Advice.Local("otelContext") Context context,
|
||||||
|
@Advice.Local("otelScope") Scope scope) {
|
||||||
|
Context parentContext = currentContext();
|
||||||
|
if (!tracer().shouldStartSpan(parentContext)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context = tracer().startSpan(parentContext, request);
|
||||||
|
scope = context.makeCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||||
|
public static void methodExit(
|
||||||
|
@Advice.Return Object result,
|
||||||
|
@Advice.Thrown Throwable throwable,
|
||||||
|
@Advice.Local("otelContext") Context context,
|
||||||
|
@Advice.Local("otelScope") Scope scope) {
|
||||||
|
if (scope == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.close();
|
||||||
|
ApacheHttpClientHelper.doMethodExit(context, result, throwable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RequestWithHandlerAdvice {
|
||||||
|
|
||||||
|
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||||
|
public static void methodEnter(
|
||||||
|
@Advice.Argument(0) ClassicHttpRequest request,
|
||||||
|
@Advice.Argument(value = 1, readOnly = false) HttpClientResponseHandler<?> handler,
|
||||||
|
@Advice.Local("otelContext") Context context,
|
||||||
|
@Advice.Local("otelScope") Scope scope) {
|
||||||
|
Context parentContext = currentContext();
|
||||||
|
if (!tracer().shouldStartSpan(parentContext)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context = tracer().startSpan(parentContext, request);
|
||||||
|
scope = context.makeCurrent();
|
||||||
|
|
||||||
|
// Wrap the handler so we capture the status code
|
||||||
|
if (handler != null) {
|
||||||
|
handler = new WrappingStatusSettingResponseHandler(context, parentContext, handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||||
|
public static void methodExit(
|
||||||
|
@Advice.Return Object result,
|
||||||
|
@Advice.Thrown Throwable throwable,
|
||||||
|
@Advice.Local("otelContext") Context context,
|
||||||
|
@Advice.Local("otelScope") Scope scope) {
|
||||||
|
if (scope == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.close();
|
||||||
|
ApacheHttpClientHelper.doMethodExit(context, result, throwable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RequestWithContextAndHandlerAdvice {
|
||||||
|
|
||||||
|
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||||
|
public static void methodEnter(
|
||||||
|
@Advice.Argument(0) ClassicHttpRequest request,
|
||||||
|
@Advice.Argument(value = 2, readOnly = false) HttpClientResponseHandler<?> handler,
|
||||||
|
@Advice.Local("otelContext") Context context,
|
||||||
|
@Advice.Local("otelScope") Scope scope) {
|
||||||
|
Context parentContext = currentContext();
|
||||||
|
if (!tracer().shouldStartSpan(parentContext)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context = tracer().startSpan(parentContext, request);
|
||||||
|
scope = context.makeCurrent();
|
||||||
|
|
||||||
|
// Wrap the handler so we capture the status code
|
||||||
|
if (handler != null) {
|
||||||
|
handler = new WrappingStatusSettingResponseHandler(context, parentContext, handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||||
|
public static void methodExit(
|
||||||
|
@Advice.Return Object result,
|
||||||
|
@Advice.Thrown Throwable throwable,
|
||||||
|
@Advice.Local("otelContext") Context context,
|
||||||
|
@Advice.Local("otelScope") Scope scope) {
|
||||||
|
if (scope == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.close();
|
||||||
|
ApacheHttpClientHelper.doMethodExit(context, result, throwable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RequestWithHostAdvice {
|
||||||
|
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||||
|
public static void methodEnter(
|
||||||
|
@Advice.Argument(0) HttpHost host,
|
||||||
|
@Advice.Argument(1) ClassicHttpRequest request,
|
||||||
|
@Advice.Local("otelContext") Context context,
|
||||||
|
@Advice.Local("otelScope") Scope scope) {
|
||||||
|
Context parentContext = currentContext();
|
||||||
|
if (!tracer().shouldStartSpan(parentContext)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context = tracer().startSpan(parentContext, host, request);
|
||||||
|
scope = context.makeCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||||
|
public static void methodExit(
|
||||||
|
@Advice.Return Object result,
|
||||||
|
@Advice.Thrown Throwable throwable,
|
||||||
|
@Advice.Local("otelContext") Context context,
|
||||||
|
@Advice.Local("otelScope") Scope scope) {
|
||||||
|
if (scope == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.close();
|
||||||
|
ApacheHttpClientHelper.doMethodExit(context, result, throwable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RequestWithHostAndHandlerAdvice {
|
||||||
|
|
||||||
|
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||||
|
public static void methodEnter(
|
||||||
|
@Advice.Argument(0) HttpHost host,
|
||||||
|
@Advice.Argument(1) ClassicHttpRequest request,
|
||||||
|
@Advice.Argument(value = 2, readOnly = false) HttpClientResponseHandler<?> handler,
|
||||||
|
@Advice.Local("otelContext") Context context,
|
||||||
|
@Advice.Local("otelScope") Scope scope) {
|
||||||
|
Context parentContext = currentContext();
|
||||||
|
if (!tracer().shouldStartSpan(parentContext)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context = tracer().startSpan(parentContext, host, request);
|
||||||
|
scope = context.makeCurrent();
|
||||||
|
|
||||||
|
// Wrap the handler so we capture the status code
|
||||||
|
if (handler != null) {
|
||||||
|
handler = new WrappingStatusSettingResponseHandler(context, parentContext, handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||||
|
public static void methodExit(
|
||||||
|
@Advice.Return Object result,
|
||||||
|
@Advice.Thrown Throwable throwable,
|
||||||
|
@Advice.Local("otelContext") Context context,
|
||||||
|
@Advice.Local("otelScope") Scope scope) {
|
||||||
|
if (scope == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.close();
|
||||||
|
ApacheHttpClientHelper.doMethodExit(context, result, throwable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RequestWithHostAndContextAndHandlerAdvice {
|
||||||
|
|
||||||
|
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||||
|
public static void methodEnter(
|
||||||
|
@Advice.Argument(0) HttpHost host,
|
||||||
|
@Advice.Argument(1) ClassicHttpRequest request,
|
||||||
|
@Advice.Argument(value = 3, readOnly = false) HttpClientResponseHandler<?> handler,
|
||||||
|
@Advice.Local("otelContext") Context context,
|
||||||
|
@Advice.Local("otelScope") Scope scope) {
|
||||||
|
Context parentContext = currentContext();
|
||||||
|
if (!tracer().shouldStartSpan(parentContext)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context = tracer().startSpan(parentContext, host, request);
|
||||||
|
scope = context.makeCurrent();
|
||||||
|
|
||||||
|
// Wrap the handler so we capture the status code
|
||||||
|
if (handler != null) {
|
||||||
|
handler = new WrappingStatusSettingResponseHandler(context, parentContext, handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||||
|
public static void methodExit(
|
||||||
|
@Advice.Return Object result,
|
||||||
|
@Advice.Thrown Throwable throwable,
|
||||||
|
@Advice.Local("otelContext") Context context,
|
||||||
|
@Advice.Local("otelScope") Scope scope) {
|
||||||
|
if (scope == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.close();
|
||||||
|
ApacheHttpClientHelper.doMethodExit(context, result, throwable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0;
|
||||||
|
|
||||||
|
import io.opentelemetry.context.Context;
|
||||||
|
import io.opentelemetry.context.propagation.TextMapPropagator;
|
||||||
|
import io.opentelemetry.instrumentation.api.tracer.HttpClientTracer;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import org.apache.hc.core5.http.ClassicHttpRequest;
|
||||||
|
import org.apache.hc.core5.http.Header;
|
||||||
|
import org.apache.hc.core5.http.HttpHost;
|
||||||
|
import org.apache.hc.core5.http.HttpMessage;
|
||||||
|
import org.apache.hc.core5.http.HttpResponse;
|
||||||
|
import org.apache.hc.core5.http.HttpVersion;
|
||||||
|
import org.apache.hc.core5.http.ProtocolVersion;
|
||||||
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
|
public class ApacheHttpClientTracer
|
||||||
|
extends HttpClientTracer<ClassicHttpRequest, ClassicHttpRequest, HttpResponse> {
|
||||||
|
|
||||||
|
private static final ApacheHttpClientTracer TRACER = new ApacheHttpClientTracer();
|
||||||
|
|
||||||
|
public static ApacheHttpClientTracer tracer() {
|
||||||
|
return TRACER;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Context startSpan(Context parentContext, HttpHost host, ClassicHttpRequest request) {
|
||||||
|
return startSpan(parentContext, new RequestWithHost(host, request));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Context startSpan(Context parentContext, ClassicHttpRequest request) {
|
||||||
|
return startSpan(parentContext, request, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String method(ClassicHttpRequest httpRequest) {
|
||||||
|
return httpRequest.getMethod();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected @Nullable String flavor(ClassicHttpRequest request) {
|
||||||
|
ProtocolVersion protocolVersion = request.getVersion();
|
||||||
|
if (protocolVersion == null) {
|
||||||
|
protocolVersion = HttpVersion.HTTP_1_1;
|
||||||
|
}
|
||||||
|
return protocolVersion.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected URI url(ClassicHttpRequest request) throws URISyntaxException {
|
||||||
|
return request.getUri();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Integer status(HttpResponse httpResponse) {
|
||||||
|
return httpResponse.getCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String requestHeader(ClassicHttpRequest request, String name) {
|
||||||
|
return header(request, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String responseHeader(HttpResponse response, String name) {
|
||||||
|
return header(response, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected TextMapPropagator.Setter<ClassicHttpRequest> getSetter() {
|
||||||
|
return HttpHeadersInjectAdapter.SETTER;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String header(HttpMessage message, String name) {
|
||||||
|
Header header = message.getFirstHeader(name);
|
||||||
|
return header != null ? header.getValue() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getInstrumentationName() {
|
||||||
|
return "io.opentelemetry.javaagent.apache-httpclient";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0;
|
||||||
|
|
||||||
|
import io.opentelemetry.context.propagation.TextMapPropagator;
|
||||||
|
import org.apache.hc.core5.http.ClassicHttpRequest;
|
||||||
|
|
||||||
|
class HttpHeadersInjectAdapter implements TextMapPropagator.Setter<ClassicHttpRequest> {
|
||||||
|
|
||||||
|
public static final HttpHeadersInjectAdapter SETTER = new HttpHeadersInjectAdapter();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(ClassicHttpRequest carrier, String key, String value) {
|
||||||
|
carrier.addHeader(key, value);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import org.apache.hc.core5.http.ClassicHttpRequest;
|
||||||
|
import org.apache.hc.core5.http.HttpEntity;
|
||||||
|
import org.apache.hc.core5.http.HttpHost;
|
||||||
|
import org.apache.hc.core5.http.message.HttpRequestWrapper;
|
||||||
|
|
||||||
|
public class RequestWithHost extends HttpRequestWrapper implements ClassicHttpRequest {
|
||||||
|
|
||||||
|
private final ClassicHttpRequest httpRequest;
|
||||||
|
private final URI uri;
|
||||||
|
|
||||||
|
public RequestWithHost(HttpHost httpHost, ClassicHttpRequest httpRequest) {
|
||||||
|
super(httpRequest);
|
||||||
|
|
||||||
|
this.httpRequest = httpRequest;
|
||||||
|
|
||||||
|
URI calculatedUri;
|
||||||
|
try {
|
||||||
|
// combine requested uri with host info
|
||||||
|
URI requestUri = httpRequest.getUri();
|
||||||
|
calculatedUri =
|
||||||
|
new URI(
|
||||||
|
httpHost.getSchemeName(),
|
||||||
|
null,
|
||||||
|
httpHost.getHostName(),
|
||||||
|
httpHost.getPort(),
|
||||||
|
requestUri.getPath(),
|
||||||
|
requestUri.getQuery(),
|
||||||
|
requestUri.getFragment());
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
calculatedUri = null;
|
||||||
|
}
|
||||||
|
uri = calculatedUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URI getUri() throws URISyntaxException {
|
||||||
|
if (uri != null) {
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
return super.getUri();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpEntity getEntity() {
|
||||||
|
return httpRequest.getEntity();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEntity(HttpEntity entity) {
|
||||||
|
httpRequest.setEntity(entity);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0;
|
||||||
|
|
||||||
|
import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0.ApacheHttpClientTracer.tracer;
|
||||||
|
|
||||||
|
import io.opentelemetry.context.Context;
|
||||||
|
import io.opentelemetry.context.Scope;
|
||||||
|
import java.io.IOException;
|
||||||
|
import org.apache.hc.core5.http.ClassicHttpResponse;
|
||||||
|
import org.apache.hc.core5.http.HttpException;
|
||||||
|
import org.apache.hc.core5.http.io.HttpClientResponseHandler;
|
||||||
|
|
||||||
|
public class WrappingStatusSettingResponseHandler<T> implements HttpClientResponseHandler<T> {
|
||||||
|
final Context context;
|
||||||
|
final Context parentContext;
|
||||||
|
final HttpClientResponseHandler<T> handler;
|
||||||
|
|
||||||
|
public WrappingStatusSettingResponseHandler(
|
||||||
|
Context context, Context parentContext, HttpClientResponseHandler<T> handler) {
|
||||||
|
this.context = context;
|
||||||
|
this.parentContext = parentContext;
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T handleResponse(ClassicHttpResponse response) throws IOException, HttpException {
|
||||||
|
tracer().end(context, response);
|
||||||
|
// ending the span before executing the callback handler (and scoping the callback handler to
|
||||||
|
// the parent context), even though we are inside of a synchronous http client callback
|
||||||
|
// underneath HttpClient.execute(..), in order to not attribute other CLIENT span timings that
|
||||||
|
// may be performed in the callback handler to the http client span (and so we don't end up with
|
||||||
|
// nested CLIENT spans, which we currently suppress)
|
||||||
|
try (Scope ignored = parentContext.makeCurrent()) {
|
||||||
|
return handler.handleResponse(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import io.opentelemetry.instrumentation.test.AgentTestTrait
|
||||||
|
import io.opentelemetry.instrumentation.test.base.HttpClientTest
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase
|
||||||
|
import org.apache.hc.client5.http.config.RequestConfig
|
||||||
|
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder
|
||||||
|
import org.apache.hc.client5.http.impl.classic.HttpClients
|
||||||
|
import org.apache.hc.core5.http.ClassicHttpRequest
|
||||||
|
import org.apache.hc.core5.http.ClassicHttpResponse
|
||||||
|
import org.apache.hc.core5.http.HttpHost
|
||||||
|
import org.apache.hc.core5.http.HttpRequest
|
||||||
|
import org.apache.hc.core5.http.io.HttpClientResponseHandler
|
||||||
|
import org.apache.hc.core5.http.message.BasicHeader
|
||||||
|
import org.apache.hc.core5.http.protocol.BasicHttpContext
|
||||||
|
import spock.lang.AutoCleanup
|
||||||
|
import spock.lang.Shared
|
||||||
|
|
||||||
|
abstract class ApacheHttpClientResponseHandlerTest<T extends HttpRequest> extends HttpClientTest implements AgentTestTrait {
|
||||||
|
|
||||||
|
@Shared
|
||||||
|
@AutoCleanup
|
||||||
|
def client
|
||||||
|
|
||||||
|
@Shared
|
||||||
|
def handler = new HttpClientResponseHandler<Integer>() {
|
||||||
|
@Override
|
||||||
|
Integer handleResponse(ClassicHttpResponse response) {
|
||||||
|
return response.code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def setupSpec() {
|
||||||
|
HttpClientBuilder builder = HttpClients.custom()
|
||||||
|
builder.setDefaultRequestConfig(RequestConfig.custom()
|
||||||
|
.setConnectTimeout(CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS)
|
||||||
|
.build())
|
||||||
|
|
||||||
|
client = builder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract int executeRequest(T request)
|
||||||
|
|
||||||
|
@Override
|
||||||
|
int doRequest(String method, URI uri, Map<String, String> headers, Closure callback) {
|
||||||
|
def request = new HttpUriRequestBase(method, uri)
|
||||||
|
headers.entrySet().each {
|
||||||
|
request.addHeader(new BasicHeader(it.key, it.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
def status = executeRequest(request)
|
||||||
|
|
||||||
|
// handler execution is included within the client span, so we can't call the callback there.
|
||||||
|
callback?.call()
|
||||||
|
|
||||||
|
return status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ApacheClientHandlerRequest extends ApacheHttpClientResponseHandlerTest<ClassicHttpRequest> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
int executeRequest(ClassicHttpRequest request) {
|
||||||
|
return client.execute(request, handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ApacheClientContextHandlerRequest extends ApacheHttpClientResponseHandlerTest<ClassicHttpRequest> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
int executeRequest(ClassicHttpRequest request) {
|
||||||
|
return client.execute(request, new BasicHttpContext(), handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ApacheClientHostHandlerRequest extends ApacheHttpClientResponseHandlerTest<ClassicHttpRequest> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
int executeRequest(ClassicHttpRequest request) {
|
||||||
|
URI uri = request.getUri()
|
||||||
|
return client.execute(new HttpHost(uri.getScheme(), uri.getHost(), uri.getPort()), request, handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ApacheClientHostAndContextHandlerRequest extends ApacheHttpClientResponseHandlerTest<ClassicHttpRequest> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
int executeRequest(ClassicHttpRequest request) {
|
||||||
|
URI uri = request.getUri()
|
||||||
|
return client.execute(new HttpHost(uri.getScheme(), uri.getHost(), uri.getPort()), request, new BasicHttpContext(), handler)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,216 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import io.opentelemetry.instrumentation.test.AgentTestTrait
|
||||||
|
import io.opentelemetry.instrumentation.test.base.HttpClientTest
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase
|
||||||
|
import org.apache.hc.client5.http.config.RequestConfig
|
||||||
|
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder
|
||||||
|
import org.apache.hc.client5.http.impl.classic.HttpClients
|
||||||
|
import org.apache.hc.core5.http.ClassicHttpRequest
|
||||||
|
import org.apache.hc.core5.http.ClassicHttpResponse
|
||||||
|
import org.apache.hc.core5.http.HttpHost
|
||||||
|
import org.apache.hc.core5.http.HttpRequest
|
||||||
|
import org.apache.hc.core5.http.message.BasicClassicHttpRequest
|
||||||
|
import org.apache.hc.core5.http.message.BasicHeader
|
||||||
|
import org.apache.hc.core5.http.protocol.BasicHttpContext
|
||||||
|
import spock.lang.AutoCleanup
|
||||||
|
import spock.lang.Shared
|
||||||
|
import spock.lang.Timeout
|
||||||
|
|
||||||
|
abstract class ApacheHttpClientTest<T extends HttpRequest> extends HttpClientTest implements AgentTestTrait {
|
||||||
|
@Shared
|
||||||
|
@AutoCleanup
|
||||||
|
def client
|
||||||
|
|
||||||
|
def setupSpec() {
|
||||||
|
HttpClientBuilder builder = HttpClients.custom()
|
||||||
|
builder.setDefaultRequestConfig(RequestConfig.custom()
|
||||||
|
.setConnectTimeout(CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS)
|
||||||
|
.build())
|
||||||
|
|
||||||
|
client = builder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
int doRequest(String method, URI uri, Map<String, String> headers, Closure callback) {
|
||||||
|
def request = createRequest(method, uri)
|
||||||
|
headers.entrySet().each {
|
||||||
|
request.addHeader(new BasicHeader(it.key, it.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
def response = executeRequest(request, uri, callback)
|
||||||
|
response.close() // Make sure the connection is closed.
|
||||||
|
|
||||||
|
return response.code
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract T createRequest(String method, URI uri)
|
||||||
|
|
||||||
|
abstract ClassicHttpResponse executeRequest(T request, URI uri, Closure callback)
|
||||||
|
|
||||||
|
static String fullPathFromURI(URI uri) {
|
||||||
|
StringBuilder builder = new StringBuilder()
|
||||||
|
if (uri.getPath() != null) {
|
||||||
|
builder.append(uri.getPath())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uri.getQuery() != null) {
|
||||||
|
builder.append('?')
|
||||||
|
builder.append(uri.getQuery())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uri.getFragment() != null) {
|
||||||
|
builder.append('#')
|
||||||
|
builder.append(uri.getFragment())
|
||||||
|
}
|
||||||
|
return builder.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Timeout(5)
|
||||||
|
class ApacheClientHostRequest extends ApacheHttpClientTest<ClassicHttpRequest> {
|
||||||
|
@Override
|
||||||
|
ClassicHttpRequest createRequest(String method, URI uri) {
|
||||||
|
return new BasicClassicHttpRequest(method, fullPathFromURI(uri))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ClassicHttpResponse executeRequest(ClassicHttpRequest request, URI uri, Closure callback) {
|
||||||
|
def response = client.execute(new HttpHost(uri.getHost(), uri.getPort()), request)
|
||||||
|
callback?.call()
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean testRemoteConnection() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Timeout(5)
|
||||||
|
class ApacheClientHostRequestContext extends ApacheHttpClientTest<ClassicHttpRequest> {
|
||||||
|
@Override
|
||||||
|
ClassicHttpRequest createRequest(String method, URI uri) {
|
||||||
|
return new BasicClassicHttpRequest(method, fullPathFromURI(uri))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ClassicHttpResponse executeRequest(ClassicHttpRequest request, URI uri, Closure callback) {
|
||||||
|
def response = client.execute(new HttpHost(uri.getHost(), uri.getPort()), request, new BasicHttpContext())
|
||||||
|
callback?.call()
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean testRemoteConnection() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Timeout(5)
|
||||||
|
class ApacheClientHostRequestResponseHandler extends ApacheHttpClientTest<ClassicHttpRequest> {
|
||||||
|
@Override
|
||||||
|
ClassicHttpRequest createRequest(String method, URI uri) {
|
||||||
|
return new BasicClassicHttpRequest(method, fullPathFromURI(uri))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ClassicHttpResponse executeRequest(ClassicHttpRequest request, URI uri, Closure callback) {
|
||||||
|
return client.execute(new HttpHost(uri.getHost(), uri.getPort()), request, {
|
||||||
|
callback?.call()
|
||||||
|
return it
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean testRemoteConnection() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Timeout(5)
|
||||||
|
class ApacheClientHostRequestResponseHandlerContext extends ApacheHttpClientTest<ClassicHttpRequest> {
|
||||||
|
@Override
|
||||||
|
ClassicHttpRequest createRequest(String method, URI uri) {
|
||||||
|
return new BasicClassicHttpRequest(method, fullPathFromURI(uri))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ClassicHttpResponse executeRequest(ClassicHttpRequest request, URI uri, Closure callback) {
|
||||||
|
return client.execute(new HttpHost(uri.getHost(), uri.getPort()), request, new BasicHttpContext(), {
|
||||||
|
callback?.call()
|
||||||
|
return it
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean testRemoteConnection() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Timeout(5)
|
||||||
|
class ApacheClientUriRequest extends ApacheHttpClientTest<ClassicHttpRequest> {
|
||||||
|
@Override
|
||||||
|
ClassicHttpRequest createRequest(String method, URI uri) {
|
||||||
|
return new HttpUriRequestBase(method, uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ClassicHttpResponse executeRequest(ClassicHttpRequest request, URI uri, Closure callback) {
|
||||||
|
def response = client.execute(request)
|
||||||
|
callback?.call()
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Timeout(5)
|
||||||
|
class ApacheClientUriRequestContext extends ApacheHttpClientTest<ClassicHttpRequest> {
|
||||||
|
@Override
|
||||||
|
ClassicHttpRequest createRequest(String method, URI uri) {
|
||||||
|
return new HttpUriRequestBase(method, uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ClassicHttpResponse executeRequest(ClassicHttpRequest request, URI uri, Closure callback) {
|
||||||
|
def response = client.execute(request, new BasicHttpContext())
|
||||||
|
callback?.call()
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Timeout(5)
|
||||||
|
class ApacheClientUriRequestResponseHandler extends ApacheHttpClientTest<ClassicHttpRequest> {
|
||||||
|
@Override
|
||||||
|
ClassicHttpRequest createRequest(String method, URI uri) {
|
||||||
|
return new HttpUriRequestBase(method, uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ClassicHttpResponse executeRequest(ClassicHttpRequest request, URI uri, Closure callback) {
|
||||||
|
return client.execute(request, {
|
||||||
|
callback?.call()
|
||||||
|
it
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Timeout(5)
|
||||||
|
class ApacheClientUriRequestResponseHandlerContext extends ApacheHttpClientTest<ClassicHttpRequest> {
|
||||||
|
@Override
|
||||||
|
ClassicHttpRequest createRequest(String method, URI uri) {
|
||||||
|
return new HttpUriRequestBase(method, uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ClassicHttpResponse executeRequest(ClassicHttpRequest request, URI uri, Closure callback) {
|
||||||
|
return client.execute(request, {
|
||||||
|
callback?.call()
|
||||||
|
it
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -60,6 +60,7 @@ include ':instrumentation:apache-camel-2.20:javaagent-unittests'
|
||||||
include ':instrumentation:apache-httpasyncclient-4.1:javaagent'
|
include ':instrumentation:apache-httpasyncclient-4.1:javaagent'
|
||||||
include ':instrumentation:apache-httpclient:apache-httpclient-2.0:javaagent'
|
include ':instrumentation:apache-httpclient:apache-httpclient-2.0:javaagent'
|
||||||
include ':instrumentation:apache-httpclient:apache-httpclient-4.0:javaagent'
|
include ':instrumentation:apache-httpclient:apache-httpclient-4.0:javaagent'
|
||||||
|
include ':instrumentation:apache-httpclient:apache-httpclient-5.0:javaagent'
|
||||||
include ':instrumentation:armeria-1.3:javaagent'
|
include ':instrumentation:armeria-1.3:javaagent'
|
||||||
include ':instrumentation:armeria-1.3:library'
|
include ':instrumentation:armeria-1.3:library'
|
||||||
include ':instrumentation:armeria-1.3:testing'
|
include ':instrumentation:armeria-1.3:testing'
|
||||||
|
|
Loading…
Reference in New Issue