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

HttpURLConnection instrumentation migration to AutoService API #592

Merged
Show file tree
Hide file tree
Changes from all 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
25 changes: 19 additions & 6 deletions instrumentation/httpurlconnection/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,28 @@ byteBuddy("io.opentelemetry.android:httpurlconnection-agent:AUTO_HTTP_URL_INSTRU

To schedule a periodically running thread to conclude/report spans on any unreported, idle connections, add the below code in the function where your application starts ( that could be onCreate() method of first Activity/Fragment/Service):
```Java
HttpUrlInstrumentationConfig.setConnectionInactivityTimeoutMs(customTimeoutValue); //This is optional. Replace customTimeoutValue with a long data type value which denotes the connection inactivity timeout in milli seconds. Defaults to 10000ms
Executors.newSingleThreadScheduledExecutor().scheduleWithFixedDelay(HttpUrlInstrumentationConfig.getReportIdleConnectionRunnable(), 0, HttpUrlInstrumentationConfig.getReportIdleConnectionInterval(), TimeUnit.MILLISECONDS);
HttpUrlInstrumentation instrumentation = AndroidInstrumentationLoader.getInstrumentation(HttpUrlInstrumentation.class);
instrumentation.setConnectionInactivityTimeoutMs(customTimeoutValue); //This is optional. Replace customTimeoutValue with a long data type value which denotes the connection inactivity timeout in milli seconds. Defaults to 10000ms
Executors.newSingleThreadScheduledExecutor().scheduleWithFixedDelay(instrumentation.getReportIdleConnectionRunnable(), 0, instrumentation.getReportIdleConnectionInterval(), TimeUnit.MILLISECONDS);
```

- `HttpUrlInstrumentationConfig.getReportIdleConnectionRunnable()` is the API to get the runnable.
- By default, connections inactive for 10000ms are reported. To optionally change the connection inactivity timeout call `HttpUrlInstrumentationConfig.setConnectionInactivityTimeoutMs(customTimeoutValue)` API as shown in above code snippet.
- It is efficient to run the harvester thread at the same time interval as the connection inactivity timeout used to identify the connections to be reported. `HttpUrlInstrumentationConfig.getReportIdleConnectionInterval()` is the API to get the same connection inactivity timeout interval (milliseconds) you have configured or the default value of 10000ms if not configured.
- `instrumentation.getReportIdleConnectionRunnable()` is the API to get the runnable.
- By default, connections inactive for 10000ms are reported. To optionally change the connection inactivity timeout call `instrumentation.setConnectionInactivityTimeoutMs(customTimeoutValue)` API as shown in above code snippet.
- It is efficient to run the harvester thread at the same time interval as the connection inactivity timeout used to identify the connections to be reported. `instrumentation.getReportIdleConnectionInterval()` is the API to get the same connection inactivity timeout interval (milliseconds) you have configured or the default value of 10000ms if not configured.

#### Other Optional Configurations
You can optionally configure the automatic instrumentation by using the setters in [HttpUrlInstrumentationConfig](library/src/main/java/io/opentelemetry/instrumentation/library/httpurlconnection/HttpUrlInstrumentationConfig.java).
You can optionally configure the automatic instrumentation by using the setters from [HttpUrlInstrumentation](library/src/main/java/io/opentelemetry/instrumentation/library/httpurlconnection/HttpUrlInstrumentation.java)
instance provided via the AndroidInstrumentationLoader as shown below:

```java
HttpUrlInstrumentation instrumentation = AndroidInstrumentationLoader.getInstrumentation(HttpUrlInstrumentation.class);

// Call `instrumentation` setters.
```

> [!NOTE]
> You must make sure to apply any configurations **before** initializing your OpenTelemetryRum
> instance (i.e. calling OpenTelemetryRum.builder()...build()). Otherwise your configs won't be
> taken into account during the RUM initialization process.

After adding the plugin and the dependencies to your project, and after doing the required configurations, your requests will be traced automatically.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ dependencies {
api(platform(libs.opentelemetry.platform))
api(libs.opentelemetry.api)
api(libs.opentelemetry.context)
api(project(":core"))
implementation(libs.opentelemetry.instrumentation.apiSemconv)
implementation(libs.opentelemetry.instrumentation.api)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,33 @@

package io.opentelemetry.instrumentation.library.httpurlconnection;

import android.app.Application;
import com.google.auto.service.AutoService;
import io.opentelemetry.android.OpenTelemetryRum;
import io.opentelemetry.android.instrumentation.AndroidInstrumentation;
import io.opentelemetry.instrumentation.api.incubator.semconv.net.PeerServiceResolver;
import io.opentelemetry.instrumentation.api.internal.HttpConstants;
import io.opentelemetry.instrumentation.library.httpurlconnection.internal.HttpUrlConnectionSingletons;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jetbrains.annotations.NotNull;

/** Configuration for automatic instrumentation of HttpURLConnection/HttpsURLConnection requests. */
public final class HttpUrlInstrumentationConfig {
private static List<String> capturedRequestHeaders = new ArrayList<>();
private static List<String> capturedResponseHeaders = new ArrayList<>();
private static Set<String> knownMethods = HttpConstants.KNOWN_METHODS;
private static Map<String, String> peerServiceMapping = new HashMap<>();
private static boolean emitExperimentalHttpClientMetrics;
/** Instrumentation for HttpURLConnection requests. */
@AutoService(AndroidInstrumentation.class)
public class HttpUrlInstrumentation implements AndroidInstrumentation {
private List<String> capturedRequestHeaders = new ArrayList<>();
private List<String> capturedResponseHeaders = new ArrayList<>();
private Set<String> knownMethods = HttpConstants.KNOWN_METHODS;
private Map<String, String> peerServiceMapping = new HashMap<>();
private boolean emitExperimentalHttpClientMetrics;

// Time (ms) to wait before assuming that an idle connection is no longer
// in use and should be reported.
private static long connectionInactivityTimeoutMs = 10000;

private HttpUrlInstrumentationConfig() {}
private long connectionInactivityTimeoutMs = 10000;

/**
* Configures the HTTP request headers that will be captured as span attributes as described in
Expand All @@ -40,11 +45,11 @@ private HttpUrlInstrumentationConfig() {}
*
* @param requestHeaders A list of HTTP header names.
*/
public static void setCapturedRequestHeaders(List<String> requestHeaders) {
HttpUrlInstrumentationConfig.capturedRequestHeaders = new ArrayList<>(requestHeaders);
public void setCapturedRequestHeaders(List<String> requestHeaders) {
capturedRequestHeaders = new ArrayList<>(requestHeaders);
}

public static List<String> getCapturedRequestHeaders() {
public List<String> getCapturedRequestHeaders() {
return capturedRequestHeaders;
}

Expand All @@ -60,11 +65,11 @@ public static List<String> getCapturedRequestHeaders() {
*
* @param responseHeaders A list of HTTP header names.
*/
public static void setCapturedResponseHeaders(List<String> responseHeaders) {
HttpUrlInstrumentationConfig.capturedResponseHeaders = new ArrayList<>(responseHeaders);
public void setCapturedResponseHeaders(List<String> responseHeaders) {
capturedResponseHeaders = new ArrayList<>(responseHeaders);
}

public static List<String> getCapturedResponseHeaders() {
public List<String> getCapturedResponseHeaders() {
return capturedResponseHeaders;
}

Expand All @@ -83,11 +88,11 @@ public static List<String> getCapturedResponseHeaders() {
*
* @param knownMethods A set of recognized HTTP request methods.
*/
public static void setKnownMethods(Set<String> knownMethods) {
HttpUrlInstrumentationConfig.knownMethods = new HashSet<>(knownMethods);
public void setKnownMethods(Set<String> knownMethods) {
this.knownMethods = new HashSet<>(knownMethods);
}

public static Set<String> getKnownMethods() {
public Set<String> getKnownMethods() {
return knownMethods;
}

Expand All @@ -96,11 +101,11 @@ public static Set<String> getKnownMethods() {
* href="https://github.com/open-telemetry/opentelemetry-specification/blob/4f23dce407b6fcaba34a049df7c3d41cdd58cb77/specification/trace/semantic_conventions/span-general.md#general-remote-service-attributes">the
* specification</a>.
*/
public static void setPeerServiceMapping(Map<String, String> peerServiceMapping) {
HttpUrlInstrumentationConfig.peerServiceMapping = new HashMap<>(peerServiceMapping);
public void setPeerServiceMapping(Map<String, String> peerServiceMapping) {
this.peerServiceMapping = new HashMap<>(peerServiceMapping);
}

public static PeerServiceResolver newPeerServiceResolver() {
public PeerServiceResolver newPeerServiceResolver() {
return PeerServiceResolver.create(peerServiceMapping);
}

Expand All @@ -109,16 +114,20 @@ public static PeerServiceResolver newPeerServiceResolver() {
* href="https://github.com/open-telemetry/opentelemetry-specification/blob/4f23dce407b6fcaba34a049df7c3d41cdd58cb77/specification/metrics/semantic_conventions/http-metrics.md#http-client">the
* experimental HTTP client metrics</a>.
*/
public static void setEmitExperimentalHttpClientMetrics(
boolean emitExperimentalHttpClientMetrics) {
HttpUrlInstrumentationConfig.emitExperimentalHttpClientMetrics =
emitExperimentalHttpClientMetrics;
public void setEmitExperimentalHttpClientMetrics(boolean emitExperimentalHttpClientMetrics) {
this.emitExperimentalHttpClientMetrics = emitExperimentalHttpClientMetrics;
}

public static boolean emitExperimentalHttpClientMetrics() {
public boolean emitExperimentalHttpClientMetrics() {
return emitExperimentalHttpClientMetrics;
}

@Override
public void install(
@NotNull Application application, @NotNull OpenTelemetryRum openTelemetryRum) {
HttpUrlConnectionSingletons.configure(this, openTelemetryRum.getOpenTelemetry());
}

/**
* Configures the connection inactivity timeout in milliseconds.
*
Expand All @@ -135,11 +144,11 @@ public static boolean emitExperimentalHttpClientMetrics() {
* @param timeoutMs the timeout period in milliseconds. Must be non-negative.
* @throws IllegalArgumentException if {@code timeoutMs} is negative.
*/
public static void setConnectionInactivityTimeoutMs(long timeoutMs) {
public void setConnectionInactivityTimeoutMs(long timeoutMs) {
if (timeoutMs < 0) {
throw new IllegalArgumentException("timeoutMs must be non-negative");
}
HttpUrlInstrumentationConfig.connectionInactivityTimeoutMs = timeoutMs;
connectionInactivityTimeoutMs = timeoutMs;
}

/**
Expand All @@ -150,8 +159,8 @@ public static void setConnectionInactivityTimeoutMs(long timeoutMs) {
*
* @param timeoutMsForTesting the timeout period in milliseconds.
*/
public static void setConnectionInactivityTimeoutMsForTesting(long timeoutMsForTesting) {
HttpUrlInstrumentationConfig.connectionInactivityTimeoutMs = timeoutMsForTesting;
public void setConnectionInactivityTimeoutMsForTesting(long timeoutMsForTesting) {
connectionInactivityTimeoutMs = timeoutMsForTesting;
}

/**
Expand All @@ -161,7 +170,7 @@ public static void setConnectionInactivityTimeoutMsForTesting(long timeoutMsForT
*
* @return The idle connection reporting runnable
*/
public static Runnable getReportIdleConnectionRunnable() {
public Runnable getReportIdleConnectionRunnable() {
return new Runnable() {
@Override
public void run() {
Expand All @@ -181,7 +190,7 @@ public String toString() {
*
* @return The interval duration in ms
*/
public static long getReportIdleConnectionInterval() {
public long getReportIdleConnectionInterval() {
return connectionInactivityTimeoutMs;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,10 @@

package io.opentelemetry.instrumentation.library.httpurlconnection;

import static io.opentelemetry.instrumentation.library.httpurlconnection.internal.HttpUrlConnectionSingletons.instrumenter;
import static io.opentelemetry.instrumentation.library.httpurlconnection.internal.HttpUrlConnectionSingletons.openTelemetryInstance;

import android.annotation.SuppressLint;
import android.os.SystemClock;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.library.httpurlconnection.internal.HttpUrlConnectionSingletons;
import io.opentelemetry.instrumentation.library.httpurlconnection.internal.RequestPropertySetter;
import java.io.IOException;
import java.io.InputStream;
Expand Down Expand Up @@ -268,20 +266,22 @@ private static void endTracing(URLConnection connection, int responseCode, Throw
HttpURLConnectionInfo info = activeURLConnections.get(connection);
if (info != null && !info.reported) {
Context context = info.context;
instrumenter().end(context, connection, responseCode, error);
HttpUrlConnectionSingletons.instrumenter()
.end(context, connection, responseCode, error);
info.reported = true;
activeURLConnections.remove(connection);
}
}

private static void startTracingAtFirstConnection(URLConnection connection) {
Context parentContext = Context.current();
if (!instrumenter().shouldStart(parentContext, connection)) {
if (!HttpUrlConnectionSingletons.instrumenter().shouldStart(parentContext, connection)) {
return;
}

if (!activeURLConnections.containsKey(connection)) {
Context context = instrumenter().start(parentContext, connection);
Context context =
HttpUrlConnectionSingletons.instrumenter().start(parentContext, connection);
activeURLConnections.put(connection, new HttpURLConnectionInfo(context));
try {
injectContextToRequest(connection, context);
Expand All @@ -301,7 +301,7 @@ private static void startTracingAtFirstConnection(URLConnection connection) {
}

private static void injectContextToRequest(URLConnection connection, Context context) {
openTelemetryInstance()
HttpUrlConnectionSingletons.openTelemetryInstance()
.getPropagators()
.getTextMapPropagator()
.inject(context, connection, RequestPropertySetter.INSTANCE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

package io.opentelemetry.instrumentation.library.httpurlconnection.internal;

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.api.incubator.semconv.http.HttpClientExperimentalMetrics;
import io.opentelemetry.instrumentation.api.incubator.semconv.http.HttpClientPeerServiceAttributesExtractor;
Expand All @@ -18,41 +17,40 @@
import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanNameExtractor;
import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanNameExtractorBuilder;
import io.opentelemetry.instrumentation.api.semconv.http.HttpSpanStatusExtractor;
import io.opentelemetry.instrumentation.library.httpurlconnection.HttpUrlInstrumentationConfig;
import io.opentelemetry.instrumentation.library.httpurlconnection.HttpUrlInstrumentation;
import java.net.URLConnection;

public final class HttpUrlConnectionSingletons {

private static volatile Instrumenter<URLConnection, Integer> instrumenter;
private static Instrumenter<URLConnection, Integer> instrumenter;
private static final String INSTRUMENTATION_NAME =
"io.opentelemetry.android.http-url-connection";
private static final Object lock = new Object();
private static OpenTelemetry openTelemetryInstance;

public static Instrumenter<URLConnection, Integer> createInstrumenter() {
public static void configure(
HttpUrlInstrumentation instrumentation, OpenTelemetry openTelemetry) {

HttpUrlHttpAttributesGetter httpAttributesGetter = new HttpUrlHttpAttributesGetter();

HttpSpanNameExtractorBuilder<URLConnection> httpSpanNameExtractorBuilder =
HttpSpanNameExtractor.builder(httpAttributesGetter)
.setKnownMethods(HttpUrlInstrumentationConfig.getKnownMethods());
.setKnownMethods(instrumentation.getKnownMethods());

HttpClientAttributesExtractorBuilder<URLConnection, Integer>
httpClientAttributesExtractorBuilder =
HttpClientAttributesExtractor.builder(httpAttributesGetter)
.setCapturedRequestHeaders(
HttpUrlInstrumentationConfig.getCapturedRequestHeaders())
instrumentation.getCapturedRequestHeaders())
.setCapturedResponseHeaders(
HttpUrlInstrumentationConfig.getCapturedResponseHeaders())
.setKnownMethods(HttpUrlInstrumentationConfig.getKnownMethods());
instrumentation.getCapturedResponseHeaders())
.setKnownMethods(instrumentation.getKnownMethods());

HttpClientPeerServiceAttributesExtractor<URLConnection, Integer>
httpClientPeerServiceAttributesExtractor =
HttpClientPeerServiceAttributesExtractor.create(
httpAttributesGetter,
HttpUrlInstrumentationConfig.newPeerServiceResolver());
httpAttributesGetter, instrumentation.newPeerServiceResolver());

openTelemetryInstance = GlobalOpenTelemetry.get();
openTelemetryInstance = openTelemetry;

InstrumenterBuilder<URLConnection, Integer> builder =
Instrumenter.<URLConnection, Integer>builder(
Expand All @@ -65,23 +63,16 @@ public static Instrumenter<URLConnection, Integer> createInstrumenter() {
.addAttributesExtractor(httpClientPeerServiceAttributesExtractor)
.addOperationMetrics(HttpClientMetrics.get());

if (HttpUrlInstrumentationConfig.emitExperimentalHttpClientMetrics()) {
if (instrumentation.emitExperimentalHttpClientMetrics()) {
builder.addAttributesExtractor(
HttpExperimentalAttributesExtractor.create(httpAttributesGetter))
.addOperationMetrics(HttpClientExperimentalMetrics.get());
}

return builder.buildClientInstrumenter(RequestPropertySetter.INSTANCE);
instrumenter = builder.buildClientInstrumenter(RequestPropertySetter.INSTANCE);
}

public static Instrumenter<URLConnection, Integer> instrumenter() {
if (instrumenter == null) {
synchronized (lock) {
if (instrumenter == null) {
instrumenter = createInstrumenter();
}
}
}
return instrumenter;
}

Expand Down
Loading