Add Apache HTTP Client 3 instrumentation (#190)
This commit is contained in:
parent
f4011b63e3
commit
d60f18f8a1
|
@ -0,0 +1,25 @@
|
||||||
|
muzzle {
|
||||||
|
pass {
|
||||||
|
group = "commons-httpclient"
|
||||||
|
module = "commons-httpclient"
|
||||||
|
versions = "[3.0,4.0)"
|
||||||
|
assertInverse = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "${rootDir}/gradle/java.gradle"
|
||||||
|
apply plugin: 'org.unbroken-dome.test-sets'
|
||||||
|
|
||||||
|
testSets {
|
||||||
|
latestDepTest {
|
||||||
|
dirName = 'test'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly group: 'commons-httpclient', name: 'commons-httpclient', version: '3.0'
|
||||||
|
|
||||||
|
testCompile group: 'commons-httpclient', name: 'commons-httpclient', version: '3.0'
|
||||||
|
|
||||||
|
latestDepTestCompile group: 'commons-httpclient', name: 'commons-httpclient', version: '+'
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
package io.opentelemetry.auto.instrumentation.apachehttpclient.v3_0;
|
||||||
|
|
||||||
|
import io.opentelemetry.OpenTelemetry;
|
||||||
|
import io.opentelemetry.auto.decorator.HttpClientDecorator;
|
||||||
|
import io.opentelemetry.trace.Tracer;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import org.apache.commons.httpclient.HttpMethod;
|
||||||
|
import org.apache.commons.httpclient.StatusLine;
|
||||||
|
import org.apache.commons.httpclient.URIException;
|
||||||
|
|
||||||
|
public class ApacheHttpClientDecorator extends HttpClientDecorator<HttpMethod, HttpMethod> {
|
||||||
|
public static final ApacheHttpClientDecorator DECORATE = new ApacheHttpClientDecorator();
|
||||||
|
|
||||||
|
public static final Tracer TRACER =
|
||||||
|
OpenTelemetry.getTracerFactory().get("io.opentelemetry.auto.apache-httpclient-3.0");
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getComponentName() {
|
||||||
|
return "apache-httpclient";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String method(final HttpMethod httpMethod) {
|
||||||
|
return httpMethod.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected URI url(final HttpMethod httpMethod) throws URISyntaxException {
|
||||||
|
final org.apache.commons.httpclient.URI uri;
|
||||||
|
try {
|
||||||
|
uri = httpMethod.getURI();
|
||||||
|
} catch (final URIException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new URI(uri.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String hostname(final HttpMethod httpMethod) {
|
||||||
|
try {
|
||||||
|
return httpMethod.getURI().getHost();
|
||||||
|
} catch (final URIException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Integer port(final HttpMethod httpMethod) {
|
||||||
|
try {
|
||||||
|
return httpMethod.getURI().getPort();
|
||||||
|
} catch (final URIException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Integer status(final HttpMethod httpMethod) {
|
||||||
|
final StatusLine statusLine = httpMethod.getStatusLine();
|
||||||
|
return statusLine == null ? null : statusLine.getStatusCode();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,110 @@
|
||||||
|
package io.opentelemetry.auto.instrumentation.apachehttpclient.v3_0;
|
||||||
|
|
||||||
|
import static io.opentelemetry.auto.instrumentation.apachehttpclient.v3_0.ApacheHttpClientDecorator.DECORATE;
|
||||||
|
import static io.opentelemetry.auto.instrumentation.apachehttpclient.v3_0.ApacheHttpClientDecorator.TRACER;
|
||||||
|
import static io.opentelemetry.auto.instrumentation.apachehttpclient.v3_0.HttpHeadersInjectAdapter.SETTER;
|
||||||
|
import static io.opentelemetry.auto.tooling.ByteBuddyElementMatchers.safeHasSuperType;
|
||||||
|
import static io.opentelemetry.trace.Span.Kind.CLIENT;
|
||||||
|
import static java.util.Collections.singletonMap;
|
||||||
|
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.auto.bootstrap.CallDepthThreadLocalMap;
|
||||||
|
import io.opentelemetry.auto.instrumentation.api.SpanWithScope;
|
||||||
|
import io.opentelemetry.auto.tooling.Instrumenter;
|
||||||
|
import io.opentelemetry.context.Scope;
|
||||||
|
import io.opentelemetry.trace.Span;
|
||||||
|
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.commons.httpclient.HttpClient;
|
||||||
|
import org.apache.commons.httpclient.HttpMethod;
|
||||||
|
|
||||||
|
@AutoService(Instrumenter.class)
|
||||||
|
public class ApacheHttpClientInstrumentation extends Instrumenter.Default {
|
||||||
|
|
||||||
|
public ApacheHttpClientInstrumentation() {
|
||||||
|
super("httpclient", "apache-httpclient", "apache-http-client");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ElementMatcher<TypeDescription> typeMatcher() {
|
||||||
|
return safeHasSuperType(named("org.apache.commons.httpclient.HttpClient"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] helperClassNames() {
|
||||||
|
return new String[] {
|
||||||
|
packageName + ".HttpHeadersInjectAdapter",
|
||||||
|
"io.opentelemetry.auto.decorator.BaseDecorator",
|
||||||
|
"io.opentelemetry.auto.decorator.ClientDecorator",
|
||||||
|
"io.opentelemetry.auto.decorator.HttpClientDecorator",
|
||||||
|
packageName + ".ApacheHttpClientDecorator"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
|
||||||
|
return singletonMap(
|
||||||
|
isMethod()
|
||||||
|
.and(named("executeMethod"))
|
||||||
|
.and(not(isAbstract()))
|
||||||
|
.and(takesArguments(3))
|
||||||
|
.and(takesArgument(0, named("org.apache.commons.httpclient.HostConfiguration")))
|
||||||
|
.and(takesArgument(1, named("org.apache.commons.httpclient.HttpMethod")))
|
||||||
|
.and(takesArgument(2, named("org.apache.commons.httpclient.HttpState"))),
|
||||||
|
ApacheHttpClientInstrumentation.class.getName() + "$ExecuteAdvice");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ExecuteAdvice {
|
||||||
|
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||||
|
public static SpanWithScope methodEnter(@Advice.Argument(1) final HttpMethod httpMethod) {
|
||||||
|
final int callDepth = CallDepthThreadLocalMap.incrementCallDepth(HttpClient.class);
|
||||||
|
if (callDepth > 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final Span span = TRACER.spanBuilder("http.request").setSpanKind(CLIENT).startSpan();
|
||||||
|
final Scope scope = TRACER.withSpan(span);
|
||||||
|
|
||||||
|
DECORATE.afterStart(span);
|
||||||
|
DECORATE.onRequest(span, httpMethod);
|
||||||
|
|
||||||
|
final boolean awsClientCall =
|
||||||
|
httpMethod.getRequestHeaders("amz-sdk-invocation-id").length > 0;
|
||||||
|
// AWS calls are often signed, so we can't add headers without breaking the signature.
|
||||||
|
if (!awsClientCall) {
|
||||||
|
TRACER.getHttpTextFormat().inject(span.getContext(), httpMethod, SETTER);
|
||||||
|
}
|
||||||
|
return new SpanWithScope(span, scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||||
|
public static void methodExit(
|
||||||
|
@Advice.Enter final SpanWithScope spanWithScope,
|
||||||
|
@Advice.Argument(1) final HttpMethod httpMethod,
|
||||||
|
@Advice.Thrown final Throwable throwable) {
|
||||||
|
|
||||||
|
if (spanWithScope == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CallDepthThreadLocalMap.reset(HttpClient.class);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final Span span = spanWithScope.getSpan();
|
||||||
|
DECORATE.onResponse(span, httpMethod);
|
||||||
|
DECORATE.onError(span, throwable);
|
||||||
|
DECORATE.beforeFinish(span);
|
||||||
|
span.end();
|
||||||
|
} finally {
|
||||||
|
spanWithScope.closeScope();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package io.opentelemetry.auto.instrumentation.apachehttpclient.v3_0;
|
||||||
|
|
||||||
|
import io.opentelemetry.context.propagation.HttpTextFormat;
|
||||||
|
import org.apache.commons.httpclient.HttpMethod;
|
||||||
|
|
||||||
|
public class HttpHeadersInjectAdapter implements HttpTextFormat.Setter<HttpMethod> {
|
||||||
|
|
||||||
|
public static final HttpHeadersInjectAdapter SETTER = new HttpHeadersInjectAdapter();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void put(final HttpMethod carrier, final String key, final String value) {
|
||||||
|
carrier.addRequestHeader(key, value);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,112 @@
|
||||||
|
import io.opentelemetry.auto.instrumentation.apachehttpclient.v3_0.ApacheHttpClientDecorator
|
||||||
|
import io.opentelemetry.auto.test.base.HttpClientTest
|
||||||
|
import org.apache.commons.httpclient.HostConfiguration
|
||||||
|
import org.apache.commons.httpclient.HttpClient
|
||||||
|
import org.apache.commons.httpclient.HttpMethod
|
||||||
|
import org.apache.commons.httpclient.HttpState
|
||||||
|
import org.apache.commons.httpclient.methods.GetMethod
|
||||||
|
import org.apache.commons.httpclient.methods.HeadMethod
|
||||||
|
import org.apache.commons.httpclient.methods.PostMethod
|
||||||
|
import org.apache.commons.httpclient.methods.PutMethod
|
||||||
|
import spock.lang.Shared
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutionException
|
||||||
|
|
||||||
|
abstract class ApacheHttpClientTest extends HttpClientTest<ApacheHttpClientDecorator> {
|
||||||
|
@Shared
|
||||||
|
def client = new HttpClient()
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ApacheHttpClientDecorator decorator() {
|
||||||
|
return ApacheHttpClientDecorator.DECORATE
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
int doRequest(String method, URI uri, Map<String, String> headers, Closure callback) {
|
||||||
|
def httpMethod
|
||||||
|
switch (method) {
|
||||||
|
case "GET":
|
||||||
|
httpMethod = new GetMethod(uri.toString())
|
||||||
|
break
|
||||||
|
case "POST":
|
||||||
|
httpMethod = new PostMethod(uri.toString())
|
||||||
|
break
|
||||||
|
case "PUT":
|
||||||
|
httpMethod = new PutMethod(uri.toString())
|
||||||
|
break
|
||||||
|
case "HEAD":
|
||||||
|
httpMethod = new HeadMethod(uri.toString())
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("Unexpected http method: " + method)
|
||||||
|
}
|
||||||
|
|
||||||
|
headers.entrySet().each {
|
||||||
|
httpMethod.addRequestHeader(it.key, it.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
def statusCode = executeRequest(httpMethod, uri)
|
||||||
|
callback?.call()
|
||||||
|
httpMethod.releaseConnection()
|
||||||
|
|
||||||
|
return statusCode
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract int executeRequest(HttpMethod request, URI uri)
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean testCircularRedirects() {
|
||||||
|
// only creates 1 server request instead of 2 server requests before throwing exception like others
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Integer statusOnRedirectError() {
|
||||||
|
return 302
|
||||||
|
}
|
||||||
|
|
||||||
|
def "basic #method request with circular redirects"() {
|
||||||
|
given:
|
||||||
|
def uri = server.address.resolve("/circular-redirect")
|
||||||
|
|
||||||
|
when:
|
||||||
|
doRequest(method, uri)
|
||||||
|
|
||||||
|
then:
|
||||||
|
def ex = thrown(Exception)
|
||||||
|
def thrownException = ex instanceof ExecutionException ? ex.cause : ex
|
||||||
|
|
||||||
|
and:
|
||||||
|
assertTraces(1) {
|
||||||
|
trace(0, 2) {
|
||||||
|
clientSpan(it, 0, null, method, false, false, uri, statusOnRedirectError(), thrownException)
|
||||||
|
serverSpan(it, 1, span(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
where:
|
||||||
|
method = "GET"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ApacheClientHttpMethod extends ApacheHttpClientTest {
|
||||||
|
@Override
|
||||||
|
int executeRequest(HttpMethod httpMethod, URI uri) {
|
||||||
|
client.executeMethod(httpMethod)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ApacheClientHostConfiguration extends ApacheHttpClientTest {
|
||||||
|
@Override
|
||||||
|
int executeRequest(HttpMethod httpMethod, URI uri) {
|
||||||
|
client.executeMethod(new HostConfiguration(), httpMethod)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ApacheClientHttpState extends ApacheHttpClientTest {
|
||||||
|
@Override
|
||||||
|
int executeRequest(HttpMethod httpMethod, URI uri) {
|
||||||
|
client.executeMethod(new HostConfiguration(), httpMethod, new HttpState())
|
||||||
|
}
|
||||||
|
}
|
|
@ -46,6 +46,7 @@ include ':smoke-tests:springboot'
|
||||||
// instrumentation:
|
// instrumentation:
|
||||||
include ':instrumentation:akka-http-10.0'
|
include ':instrumentation:akka-http-10.0'
|
||||||
include ':instrumentation:apache-httpasyncclient-4.0'
|
include ':instrumentation:apache-httpasyncclient-4.0'
|
||||||
|
include ':instrumentation:apache-httpclient-3.0'
|
||||||
include ':instrumentation:apache-httpclient-4.0'
|
include ':instrumentation:apache-httpclient-4.0'
|
||||||
include ':instrumentation:aws-java-sdk-1.11'
|
include ':instrumentation:aws-java-sdk-1.11'
|
||||||
include ':instrumentation:aws-java-sdk-2.2'
|
include ':instrumentation:aws-java-sdk-2.2'
|
||||||
|
|
Loading…
Reference in New Issue