Skip to content

[Experiment] Group all the HTTP semconv components into one super-factory #9728

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

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.api.instrumenter.http;

import static io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor.alwaysClient;

import com.google.errorprone.annotations.CanIgnoreReturnValue;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.context.propagation.TextMapSetter;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.SpanStatusExtractor;
import io.opentelemetry.instrumentation.api.internal.HttpConstants;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;

public final class HttpClientSemanticConvention<REQUEST, RESPONSE> {

public static <REQUEST, RESPONSE> HttpClientSemanticConvention<REQUEST, RESPONSE> create(
OpenTelemetry openTelemetry,
String instrumentationName,
HttpClientAttributesGetter<REQUEST, RESPONSE> getter) {
return new HttpClientSemanticConvention<>(openTelemetry, instrumentationName, getter);
}

final OpenTelemetry openTelemetry;
final String instrumentationName;
final HttpClientAttributesGetter<REQUEST, RESPONSE> getter;

final HttpSpanNameExtractorBuilder<REQUEST> spanNameExtractorBuilder;
final HttpClientAttributesExtractorBuilder<REQUEST, RESPONSE> attributesExtractorBuilder;
final SpanStatusExtractor<REQUEST, RESPONSE> spanStatusExtractor;

Consumer<InstrumenterBuilder<REQUEST, RESPONSE>> instrumenterConfigurer = builder -> {};

private HttpClientSemanticConvention(
OpenTelemetry openTelemetry,
String instrumentationName,
HttpClientAttributesGetter<REQUEST, RESPONSE> getter) {
this.openTelemetry = openTelemetry;
this.instrumentationName = instrumentationName;
this.getter = getter;

spanNameExtractorBuilder = HttpSpanNameExtractor.builder(getter);
spanStatusExtractor = HttpSpanStatusExtractor.create(getter);
attributesExtractorBuilder = HttpClientAttributesExtractor.builder(getter);
}

@CanIgnoreReturnValue
public HttpClientSemanticConvention<REQUEST, RESPONSE> configureInstrumenter(
Consumer<InstrumenterBuilder<REQUEST, RESPONSE>> instrumenterConfigurer) {
this.instrumenterConfigurer = this.instrumenterConfigurer.andThen(instrumenterConfigurer);
return this;
}

/**
* Configures the HTTP request headers that will be captured as span attributes as described in <a
* href="https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-spans.md#common-attributes">HTTP
* semantic conventions</a>.
*
* <p>The HTTP request header values will be captured under the {@code http.request.header.<name>}
* attribute key. The {@code <name>} part in the attribute key is the normalized header name:
* lowercase, with dashes replaced by underscores.
*
* @param requestHeaders A list of HTTP header names.
*/
@CanIgnoreReturnValue
public HttpClientSemanticConvention<REQUEST, RESPONSE> setCapturedRequestHeaders(
List<String> requestHeaders) {
attributesExtractorBuilder.setCapturedRequestHeaders(requestHeaders);
return this;
}

/**
* Configures the HTTP response headers that will be captured as span attributes as described in
* <a
* href="https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-spans.md#common-attributes">HTTP
* semantic conventions</a>.
*
* <p>The HTTP response header values will be captured under the {@code
* http.response.header.<name>} attribute key. The {@code <name>} part in the attribute key is the
* normalized header name: lowercase, with dashes replaced by underscores.
*
* @param responseHeaders A list of HTTP header names.
*/
@CanIgnoreReturnValue
public HttpClientSemanticConvention<REQUEST, RESPONSE> setCapturedResponseHeaders(
List<String> responseHeaders) {
attributesExtractorBuilder.setCapturedResponseHeaders(responseHeaders);
return this;
}

/**
* Configures the extractor to recognize an alternative set of HTTP request methods.
*
* <p>By default, this extractor defines "known" methods as the ones listed in <a
* href="https://www.rfc-editor.org/rfc/rfc9110.html#name-methods">RFC9110</a> and the PATCH
* method defined in <a href="https://www.rfc-editor.org/rfc/rfc5789.html">RFC5789</a>. If an
* unknown method is encountered, the extractor will use the value {@value HttpConstants#_OTHER}
* instead of it and put the original value in an extra {@code http.request.method_original}
* attribute.
*
* <p>Note: calling this method <b>overrides</b> the default known method sets completely; it does
* not supplement it.
*
* @param knownMethods A set of recognized HTTP request methods.
*/
@CanIgnoreReturnValue
public HttpClientSemanticConvention<REQUEST, RESPONSE> setKnownMethods(Set<String> knownMethods) {
spanNameExtractorBuilder.setKnownMethods(knownMethods);
attributesExtractorBuilder.setKnownMethods(knownMethods);
return this;
}

public Instrumenter<REQUEST, RESPONSE> buildInstrumenter(TextMapSetter<REQUEST> setter) {
return buildInstrumenter(builder -> builder.buildClientInstrumenter(setter));
}

public Instrumenter<REQUEST, RESPONSE> buildInstrumenter() {
return buildInstrumenter(builder -> builder.buildInstrumenter(alwaysClient()));
}

private Instrumenter<REQUEST, RESPONSE> buildInstrumenter(
Function<InstrumenterBuilder<REQUEST, RESPONSE>, Instrumenter<REQUEST, RESPONSE>>
constructor) {

InstrumenterBuilder<REQUEST, RESPONSE> builder =
Instrumenter.<REQUEST, RESPONSE>builder(
openTelemetry, instrumentationName, spanNameExtractorBuilder.build())
.addAttributesExtractor(attributesExtractorBuilder.build())
.setSpanStatusExtractor(spanStatusExtractor)
.addOperationMetrics(HttpClientMetrics.get());

instrumenterConfigurer.accept(builder);

return constructor.apply(builder);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,62 +5,44 @@

package io.opentelemetry.javaagent.instrumentation.okhttp.v2_2;

import static io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor.alwaysClient;

import com.squareup.okhttp.Interceptor;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientExperimentalMetrics;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientMetrics;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientPeerServiceAttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientSemanticConvention;
import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig;

public final class OkHttp2Singletons {

private static final String INSTRUMENTATION_NAME = "io.opentelemetry.okhttp-2.2";

private static final Instrumenter<Request, Response> INSTRUMENTER;
private static final TracingInterceptor TRACING_INTERCEPTOR;

static {
OkHttp2HttpAttributesGetter httpAttributesGetter = new OkHttp2HttpAttributesGetter();

OkHttp2HttpAttributesGetter getter = new OkHttp2HttpAttributesGetter();
OpenTelemetry openTelemetry = GlobalOpenTelemetry.get();

InstrumenterBuilder<Request, Response> builder =
Instrumenter.<Request, Response>builder(
openTelemetry,
INSTRUMENTATION_NAME,
HttpSpanNameExtractor.builder(httpAttributesGetter)
.setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods())
.build())
.setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter))
.addAttributesExtractor(
HttpClientAttributesExtractor.builder(httpAttributesGetter)
.setCapturedRequestHeaders(CommonConfig.get().getClientRequestHeaders())
.setCapturedResponseHeaders(CommonConfig.get().getClientResponseHeaders())
.setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods())
.build())
.addAttributesExtractor(
HttpClientPeerServiceAttributesExtractor.create(
httpAttributesGetter, CommonConfig.get().getPeerServiceResolver()))
.addOperationMetrics(HttpClientMetrics.get());
if (CommonConfig.get().shouldEmitExperimentalHttpClientMetrics()) {
builder.addOperationMetrics(HttpClientExperimentalMetrics.get());
}
INSTRUMENTER = builder.buildInstrumenter(alwaysClient());

