Skip to content

Commit 27c85e2

Browse files
elbiocaetanotrask
andauthored
feat: instrumentation for apache httpclient 5 (#10100)
Co-authored-by: Trask Stalnaker <[email protected]>
1 parent 7e35f0c commit 27c85e2

File tree

13 files changed

+1085
-1
lines changed

13 files changed

+1085
-1
lines changed

docs/supported-libraries.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ These are the supported libraries and frameworks:
2929
| [Apache DBCP](https://commons.apache.org/proper/commons-dbcp/) | 2.0+ | [opentelemetry-apache-dbcp-2.0](../instrumentation/apache-dbcp-2.0/library) | [Database Pool Metrics] |
3030
| [Apache Dubbo](https://github.com/apache/dubbo/) | 2.7+ | [opentelemetry-apache-dubbo-2.7](../instrumentation/apache-dubbo-2.7/library-autoconfigure) | [RPC Client Spans], [RPC Server Spans] |
3131
| [Apache HttpAsyncClient](https://hc.apache.org/index.html) | 4.1+ | N/A | [HTTP Client Spans], [HTTP Client Metrics] |
32-
| [Apache HttpClient](https://hc.apache.org/index.html) | 2.0+ | [opentelemetry-apache-httpclient-4.3](../instrumentation/apache-httpclient/apache-httpclient-4.3/library) | [HTTP Client Spans], [HTTP Client Metrics] |
32+
| [Apache HttpClient](https://hc.apache.org/index.html) | 2.0+ | [opentelemetry-apache-httpclient-4.3](../instrumentation/apache-httpclient/apache-httpclient-4.3/library),<br>[opentelemetry-apache-httpclient-5.2](../instrumentation/apache-httpclient/apache-httpclient-5.2/library) | [HTTP Client Spans], [HTTP Client Metrics] |
3333
| [Apache Kafka Producer/Consumer API](https://kafka.apache.org/documentation/#producerapi) | 0.11+ | [opentelemetry-kafka-clients-2.6](../instrumentation/kafka/kafka-clients/kafka-clients-2.6/library) | [Messaging Spans] |
3434
| [Apache Kafka Streams API](https://kafka.apache.org/documentation/streams/) | 0.11+ | N/A | [Messaging Spans] |
3535
| [Apache MyFaces](https://myfaces.apache.org/) | 1.2+ (not including 3.x yet) | N/A | Provides `http.route` [2], Controller Spans [3] |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Library Instrumentation for Apache Http client version 5.2
2+
3+
Provides OpenTelemetry instrumentation for [Apache Http Client 5.2](https://hc.apache.org/httpcomponents-client-5.2.x/).
4+
5+
## Quickstart
6+
7+
### Add these dependencies to your project
8+
9+
Replace `OPENTELEMETRY_VERSION` with the [latest
10+
release](https://search.maven.org/search?q=g:io.opentelemetry.instrumentation%20AND%20a:opentelemetry-apache-httpclient-5.2).
11+
12+
For Maven, add to your `pom.xml` dependencies:
13+
14+
```xml
15+
<dependencies>
16+
<dependency>
17+
<groupId>io.opentelemetry.instrumentation</groupId>
18+
<artifactId>opentelemetry-apache-httpclient-5.2</artifactId>
19+
<version>OPENTELEMETRY_VERSION</version>
20+
</dependency>
21+
</dependencies>
22+
```
23+
24+
For Gradle, add to your dependencies:
25+
26+
```groovy
27+
implementation("io.opentelemetry.instrumentation:opentelemetry-apache-httpclient-5.2:OPENTELEMETRY_VERSION")
28+
```
29+
30+
### Usage
31+
32+
The instrumentation library provides the class `ApacheHttpClient5Telemetry` that has a builder
33+
method and allows the creation of an instance of the `HttpClientBuilder` to provide
34+
OpenTelemetry-based spans and context propagation:
35+
36+
```java
37+
import io.opentelemetry.api.OpenTelemetry;
38+
import io.opentelemetry.instrumentation.apachehttpclient.v5_2.ApacheHttpClient5Telemetry;
39+
import org.apache.hc.client5.http.classic.HttpClient;
40+
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
41+
42+
public class ApacheHttpClient5Configuration {
43+
44+
private OpenTelemetry openTelemetry;
45+
46+
public ApacheHttpClient5Configuration(OpenTelemetry openTelemetry) {
47+
this.openTelemetry = openTelemetry;
48+
}
49+
50+
// creates a new http client builder for constructing http clients with open telemetry instrumentation
51+
public HttpClientBuilder createBuilder() {
52+
return ApacheHttpClient5Telemetry.builder(openTelemetry).build().newHttpClientBuilder();
53+
}
54+
55+
// creates a new http client with open telemetry instrumentation
56+
public HttpClient newHttpClient() {
57+
return ApacheHttpClient5Telemetry.builder(openTelemetry).build().newHttpClient();
58+
}
59+
}
60+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.apachehttpclient.v5_2;
7+
8+
import io.opentelemetry.instrumentation.api.semconv.http.HttpClientAttributesGetter;
9+
import java.util.ArrayList;
10+
import java.util.Collections;
11+
import java.util.List;
12+
import javax.annotation.Nullable;
13+
import org.apache.hc.core5.http.Header;
14+
import org.apache.hc.core5.http.HttpResponse;
15+
import org.apache.hc.core5.http.MessageHeaders;
16+
import org.apache.hc.core5.http.ProtocolVersion;
17+
18+
enum ApacheHttpClient5HttpAttributesGetter
19+
implements HttpClientAttributesGetter<ApacheHttpClient5Request, HttpResponse> {
20+
INSTANCE;
21+
22+
@Override
23+
public String getHttpRequestMethod(ApacheHttpClient5Request request) {
24+
return request.getMethod();
25+
}
26+
27+
@Override
28+
@Nullable
29+
public String getUrlFull(ApacheHttpClient5Request request) {
30+
return request.getUrl();
31+
}
32+
33+
@Override
34+
public List<String> getHttpRequestHeader(ApacheHttpClient5Request request, String name) {
35+
return getHeader(request, name);
36+
}
37+
38+
@Override
39+
public Integer getHttpResponseStatusCode(
40+
ApacheHttpClient5Request request, HttpResponse response, @Nullable Throwable error) {
41+
return response.getCode();
42+
}
43+
44+
@Override
45+
public List<String> getHttpResponseHeader(
46+
ApacheHttpClient5Request request, HttpResponse response, String name) {
47+
return getHeader(response, name);
48+
}
49+
50+
private static List<String> getHeader(MessageHeaders messageHeaders, String name) {
51+
return headersToList(messageHeaders.getHeaders(name));
52+
}
53+
54+
private static List<String> getHeader(ApacheHttpClient5Request messageHeaders, String name) {
55+
return headersToList(messageHeaders.getDelegate().getHeaders(name));
56+
}
57+
58+
// minimize memory overhead by not using streams
59+
private static List<String> headersToList(Header[] headers) {
60+
if (headers.length == 0) {
61+
return Collections.emptyList();
62+
}
63+
List<String> headersList = new ArrayList<>(headers.length);
64+
for (Header header : headers) {
65+
headersList.add(header.getValue());
66+
}
67+
return headersList;
68+
}
69+
70+
@Nullable
71+
@Override
72+
public String getNetworkProtocolName(
73+
ApacheHttpClient5Request request, @Nullable HttpResponse response) {
74+
ProtocolVersion protocolVersion = getVersion(request, response);
75+
if (protocolVersion == null) {
76+
return null;
77+
}
78+
return protocolVersion.getProtocol();
79+
}
80+
81+
@Nullable
82+
@Override
83+
public String getNetworkProtocolVersion(
84+
ApacheHttpClient5Request request, @Nullable HttpResponse response) {
85+
ProtocolVersion protocolVersion = getVersion(request, response);
86+
if (protocolVersion == null) {
87+
return null;
88+
}
89+
if (protocolVersion.getMinor() == 0) {
90+
return Integer.toString(protocolVersion.getMajor());
91+
}
92+
return protocolVersion.getMajor() + "." + protocolVersion.getMinor();
93+
}
94+
95+
@Override
96+
@Nullable
97+
public String getServerAddress(ApacheHttpClient5Request request) {
98+
return request.getDelegate().getAuthority().getHostName();
99+
}
100+
101+
@Override
102+
public Integer getServerPort(ApacheHttpClient5Request request) {
103+
return request.getDelegate().getAuthority().getPort();
104+
}
105+
106+
private static ProtocolVersion getVersion(
107+
ApacheHttpClient5Request request, @Nullable HttpResponse response) {
108+
ProtocolVersion protocolVersion = request.getDelegate().getVersion();
109+
if (protocolVersion == null && response != null) {
110+
protocolVersion = response.getVersion();
111+
}
112+
return protocolVersion;
113+
}
114+
}
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_2;
7+
8+
import static java.util.logging.Level.FINE;
9+
10+
import java.net.InetAddress;
11+
import java.net.InetSocketAddress;
12+
import java.net.URI;
13+
import java.net.URISyntaxException;
14+
import java.util.ArrayList;
15+
import java.util.Collections;
16+
import java.util.List;
17+
import java.util.logging.Logger;
18+
import javax.annotation.Nullable;
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+
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_2;
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 HttpClientBuilder.create()
53+
.addExecInterceptorAfter(
54+
ChainElement.PROTOCOL.name(),
55+
"OtelExecChainHandler",
56+
new OtelExecChainHandler(instrumenter, propagators));
57+
}
58+
}

0 commit comments

Comments
 (0)