Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: instrumentation for apache httpclient 5 #10100

Merged
merged 7 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/supported-libraries.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ These are the supported libraries and frameworks:
| [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] |
| [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] |
| [Apache HttpAsyncClient](https://hc.apache.org/index.html) | 4.1+ | N/A | [HTTP Client Spans], [HTTP Client Metrics] |
| [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] |
| [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] |
| [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] |
| [Apache Kafka Streams API](https://kafka.apache.org/documentation/streams/) | 0.11+ | N/A | [Messaging Spans] |
| [Apache MyFaces](https://myfaces.apache.org/) | 1.2+ (not including 3.x yet) | N/A | Provides `http.route` [2], Controller Spans [3] |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Library Instrumentation for Apache Http client version 5.2

Provides OpenTelemetry instrumentation for [Apache Http Client 5.2](https://hc.apache.org/httpcomponents-client-5.2.x/).

## Quickstart

### Add these dependencies to your project

Replace `OPENTELEMETRY_VERSION` with the [latest
release](https://search.maven.org/search?q=g:io.opentelemetry.instrumentation%20AND%20a:opentelemetry-apache-httpclient-5.2).

For Maven, add to your `pom.xml` dependencies:

```xml
<dependencies>
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-apache-httpclient-5.2</artifactId>
<version>OPENTELEMETRY_VERSION</version>
</dependency>
</dependencies>
```

For Gradle, add to your dependencies:

```groovy
implementation("io.opentelemetry.instrumentation:opentelemetry-apache-httpclient-5.2:OPENTELEMETRY_VERSION")
```

### Usage

The instrumentation library provides the class `ApacheHttpClient5Telemetry` that has a builder
method and allows the creation of an instance of the `HttpClientBuilder` to provide
OpenTelemetry-based spans and context propagation:

```java
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.apachehttpclient.v5_2.ApacheHttpClient5Telemetry;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;

public class ApacheHttpClient5Configuration {

private OpenTelemetry openTelemetry;

public ApacheHttpClient5Configuration(OpenTelemetry openTelemetry) {
this.openTelemetry = openTelemetry;
}

// creates a new http client builder for constructing http clients with open telemetry instrumentation
protected HttpClientBuilder createBuilder() {
elbiocaetano marked this conversation as resolved.
Show resolved Hide resolved
return ApacheHttpClient5Telemetry.builder(openTelemetry).build().newHttpClientBuilder();
}

// creates a new http client with open telemetry instrumentation
public HttpClient newHttpClient() {
return ApacheHttpClient5Telemetry.builder(openTelemetry).build().newHttpClient();
}
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
plugins {
id("otel.library-instrumentation")
id("otel.nullaway-conventions")
id("otel.animalsniffer-conventions")
}

dependencies {
library("org.apache.httpcomponents.client5:httpclient5:5.2.1")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.apachehttpclient.v5_2;

import io.opentelemetry.instrumentation.api.semconv.http.HttpClientAttributesGetter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.annotation.Nullable;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.MessageHeaders;
import org.apache.hc.core5.http.ProtocolVersion;

enum ApacheHttpClient5HttpAttributesGetter
implements HttpClientAttributesGetter<ApacheHttpClient5Request, HttpResponse> {
INSTANCE;

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

@Override
@Nullable
public String getUrlFull(ApacheHttpClient5Request request) {
return request.getUrl();
}

@Override
public List<String> getHttpRequestHeader(ApacheHttpClient5Request request, String name) {
return getHeader(request, name);
}

@Override
public Integer getHttpResponseStatusCode(
ApacheHttpClient5Request request, HttpResponse response, @Nullable Throwable error) {
return response.getCode();
}

@Override
public List<String> getHttpResponseHeader(
ApacheHttpClient5Request request, HttpResponse response, String name) {
return getHeader(response, name);
}

private static List<String> getHeader(MessageHeaders messageHeaders, String name) {
return headersToList(messageHeaders.getHeaders(name));
}

private static List<String> getHeader(ApacheHttpClient5Request messageHeaders, String name) {
return headersToList(messageHeaders.getDelegate().getHeaders(name));
}

// minimize memory overhead by not using streams
private static List<String> headersToList(Header[] headers) {
if (headers.length == 0) {
return Collections.emptyList();
}
List<String> headersList = new ArrayList<>(headers.length);
for (Header header : headers) {
headersList.add(header.getValue());
}
return headersList;
}

@Nullable
@Override
public String getNetworkProtocolName(
ApacheHttpClient5Request request, @Nullable HttpResponse response) {
ProtocolVersion protocolVersion = getVersion(request, response);
if (protocolVersion == null) {
return null;
}
return protocolVersion.getProtocol();
}

@Nullable
@Override
public String getNetworkProtocolVersion(
ApacheHttpClient5Request request, @Nullable HttpResponse response) {
ProtocolVersion protocolVersion = getVersion(request, response);
if (protocolVersion == null) {
return null;
}
if (protocolVersion.getMinor() == 0) {
return Integer.toString(protocolVersion.getMajor());
}
return protocolVersion.getMajor() + "." + protocolVersion.getMinor();
}

@Override
@Nullable
public String getServerAddress(ApacheHttpClient5Request request) {
return request.getDelegate().getAuthority().getHostName();
}

@Override
public Integer getServerPort(ApacheHttpClient5Request request) {
return request.getDelegate().getAuthority().getPort();
}

private static ProtocolVersion getVersion(
ApacheHttpClient5Request request, @Nullable HttpResponse response) {
ProtocolVersion protocolVersion = request.getDelegate().getVersion();
if (protocolVersion == null && response != null) {
protocolVersion = response.getVersion();
}
return protocolVersion;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.apachehttpclient.v5_2;

import static java.util.logging.Level.FINE;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.ProtocolVersion;

final class ApacheHttpClient5Request {

private static final Logger logger = Logger.getLogger(ApacheHttpClient5Request.class.getName());

@Nullable private final URI uri;

private final HttpRequest delegate;
@Nullable private final HttpHost target;

ApacheHttpClient5Request(@Nullable HttpHost httpHost, HttpRequest httpRequest) {
URI calculatedUri = getUri(httpRequest);
if (calculatedUri != null && httpHost != null) {
uri = getCalculatedUri(httpHost, calculatedUri);
} else {
uri = calculatedUri;
}
delegate = httpRequest;
target = httpHost;
}

/** Returns the actual {@link HttpRequest} being executed by the client. */
public HttpRequest getDelegate() {
return delegate;
}

List<String> getHeader(String name) {
return headersToList(delegate.getHeaders(name));
}

// minimize memory overhead by not using streams
static List<String> headersToList(Header[] headers) {
if (headers.length == 0) {
return Collections.emptyList();
}
List<String> headersList = new ArrayList<>(headers.length);
for (Header header : headers) {
headersList.add(header.getValue());
}
return headersList;
}

void setHeader(String name, String value) {
delegate.setHeader(name, value);
}

String getMethod() {
return delegate.getMethod();
}

@Nullable
String getUrl() {
return uri != null ? uri.toString() : null;
}

String getProtocolName() {
return delegate.getVersion().getProtocol();
}

String getProtocolVersion() {
ProtocolVersion protocolVersion = delegate.getVersion();
if (protocolVersion.getMinor() == 0) {
return Integer.toString(protocolVersion.getMajor());
}
return protocolVersion.getMajor() + "." + protocolVersion.getMinor();
}

@Nullable
public String getServerAddress() {
return uri == null ? null : uri.getHost();
}

@Nullable
public Integer getServerPort() {
return uri == null ? null : uri.getPort();
}

@Nullable
private static URI getUri(HttpRequest httpRequest) {
try {
// this can be relative or absolute
return new URI(httpRequest.getUri().toString());
} catch (URISyntaxException e) {
logger.log(FINE, e.getMessage(), e);
return null;
}
}

@Nullable
private static URI getCalculatedUri(HttpHost httpHost, URI uri) {
try {
return new URI(
httpHost.getSchemeName(),
uri.getUserInfo(),
httpHost.getHostName(),
httpHost.getPort(),
uri.getPath(),
uri.getQuery(),
uri.getFragment());
} catch (URISyntaxException e) {
logger.log(FINE, e.getMessage(), e);
return null;
}
}

@Nullable
public InetSocketAddress getServerSocketAddress() {
if (target == null) {
return null;
}
InetAddress inetAddress = target.getAddress();
return inetAddress == null ? null : new InetSocketAddress(inetAddress, target.getPort());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.apachehttpclient.v5_2;

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import org.apache.hc.client5.http.impl.ChainElement;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.core5.http.HttpResponse;

/** Entrypoint for instrumenting Apache HTTP Client. */
public final class ApacheHttpClient5Telemetry {

/**
* Returns a new {@link ApacheHttpClient5Telemetry} configured with the given {@link
* OpenTelemetry}.
*/
public static ApacheHttpClient5Telemetry create(OpenTelemetry openTelemetry) {
return builder(openTelemetry).build();
}

/**
* Returns a new {@link ApacheHttpClient5TelemetryBuilder} configured with the given {@link
* OpenTelemetry}.
*/
public static ApacheHttpClient5TelemetryBuilder builder(OpenTelemetry openTelemetry) {
return new ApacheHttpClient5TelemetryBuilder(openTelemetry);
}

private final Instrumenter<ApacheHttpClient5Request, HttpResponse> instrumenter;
private final ContextPropagators propagators;

ApacheHttpClient5Telemetry(
Instrumenter<ApacheHttpClient5Request, HttpResponse> instrumenter,
ContextPropagators propagators) {
this.instrumenter = instrumenter;
this.propagators = propagators;
}

/** Returns a new {@link CloseableHttpClient} with tracing configured. */
public CloseableHttpClient newHttpClient() {
return newHttpClientBuilder().build();
}

/** Returns a new {@link HttpClientBuilder} to create a client with tracing configured. */
public HttpClientBuilder newHttpClientBuilder() {
return HttpClientBuilder.create()
.addExecInterceptorAfter(
ChainElement.PROTOCOL.name(),
"OtelExecChainHandler",
new OtelExecChainHandler(instrumenter, propagators));
}
}
Loading
Loading