Skip to content

Commit

Permalink
added apachehttpclient5 sync instrumentation
Browse files Browse the repository at this point in the history
  • Loading branch information
videnkz committed Nov 8, 2023
1 parent 3741d69 commit f1a06f4
Show file tree
Hide file tree
Showing 12 changed files with 283 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public static <RequestObject extends HttpRequest, HttpRequest, HttpHost, Closeab
return;
}
try {
if (response != null && adapter.getStatusLine(response) != null) {
if (response != null && adapter.isNotNullStatusLine(response)) {
int statusCode = adapter.getResponseCode(response);
span.getContext().getHttp().withStatusCode(statusCode);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ public interface ApacheHttpClientApiAdapter<RequestObject extends HttpRequest, H

boolean isCircularRedirectException(Throwable t);

StatusLine getStatusLine(CloseableResponse response);
boolean isNotNullStatusLine(CloseableResponse response);
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public boolean isCircularRedirectException(Throwable t) {
}

@Override
public StatusLine getStatusLine(CloseableHttpResponse o) {
return o.getStatusLine();
public boolean isNotNullStatusLine(CloseableHttpResponse o) {
return null != o.getStatusLine();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>co.elastic.apm</groupId>
<artifactId>apm-apache-httpclient</artifactId>
<version>1.43.1-SNAPSHOT</version>
</parent>

<artifactId>apm-apache-httpclient5-plugin</artifactId>
<name>${project.groupId}:${project.artifactId}</name>

<properties>
<apm-agent-parent.base.dir>${project.basedir}/../../..</apm-agent-parent.base.dir>
<httpclient.version>5.2.1</httpclient.version>
</properties>

<dependencies>
<!--
this dependency duplicates the transitive one we get from 'httpasyncclient'
but keeping it explicit avoids relying on transitive dependency
-->
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>apm-apache-httpclient-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>${httpclient.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5-fluent</artifactId>
<version>${httpclient.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>


</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package co.elastic.apm.agent.httpclient.v5;


import co.elastic.apm.agent.httpclient.common.AbstractApacheHttpClientAdvice;
import co.elastic.apm.agent.httpclient.v5.helper.ApacheHttpClient5ApiAdapter;
import co.elastic.apm.agent.httpclient.v5.helper.RequestHeaderAccessor;
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.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.protocol.HttpContext;

import javax.annotation.Nullable;
import java.net.URISyntaxException;

import static net.bytebuddy.matcher.ElementMatchers.hasSuperType;
import static net.bytebuddy.matcher.ElementMatchers.nameContains;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.returns;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;

public class ApacheHttpClient5Instrumentation extends BaseApacheHttpClient5Instrumentation {

public static class HttpClient5Advice extends AbstractApacheHttpClientAdvice {
private static final ApacheHttpClient5ApiAdapter adapter = ApacheHttpClient5ApiAdapter.get();

@Nullable
@Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
public static Object onBeforeExecute(@Advice.Argument(0) HttpHost httpHost,
@Advice.Argument(1) ClassicHttpRequest request,
@Advice.Argument(2) HttpContext context) throws URISyntaxException {
return startSpan(tracer, adapter, request, httpHost, RequestHeaderAccessor.INSTANCE);
}

@Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false)
public static void onAfterExecute(@Advice.Return @Nullable CloseableHttpResponse response,
@Advice.Enter @Nullable Object spanObj,
@Advice.Thrown @Nullable Throwable t) {
endSpan(adapter, spanObj, t, response);
}
}

@Override
public String getAdviceClassName() {
return "co.elastic.apm.agent.httpclient.v5.ApacheHttpClient5Instrumentation$HttpClient5Advice";
}

@Override
public ElementMatcher<? super TypeDescription> getTypeMatcher() {
return hasSuperType(named("org.apache.hc.client5.http.impl.classic.CloseableHttpClient"));
}

@Override
public ElementMatcher<? super NamedElement> getTypeMatcherPreFilter() {
return nameContains("HttpClient");
}

@Override
public ElementMatcher<? super MethodDescription> getMethodMatcher() {
return named("doExecute")
.and(takesArguments(3))
.and(returns(hasSuperType(named("org.apache.hc.client5.http.impl.classic.CloseableHttpResponse"))))
.and(takesArgument(0, hasSuperType(named("org.apache.hc.core5.http.HttpHost"))))
.and(takesArgument(1, hasSuperType(named("org.apache.hc.core5.http.ClassicHttpRequest"))))
.and(takesArgument(2, hasSuperType(named("org.apache.hc.core5.http.protocol.HttpContext"))));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package co.elastic.apm.agent.httpclient.v5;

import co.elastic.apm.agent.sdk.ElasticApmInstrumentation;
import co.elastic.apm.agent.tracer.GlobalTracer;
import co.elastic.apm.agent.tracer.Tracer;

import java.util.Arrays;
import java.util.Collection;

public abstract class BaseApacheHttpClient5Instrumentation extends ElasticApmInstrumentation {

static final Tracer tracer = GlobalTracer.get();

@Override
public Collection<String> getInstrumentationGroupNames() {
return Arrays.asList("http-client", "apache-httpclient");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package co.elastic.apm.agent.httpclient.v5.helper;

public class ApacheHttpAsyncClientHelper {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package co.elastic.apm.agent.httpclient.v5.helper;

import co.elastic.apm.agent.httpclient.common.ApacheHttpClientApiAdapter;
import org.apache.hc.client5.http.CircularRedirectException;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
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.StatusLine;

import java.net.URI;
import java.net.URISyntaxException;

public class ApacheHttpClient5ApiAdapter implements ApacheHttpClientApiAdapter<ClassicHttpRequest, HttpRequest, HttpHost, CloseableHttpResponse, StatusLine> {
private static final ApacheHttpClient5ApiAdapter INSTANCE = new ApacheHttpClient5ApiAdapter();

private ApacheHttpClient5ApiAdapter() {
}

public static ApacheHttpClient5ApiAdapter get() {
return INSTANCE;
}

@Override
public String getMethod(ClassicHttpRequest request) {
return request.getMethod();
}

@Override
public URI getUri(ClassicHttpRequest request) throws URISyntaxException {
return request.getUri();
}

@Override
public CharSequence getHostName(HttpHost httpHost) {
return httpHost.getHostName();
}

@Override
public int getResponseCode(CloseableHttpResponse closeableHttpResponse) {
return closeableHttpResponse.getCode();
}

@Override
public boolean isCircularRedirectException(Throwable t) {
return t instanceof CircularRedirectException;
}

@Override
public boolean isNotNullStatusLine(CloseableHttpResponse closeableHttpResponse) {
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package co.elastic.apm.agent.httpclient.v5.helper;

import co.elastic.apm.agent.tracer.dispatch.TextHeaderGetter;
import co.elastic.apm.agent.tracer.dispatch.TextHeaderSetter;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpRequest;

import javax.annotation.Nullable;

public class RequestHeaderAccessor implements TextHeaderGetter<HttpRequest>, TextHeaderSetter<HttpRequest> {

public static final RequestHeaderAccessor INSTANCE = new RequestHeaderAccessor();

@Nullable
@Override
public String getFirstHeader(String headerName, HttpRequest request) {
Header header = request.getFirstHeader(headerName);
if (header == null) {
return null;
}
return header.getValue();
}

@Override
public <S> void forEach(String headerName, HttpRequest carrier, S state, HeaderConsumer<String, S> consumer) {
Header[] headers = carrier.getHeaders(headerName);
if (headers == null) {
return;
}
for (Header header : headers) {
consumer.accept(header.getValue(), state);
}
}

@Override
public void setHeader(String headerName, String headerValue, HttpRequest request) {
request.setHeader(headerName, headerValue);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
co.elastic.apm.agent.httpclient.v5.ApacheHttpClient5Instrumentation
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package co.elastic.apm.agent.httpclient.v5;


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.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
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.junit.AfterClass;
import org.junit.BeforeClass;

import java.io.IOException;

public class ApacheHttpClientInstrumentationTest extends AbstractHttpClientInstrumentationTest {

private static CloseableHttpClient client;

@BeforeClass
public static void setUp() {
client = HttpClients.createDefault();
}

@AfterClass
public static void close() throws IOException {
client.close();
}

@Override
protected void performGet(String path) throws Exception {
HttpClientResponseHandler<String> 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);
}
}
1 change: 1 addition & 0 deletions apm-agent-plugins/apm-apache-httpclient/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<modules>
<module>apm-apache-httpclient3-plugin</module>
<module>apm-apache-httpclient4-plugin</module>
<module>apm-apache-httpclient5-plugin</module>
</modules>

<dependencies>
Expand Down

0 comments on commit f1a06f4

Please sign in to comment.