TRACING_INTERCEPTOR = new TracingInterceptor(INSTRUMENTER, openTelemetry.getPropagators());
}

public static Instrumenter<Request, Response> instrumenter() {
return INSTRUMENTER;
Instrumenter<Request, Response> instrumenter =
HttpClientSemanticConvention.create(openTelemetry, INSTRUMENTATION_NAME, getter)
.setCapturedRequestHeaders(CommonConfig.get().getClientRequestHeaders())
.setCapturedResponseHeaders(CommonConfig.get().getClientResponseHeaders())
.setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods())
.configureInstrumenter(
builder -> {
builder.addAttributesExtractor(
HttpClientPeerServiceAttributesExtractor.create(
getter, CommonConfig.get().getPeerServiceResolver()));
if (CommonConfig.get().shouldEmitExperimentalHttpClientMetrics()) {
builder.addOperationMetrics(HttpClientExperimentalMetrics.get());
}
})
.buildInstrumenter();

TRACING_INTERCEPTOR = new TracingInterceptor(instrumenter, openTelemetry.getPropagators());
}

public static Interceptor tracingInterceptor() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,16 @@

package io.opentelemetry.javaagent.instrumentation.okhttp.v3_0;

import static java.util.Collections.singletonList;

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientExperimentalMetrics;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientPeerServiceAttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientRequestResendCount;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientSemanticConvention;
import io.opentelemetry.instrumentation.okhttp.v3_0.internal.ConnectionErrorSpanInterceptor;
import io.opentelemetry.instrumentation.okhttp.v3_0.internal.OkHttpAttributesGetter;
import io.opentelemetry.instrumentation.okhttp.v3_0.internal.OkHttpInstrumenterFactory;
import io.opentelemetry.instrumentation.okhttp.v3_0.internal.TracingInterceptor;
import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig;
import okhttp3.Interceptor;
Expand All @@ -25,19 +24,25 @@
/** Holder of singleton interceptors for adding to instrumented clients. */
public final class OkHttp3Singletons {

private static final String INSTRUMENTATION_NAME = "io.opentelemetry.okhttp-3.0";

private static final Instrumenter<Request, Response> INSTRUMENTER =
OkHttpInstrumenterFactory.create(
GlobalOpenTelemetry.get(),
builder ->
builder
.setCapturedRequestHeaders(CommonConfig.get().getClientRequestHeaders())
.setCapturedResponseHeaders(CommonConfig.get().getClientResponseHeaders())
.setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()),
builder -> builder.setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()),
singletonList(
HttpClientPeerServiceAttributesExtractor.create(
OkHttpAttributesGetter.INSTANCE, CommonConfig.get().getPeerServiceResolver())),
CommonConfig.get().shouldEmitExperimentalHttpClientMetrics());
HttpClientSemanticConvention.create(
GlobalOpenTelemetry.get(), INSTRUMENTATION_NAME, OkHttpAttributesGetter.INSTANCE)
.setCapturedRequestHeaders(CommonConfig.get().getClientRequestHeaders())
.setCapturedResponseHeaders(CommonConfig.get().getClientResponseHeaders())
.setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods())
.configureInstrumenter(
builder -> {
if (CommonConfig.get().shouldEmitExperimentalHttpClientMetrics()) {
builder.addOperationMetrics(HttpClientExperimentalMetrics.get());
}
builder.addAttributesExtractor(
HttpClientPeerServiceAttributesExtractor.create(
OkHttpAttributesGetter.INSTANCE,
CommonConfig.get().getPeerServiceResolver()));
})
.buildInstrumenter();

