Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
Expand Up @@ -8,11 +8,13 @@
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.propagation.TextMapSetter;
import io.opentelemetry.instrumentation.api.incubator.config.internal.CommonConfig;
import io.opentelemetry.instrumentation.api.incubator.semconv.http.HttpClientExperimentalAttributesGetter;
import io.opentelemetry.instrumentation.api.incubator.semconv.http.HttpClientExperimentalMetrics;
import io.opentelemetry.instrumentation.api.incubator.semconv.http.HttpClientPeerServiceAttributesExtractor;
import io.opentelemetry.instrumentation.api.incubator.semconv.http.HttpClientUrlTemplate;
import io.opentelemetry.instrumentation.api.incubator.semconv.http.HttpExperimentalAttributesExtractor;
import io.opentelemetry.instrumentation.api.incubator.semconv.net.PeerServiceResolver;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
Expand Down Expand Up @@ -82,7 +84,7 @@ public static <REQUEST, RESPONSE> DefaultHttpClientInstrumenterBuilder<REQUEST,
String instrumentationName,
OpenTelemetry openTelemetry,
HttpClientAttributesGetter<REQUEST, RESPONSE> attributesGetter) {
return new DefaultHttpClientInstrumenterBuilder<REQUEST, RESPONSE>(
return new DefaultHttpClientInstrumenterBuilder<>(
instrumentationName, openTelemetry, attributesGetter, null);
}

Expand All @@ -91,7 +93,7 @@ public static <REQUEST, RESPONSE> DefaultHttpClientInstrumenterBuilder<REQUEST,
OpenTelemetry openTelemetry,
HttpClientAttributesGetter<REQUEST, RESPONSE> attributesGetter,
TextMapSetter<REQUEST> headerSetter) {
return new DefaultHttpClientInstrumenterBuilder<REQUEST, RESPONSE>(
return new DefaultHttpClientInstrumenterBuilder<>(
instrumentationName,
openTelemetry,
attributesGetter,
Expand Down Expand Up @@ -219,12 +221,20 @@ public DefaultHttpClientInstrumenterBuilder<REQUEST, RESPONSE> setBuilderCustomi
}

public Instrumenter<REQUEST, RESPONSE> build() {
if (emitExperimentalHttpClientTelemetry
&& attributesGetter instanceof HttpClientExperimentalAttributesGetter) {
HttpClientExperimentalAttributesGetter<REQUEST, RESPONSE> experimentalAttributesGetter =
(HttpClientExperimentalAttributesGetter<REQUEST, RESPONSE>) attributesGetter;
if (emitExperimentalHttpClientTelemetry) {
Function<REQUEST, String> urlTemplateExtractorFunction = unused -> null;
if (attributesGetter instanceof HttpClientExperimentalAttributesGetter) {
HttpClientExperimentalAttributesGetter<REQUEST, RESPONSE> experimentalAttributesGetter =
(HttpClientExperimentalAttributesGetter<REQUEST, RESPONSE>) attributesGetter;
urlTemplateExtractorFunction = experimentalAttributesGetter::getUrlTemplate;
}
Function<REQUEST, String> urlTemplateExtractor = urlTemplateExtractorFunction;
Experimental.setUrlTemplateExtractor(
httpSpanNameExtractorBuilder, experimentalAttributesGetter::getUrlTemplate);
httpSpanNameExtractorBuilder,
request -> {
String urlTemplate = HttpClientUrlTemplate.get(Context.current());
return urlTemplate != null ? urlTemplate : urlTemplateExtractor.apply(request);
});
}
SpanNameExtractor<? super REQUEST> spanNameExtractor =
spanNameExtractorTransformer.apply(httpSpanNameExtractorBuilder.build());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.api.incubator.semconv.http;

import io.opentelemetry.context.Context;
import io.opentelemetry.context.ContextKey;
import io.opentelemetry.context.ImplicitContextKeyed;
import io.opentelemetry.context.Scope;
import javax.annotation.Nullable;

/** A helper class for setting {@code url.template} attribute value for HTTP client calls. */
public final class HttpClientUrlTemplate {

/**
* Add url template to context and make the new context current. Http client calls made while the
* context is active will set {@code url.template} attribute to the supplied value.
*/
public static Scope with(Context context, String urlTemplate) {
return context.with(new UrlTemplateState(urlTemplate)).makeCurrent();
}

@Nullable
public static String get(Context context) {
UrlTemplateState state = UrlTemplateState.fromContextOrNull(context);
return state != null ? state.urlTemplate : null;
}

private HttpClientUrlTemplate() {}

private static class UrlTemplateState implements ImplicitContextKeyed {

private static final ContextKey<UrlTemplateState> KEY =
ContextKey.named("opentelemetry-http-client-url-template-key");

private final String urlTemplate;

@Nullable
static UrlTemplateState fromContextOrNull(Context context) {
return context.get(KEY);
}

UrlTemplateState(String urlTemplate) {
this.urlTemplate = urlTemplate;
}

@Override
public Context storeInContext(Context context) {
return context.with(KEY, this);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,10 @@ public void onEnd(
internalSet(attributes, HTTP_RESPONSE_BODY_SIZE, responseBodySize);
}

if (getter instanceof HttpClientExperimentalAttributesGetter) {
String urlTemplate = HttpClientUrlTemplate.get(context);
if (urlTemplate != null) {
internalSet(attributes, URL_TEMPLATE, urlTemplate);
} else if (getter instanceof HttpClientExperimentalAttributesGetter) {
HttpClientExperimentalAttributesGetter<REQUEST, RESPONSE> experimentalGetter =
(HttpClientExperimentalAttributesGetter<REQUEST, RESPONSE>) getter;
internalSet(attributes, URL_TEMPLATE, experimentalGetter.getUrlTemplate(request));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@

package io.opentelemetry.instrumentation.api.incubator.semconv.http;

import static io.opentelemetry.api.common.AttributeKey.stringKey;
import static java.util.Arrays.asList;

import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.incubator.metrics.ExtendedLongHistogramBuilder;
import io.opentelemetry.api.incubator.metrics.ExtendedLongUpDownCounterBuilder;
import io.opentelemetry.api.metrics.LongHistogramBuilder;
Expand All @@ -18,6 +20,8 @@
import io.opentelemetry.semconv.UrlAttributes;

final class HttpExperimentalMetricsAdvice {
// copied from UrlIncubatingAttributes
private static final AttributeKey<String> URL_TEMPLATE = stringKey("url.template");

static void applyClientRequestSizeAdvice(LongHistogramBuilder builder) {
if (!(builder instanceof ExtendedLongHistogramBuilder)) {
Expand All @@ -32,7 +36,8 @@ static void applyClientRequestSizeAdvice(LongHistogramBuilder builder) {
NetworkAttributes.NETWORK_PROTOCOL_NAME,
NetworkAttributes.NETWORK_PROTOCOL_VERSION,
ServerAttributes.SERVER_ADDRESS,
ServerAttributes.SERVER_PORT));
ServerAttributes.SERVER_PORT,
URL_TEMPLATE));
}

static void applyServerRequestSizeAdvice(LongHistogramBuilder builder) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@

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

import static io.opentelemetry.api.common.AttributeKey.stringKey;
import static java.util.Arrays.asList;
import static java.util.Collections.unmodifiableList;

import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.incubator.metrics.ExtendedDoubleHistogramBuilder;
import io.opentelemetry.api.metrics.DoubleHistogramBuilder;
import io.opentelemetry.semconv.ErrorAttributes;
Expand All @@ -23,6 +25,9 @@ final class HttpMetricsAdvice {
unmodifiableList(
asList(0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1.0, 2.5, 5.0, 7.5, 10.0));

// copied from UrlIncubatingAttributes
private static final AttributeKey<String> URL_TEMPLATE = stringKey("url.template");

static void applyClientDurationAdvice(DoubleHistogramBuilder builder) {
if (!(builder instanceof ExtendedDoubleHistogramBuilder)) {
return;
Expand All @@ -36,7 +41,9 @@ static void applyClientDurationAdvice(DoubleHistogramBuilder builder) {
NetworkAttributes.NETWORK_PROTOCOL_NAME,
NetworkAttributes.NETWORK_PROTOCOL_VERSION,
ServerAttributes.SERVER_ADDRESS,
ServerAttributes.SERVER_PORT));
ServerAttributes.SERVER_PORT,
// we only add url.template when experimental http client telemetry is enabled
URL_TEMPLATE));
}

static void applyServerDurationAdvice(DoubleHistogramBuilder builder) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.incubator.semconv.http.HttpClientUrlTemplate;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.javaagent.bootstrap.internal.AgentCommonConfig;
import io.opentelemetry.semconv.HttpAttributes;
import java.net.HttpURLConnection;
import java.util.Set;
Expand All @@ -22,9 +24,12 @@ public class HttpMethodAttributeExtractor<
implements AttributesExtractor<REQUEST, RESPONSE> {

private final Set<String> knownMethods;
private final boolean emitExperimentalHttpClientTelemetry;

private HttpMethodAttributeExtractor(Set<String> knownMethods) {
this.knownMethods = knownMethods;
emitExperimentalHttpClientTelemetry =
AgentCommonConfig.get().shouldEmitExperimentalHttpClientTelemetry();
}

public static AttributesExtractor<? super HttpURLConnection, ? super Integer> create(
Expand Down Expand Up @@ -55,9 +60,12 @@ public void onEnd(
} else {
internalSet(attributes, HttpAttributes.HTTP_REQUEST_METHOD, _OTHER);
internalSet(attributes, HttpAttributes.HTTP_REQUEST_METHOD_ORIGINAL, method);
method = "HTTP";
}
Span span = Span.fromContext(context);
span.updateName(method);
String urlTemplate =
emitExperimentalHttpClientTelemetry ? HttpClientUrlTemplate.get(context) : null;
span.updateName(method + (urlTemplate != null ? " " + urlTemplate : ""));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,16 @@ muzzle {
}

dependencies {
compileOnly("org.springframework:spring-web:6.0.0")
library("org.springframework:spring-web:6.0.0")

testInstrumentation(project(":instrumentation:http-url-connection:javaagent"))
}

// spring 6 requires java 17
otelJava {
minJavaVersionSupported.set(JavaVersion.VERSION_17)
}

tasks.withType<Test>().configureEach {
jvmArgs("-Dotel.instrumentation.http.client.emit-experimental-telemetry=true")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.spring.web.v6_0;

import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;

import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.api.incubator.semconv.http.HttpClientUrlTemplate;
import io.opentelemetry.instrumentation.api.incubator.semconv.net.internal.UrlParser;
import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import org.springframework.lang.Nullable;

public class RestTemplateInstrumentation implements TypeInstrumentation {

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("org.springframework.web.client.RestTemplate");
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
named("doExecute").and(takesArgument(1, String.class)),
this.getClass().getName() + "$UrlTemplateAdvice");
}

@SuppressWarnings("unused")
public static class UrlTemplateAdvice {

@Advice.OnMethodEnter(suppress = Throwable.class)
public static Scope onEnter(@Advice.Argument(1) String uriTemplate) {
if (uriTemplate != null) {
String path = UrlParser.getPath(uriTemplate);
if (path != null) {
return HttpClientUrlTemplate.with(Java8BytecodeBridge.currentContext(), path);
}
}
return null;
}

@Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
public static void onExit(@Advice.Enter @Nullable Scope scope) {
if (scope != null) {
scope.close();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
package io.opentelemetry.javaagent.instrumentation.spring.web.v6_0;

import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static java.util.Collections.singletonList;
import static java.util.Arrays.asList;

import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.HelperResourceBuilder;
Expand Down Expand Up @@ -53,6 +53,6 @@ public String getModuleGroup() {

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return singletonList(new WebApplicationContextInstrumentation());
return asList(new WebApplicationContextInstrumentation(), new RestTemplateInstrumentation());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public static class FilterInjectingAdvice {

@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(@Advice.Argument(0) ConfigurableListableBeanFactory beanFactory) {
if (beanFactory instanceof BeanDefinitionRegistry
if (beanFactory instanceof BeanDefinitionRegistry beanDefinitionRegistry
&& !beanFactory.containsBean("otelAutoDispatcherFilter")) {
// Explicitly loading classes allows to catch any class-loading issue or deal with cases
// where the class is not visible.
Expand All @@ -83,8 +83,7 @@ public static void onEnter(@Advice.Argument(0) ConfigurableListableBeanFactory b
beanDefinition.setScope(SCOPE_SINGLETON);
beanDefinition.setBeanClass(clazz);

((BeanDefinitionRegistry) beanFactory)
.registerBeanDefinition("otelAutoDispatcherFilter", beanDefinition);
beanDefinitionRegistry.registerBeanDefinition("otelAutoDispatcherFilter", beanDefinition);
} catch (ClassNotFoundException ignored) {
// Ignore
}
Expand Down
Loading