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:
|
||||
include ':instrumentation:akka-http-10.0'
|
||||
include ':instrumentation:apache-httpasyncclient-4.0'
|
||||
include ':instrumentation:apache-httpclient-3.0'
|
||||
include ':instrumentation:apache-httpclient-4.0'
|
||||
include ':instrumentation:aws-java-sdk-1.11'
|
||||
include ':instrumentation:aws-java-sdk-2.2'
|
||||
|
|
Loading…
Reference in New Issue