public static final Interceptor CONTEXT_INTERCEPTOR =
chain -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,28 @@
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractorBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractorBuilder;
import io.opentelemetry.instrumentation.okhttp.v3_0.internal.OkHttpInstrumenterFactory;
import java.util.ArrayList;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientExperimentalMetrics;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientSemanticConvention;
import io.opentelemetry.instrumentation.okhttp.v3_0.internal.OkHttpAttributesGetter;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import okhttp3.Request;
import okhttp3.Response;

/** A builder of {@link OkHttpTelemetry}. */
public final class OkHttpTelemetryBuilder {

private static final String INSTRUMENTATION_NAME = "io.opentelemetry.okhttp-3.0";

private final OpenTelemetry openTelemetry;
private final List<AttributesExtractor<Request, Response>> additionalExtractors =
new ArrayList<>();
private Consumer<HttpClientAttributesExtractorBuilder<Request, Response>> extractorConfigurer =
builder -> {};
private Consumer<HttpSpanNameExtractorBuilder<Request>> spanNameExtractorConfigurer =
builder -> {};
private final HttpClientSemanticConvention<Request, Response> semanticConvention;
private boolean emitExperimentalHttpClientMetrics = false;

OkHttpTelemetryBuilder(OpenTelemetry openTelemetry) {
this.openTelemetry = openTelemetry;
semanticConvention =
HttpClientSemanticConvention.create(
openTelemetry, INSTRUMENTATION_NAME, OkHttpAttributesGetter.INSTANCE);
}

/**
Expand All @@ -41,7 +40,8 @@ public final class OkHttpTelemetryBuilder {
@CanIgnoreReturnValue
public OkHttpTelemetryBuilder addAttributesExtractor(
AttributesExtractor<Request, Response> attributesExtractor) {
additionalExtractors.add(attributesExtractor);
semanticConvention.configureInstrumenter(
builder -> builder.addAttributesExtractor(attributesExtractor));
return this;
}

Expand All @@ -52,8 +52,7 @@ public OkHttpTelemetryBuilder addAttributesExtractor(
*/
@CanIgnoreReturnValue
public OkHttpTelemetryBuilder setCapturedRequestHeaders(List<String> requestHeaders) {
extractorConfigurer =
extractorConfigurer.andThen(builder -> builder.setCapturedRequestHeaders(requestHeaders));
semanticConvention.setCapturedRequestHeaders(requestHeaders);
return this;
}

Expand All @@ -64,8 +63,7 @@ public OkHttpTelemetryBuilder setCapturedRequestHeaders(List<String> requestHead
*/
@CanIgnoreReturnValue
public OkHttpTelemetryBuilder setCapturedResponseHeaders(List<String> responseHeaders) {
extractorConfigurer =
extractorConfigurer.andThen(builder -> builder.setCapturedResponseHeaders(responseHeaders));
semanticConvention.setCapturedResponseHeaders(responseHeaders);
return this;
}

Expand All @@ -84,10 +82,7 @@ public OkHttpTelemetryBuilder setCapturedResponseHeaders(List<String> responseHe
*/
@CanIgnoreReturnValue
public OkHttpTelemetryBuilder setKnownMethods(Set<String> knownMethods) {
extractorConfigurer =
extractorConfigurer.andThen(builder -> builder.setKnownMethods(knownMethods));
spanNameExtractorConfigurer =
spanNameExtractorConfigurer.andThen(builder -> builder.setKnownMethods(knownMethods));
semanticConvention.setKnownMethods(knownMethods);
return this;
}

Expand All @@ -108,13 +103,11 @@ public OkHttpTelemetryBuilder setEmitExperimentalHttpClientMetrics(
* Returns a new {@link OkHttpTelemetry} with the settings of this {@link OkHttpTelemetryBuilder}.
*/
public OkHttpTelemetry build() {
if (emitExperimentalHttpClientMetrics) {
semanticConvention.configureInstrumenter(
builder -> builder.addOperationMetrics(HttpClientExperimentalMetrics.get()));
}
return new OkHttpTelemetry(
OkHttpInstrumenterFactory.create(
openTelemetry,
extractorConfigurer,
spanNameExtractorConfigurer,
additionalExtractors,
emitExperimentalHttpClientMetrics),
openTelemetry.getPropagators());
semanticConvention.buildInstrumenter(), openTelemetry.getPropagators());
}
}
Loading