Skip to content

Commit 8a8da27

Browse files
committed
feat: instrumentation for apache httpclient 5
1 parent 1bfb633 commit 8a8da27

File tree

12 files changed

+1075
-0
lines changed

12 files changed

+1075
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
plugins {
2+
id("otel.library-instrumentation")
3+
id("otel.nullaway-conventions")
4+
id("otel.animalsniffer-conventions")
5+
}
6+
7+
dependencies {
8+
library("org.apache.httpcomponents.client5:httpclient5:5.2.1")
9+
library("jakarta.annotation:jakarta.annotation-api:2.1.1")
10+
11+
testImplementation(project(":instrumentation:apache-httpclient:apache-httpclient-5.0:testing"))
12+
13+
latestDepTestLibrary("org.apache.httpcomponents.client5:httpclient5:5+") // see apache-httpclient-5.0 module
14+
}
15+
16+
tasks {
17+
val testStableSemconv by registering(Test::class) {
18+
jvmArgs("-Dotel.semconv-stability.opt-in=http")
19+
}
20+
21+
check {
22+
dependsOn(testStableSemconv)
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.apachehttpclient.v5_0;
7+
8+
import io.opentelemetry.instrumentation.api.semconv.http.HttpClientAttributesGetter;
9+
import jakarta.annotation.Nullable;
10+
import java.util.List;
11+
import org.apache.hc.core5.http.HttpResponse;
12+
13+
enum ApacheHttpClient5HttpAttributesGetter
14+
implements HttpClientAttributesGetter<ApacheHttpClient5Request, HttpResponse> {
15+
INSTANCE;
16+
17+
@Override
18+
public String getHttpRequestMethod(ApacheHttpClient5Request request) {
19+
return request.getMethod();
20+
}
21+
22+
@Override
23+
@Nullable
24+
public String getUrlFull(ApacheHttpClient5Request request) {
25+
return request.getUrl();
26+
}
27+
28+
@Override
29+
public List<String> getHttpRequestHeader(ApacheHttpClient5Request request, String name) {
30+
return request.getHeader(name);
31+
}
32+
33+
@Override
34+
public Integer getHttpResponseStatusCode(
35+
ApacheHttpClient5Request request, HttpResponse response, @Nullable Throwable error) {
36+
return response.getCode();
37+
}
38+
39+
@Override
40+
public List<String> getHttpResponseHeader(
41+
ApacheHttpClient5Request request, HttpResponse response, String name) {
42+
return ApacheHttpClient5Request.headersToList(response.getHeaders(name));
43+
}
44+
45+
@Override
46+
public String getNetworkProtocolName(
47+
ApacheHttpClient5Request request, @Nullable HttpResponse response) {
48+
return request.getProtocolName();
49+
}
50+
51+
@Override
52+
public String getNetworkProtocolVersion(
53+
ApacheHttpClient5Request request, @Nullable HttpResponse response) {
54+
return request.getProtocolVersion();
55+
}
56+
57+
@Override
58+
@Nullable
59+
public String getServerAddress(ApacheHttpClient5Request request) {
60+
return request.getServerAddress();
61+
}
62+
63+
@Override
64+
@Nullable
65+
public Integer getServerPort(ApacheHttpClient5Request request) {
66+
return request.getServerPort();
67+
}
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.apachehttpclient.v5_0;
7+
8+
import static java.util.logging.Level.FINE;
9+
10+
import jakarta.annotation.Nullable;
11+
import java.net.InetAddress;
12+
import java.net.InetSocketAddress;
13+
import java.net.URI;
14+
import java.net.URISyntaxException;
15+
import java.util.ArrayList;
16+
import java.util.Collections;
17+
import java.util.List;
18+
import java.util.logging.Logger;
19+
import org.apache.hc.core5.http.Header;
20+
import org.apache.hc.core5.http.HttpHost;
21+
import org.apache.hc.core5.http.HttpRequest;
22+
import org.apache.hc.core5.http.ProtocolVersion;
23+
24+
public final class ApacheHttpClient5Request {
25+
26+
private static final Logger logger = Logger.getLogger(ApacheHttpClient5Request.class.getName());
27+
28+
@Nullable private final URI uri;
29+
30+
private final HttpRequest delegate;
31+
@Nullable private final HttpHost target;
32+
33+
ApacheHttpClient5Request(@Nullable HttpHost httpHost, HttpRequest httpRequest) {
34+
URI calculatedUri = getUri(httpRequest);
35+
if (calculatedUri != null && httpHost != null) {
36+
uri = getCalculatedUri(httpHost, calculatedUri);
37+
} else {
38+
uri = calculatedUri;
39+
}
40+
delegate = httpRequest;
41+
target = httpHost;
42+
}
43+
44+
/** Returns the actual {@link HttpRequest} being executed by the client. */
45+
public HttpRequest getDelegate() {
46+
return delegate;
47+
}
48+
49+
List<String> getHeader(String name) {
50+
return headersToList(delegate.getHeaders(name));
51+
}
52+
53+
// minimize memory overhead by not using streams
54+
static List<String> headersToList(Header[] headers) {
55+
if (headers.length == 0) {
56+
return Collections.emptyList();
57+
}
58+
List<String> headersList = new ArrayList<>(headers.length);
59+
for (Header header : headers) {
60+
headersList.add(header.getValue());
61+
}
62+
return headersList;
63+
}
64+
65+
void setHeader(String name, String value) {
66+
delegate.setHeader(name, value);
67+
}
68+
69+
String getMethod() {
70+
return delegate.getMethod();
71+
}
72+
73+
@Nullable
74+
String getUrl() {
75+
return uri != null ? uri.toString() : null;
76+
}
77+
78+
String getProtocolName() {
79+
return delegate.getVersion().getProtocol();
80+
}
81+
82+
String getProtocolVersion() {
83+
ProtocolVersion protocolVersion = delegate.getVersion();
84+
if (protocolVersion.getMinor() == 0) {
85+
return Integer.toString(protocolVersion.getMajor());
86+
}
87+
return protocolVersion.getMajor() + "." + protocolVersion.getMinor();
88+
}
89+
90+
@Nullable
91+
public String getServerAddress() {
92+
return uri == null ? null : uri.getHost();
93+
}
94+
95+
@Nullable
96+
public Integer getServerPort() {
97+
return uri == null ? null : uri.getPort();
98+
}
99+
100+
@Nullable
101+
private static URI getUri(HttpRequest httpRequest) {
102+
try {
103+
// this can be relative or absolute
104+
return new URI(httpRequest.getUri().toString());
105+
} catch (URISyntaxException e) {
106+
logger.log(FINE, e.getMessage(), e);
107+
return null;
108+
}
109+
}
110+
111+
@Nullable
112+
private static URI getCalculatedUri(HttpHost httpHost, URI uri) {
113+
try {
114+
return new URI(
115+
httpHost.getSchemeName(),
116+
uri.getUserInfo(),
117+
httpHost.getHostName(),
118+
httpHost.getPort(),
119+
uri.getPath(),
120+
uri.getQuery(),
121+
uri.getFragment());
122+
} catch (URISyntaxException e) {
123+
logger.log(FINE, e.getMessage(), e);
124+
return null;
125+
}
126+
}
127+
128+
@Nullable
129+
public InetSocketAddress getServerSocketAddress() {
130+
if (target == null) {
131+
return null;
132+
}
133+
InetAddress inetAddress = target.getAddress();
134+
return inetAddress == null ? null : new InetSocketAddress(inetAddress, target.getPort());
135+
}
136+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.apachehttpclient.v5_0;
7+
8+
import io.opentelemetry.api.OpenTelemetry;
9+
import io.opentelemetry.context.propagation.ContextPropagators;
10+
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
11+
import org.apache.hc.client5.http.impl.ChainElement;
12+
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
13+
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
14+
import org.apache.hc.core5.http.HttpResponse;
15+
16+
/** Entrypoint for instrumenting Apache HTTP Client. */
17+
public final class ApacheHttpClient5Telemetry {
18+
19+
/**
20+
* Returns a new {@link ApacheHttpClient5Telemetry} configured with the given {@link
21+
* OpenTelemetry}.
22+
*/
23+
public static ApacheHttpClient5Telemetry create(OpenTelemetry openTelemetry) {
24+
return builder(openTelemetry).build();
25+
}
26+
27+
/**
28+
* Returns a new {@link ApacheHttpClient5TelemetryBuilder} configured with the given {@link
29+
* OpenTelemetry}.
30+
*/
31+
public static ApacheHttpClient5TelemetryBuilder builder(OpenTelemetry openTelemetry) {
32+
return new ApacheHttpClient5TelemetryBuilder(openTelemetry);
33+
}
34+
35+
private final Instrumenter<ApacheHttpClient5Request, HttpResponse> instrumenter;
36+
private final ContextPropagators propagators;
37+
38+
ApacheHttpClient5Telemetry(
39+
Instrumenter<ApacheHttpClient5Request, HttpResponse> instrumenter,
40+
ContextPropagators propagators) {
41+
this.instrumenter = instrumenter;
42+
this.propagators = propagators;
43+
}
44+
45+
/** Returns a new {@link CloseableHttpClient} with tracing configured. */
46+
public CloseableHttpClient newHttpClient() {
47+
return newHttpClientBuilder().build();
48+
}
49+
50+
/** Returns a new {@link HttpClientBuilder} to create a client with tracing configured. */
51+
public HttpClientBuilder newHttpClientBuilder() {
52+
return org.apache.hc.client5.http.impl.classic.HttpClientBuilder.create()
53+
.addExecInterceptorAfter(
54+
ChainElement.PROTOCOL.name(),
55+
"OtelExecChainHandler",
56+
new OtelExecChainHandler(instrumenter, propagators));
57+
}
58+
}

0 commit comments

Comments
 (0)