Add HTTP server tests for non standard methods (#9446)

This commit is contained in:
Mateusz Rzeszutek 2023-09-15 17:43:36 +02:00 committed by GitHub
parent 2b2c4ca2d2
commit 3136916749
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
65 changed files with 495 additions and 143 deletions

View File

@ -31,13 +31,16 @@ public final class HttpServerRoute {
*/ */
public static <REQUEST> ContextCustomizer<REQUEST> create( public static <REQUEST> ContextCustomizer<REQUEST> create(
HttpServerAttributesGetter<REQUEST, ?> getter) { HttpServerAttributesGetter<REQUEST, ?> getter) {
return (context, request, startAttributes) -> { return builder(getter).build();
if (HttpRouteState.fromContextOrNull(context) != null) {
return context;
} }
String method = getter.getHttpRequestMethod(request);
return context.with(HttpRouteState.create(method, null, 0)); /**
}; * Returns a new {@link HttpServerRouteBuilder} that can be used to configure the {@link
* HttpServerRoute}.
*/
public static <REQUEST> HttpServerRouteBuilder<REQUEST> builder(
HttpServerAttributesGetter<REQUEST, ?> getter) {
return new HttpServerRouteBuilder<>(getter);
} }
private HttpServerRoute() {} private HttpServerRoute() {}
@ -147,12 +150,9 @@ public final class HttpServerRoute {
private static void updateSpanName(Span serverSpan, HttpRouteState httpRouteState, String route) { private static void updateSpanName(Span serverSpan, HttpRouteState httpRouteState, String route) {
String method = httpRouteState.getMethod(); String method = httpRouteState.getMethod();
// method should never really be null - but in case it for some reason is, we'll rely on the // method should never really be null
// span name extractor behavior
if (method != null) {
serverSpan.updateName(method + " " + route); serverSpan.updateName(method + " " + route);
} }
}
/** /**
* Returns the {@code http.route} attribute value that's stored in the {@code context}, or null if * Returns the {@code http.route} attribute value that's stored in the {@code context}, or null if

View File

@ -0,0 +1,65 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.api.instrumenter.http;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.instrumenter.ContextCustomizer;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.internal.HttpConstants;
import io.opentelemetry.instrumentation.api.internal.HttpRouteState;
import java.util.HashSet;
import java.util.Set;
/** A builder of {@link HttpSpanNameExtractor}. */
public final class HttpServerRouteBuilder<REQUEST> {
final HttpServerAttributesGetter<REQUEST, ?> getter;
Set<String> knownMethods = HttpConstants.KNOWN_METHODS;
HttpServerRouteBuilder(HttpServerAttributesGetter<REQUEST, ?> getter) {
this.getter = getter;
}
/**
* Configures the customizer to recognize an alternative set of HTTP request methods.
*
* <p>By default, this customizer 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 customizer will use the value {@value HttpConstants#_OTHER}
* instead.
*
* <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 HttpServerRouteBuilder<REQUEST> setKnownMethods(Set<String> knownMethods) {
this.knownMethods = new HashSet<>(knownMethods);
return this;
}
/**
* Returns a {@link ContextCustomizer} that initializes an {@link HttpServerRoute} in the {@link
* Context} returned from {@link Instrumenter#start(Context, Object)}. The returned customizer is
* configured with the settings of this {@link HttpServerRouteBuilder}.
*/
public ContextCustomizer<REQUEST> build() {
Set<String> knownMethods = new HashSet<>(this.knownMethods);
return (context, request, startAttributes) -> {
if (HttpRouteState.fromContextOrNull(context) != null) {
return context;
}
String method = getter.getHttpRequestMethod(request);
if (method == null || !knownMethods.contains(method)) {
method = "HTTP";
}
return context.with(HttpRouteState.create(method, null, 0));
};
}
}

View File

@ -6,6 +6,7 @@
package io.opentelemetry.instrumentation.api.instrumenter.http; package io.opentelemetry.instrumentation.api.instrumenter.http;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
import static java.util.Collections.singletonList;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ -14,6 +15,7 @@ import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context; import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.sdk.testing.junit5.OpenTelemetryExtension; import io.opentelemetry.sdk.testing.junit5.OpenTelemetryExtension;
import java.util.HashSet;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
@ -33,7 +35,10 @@ class HttpServerRouteTest {
void setUp() { void setUp() {
instrumenter = instrumenter =
Instrumenter.<String, Void>builder(testing.getOpenTelemetry(), "test", s -> s) Instrumenter.<String, Void>builder(testing.getOpenTelemetry(), "test", s -> s)
.addContextCustomizer(HttpServerRoute.create(getter)) .addContextCustomizer(
HttpServerRoute.builder(getter)
.setKnownMethods(new HashSet<>(singletonList("GET")))
.build())
.buildInstrumenter(); .buildInstrumenter();
} }
@ -158,7 +163,7 @@ class HttpServerRouteTest {
} }
@Test @Test
void shouldNotUpdateSpanName_noMethod() { void shouldUseHttp_noMethod() {
when(getter.getHttpRequestMethod("test")).thenReturn(null); when(getter.getHttpRequestMethod("test")).thenReturn(null);
Context context = instrumenter.start(Context.root(), "test"); Context context = instrumenter.start(Context.root(), "test");
@ -169,6 +174,23 @@ class HttpServerRouteTest {
instrumenter.end(context, "test", null, null); instrumenter.end(context, "test", null, null);
assertEquals("/get/:id", HttpServerRoute.get(context)); assertEquals("/get/:id", HttpServerRoute.get(context));
assertThat(testing.getSpans()).satisfiesExactly(span -> assertThat(span).hasName("test")); assertThat(testing.getSpans())
.satisfiesExactly(span -> assertThat(span).hasName("HTTP /get/:id"));
}
@Test
void shouldUseHttp_unknownMethod() {
when(getter.getHttpRequestMethod("test")).thenReturn("POST");
Context context = instrumenter.start(Context.root(), "test");
assertNull(HttpServerRoute.get(context));
HttpServerRoute.update(context, HttpServerRouteSource.SERVER, "/get/:id");
instrumenter.end(context, "test", null, null);
assertEquals("/get/:id", HttpServerRoute.get(context));
assertThat(testing.getSpans())
.satisfiesExactly(span -> assertThat(span).hasName("HTTP /get/:id"));
} }
} }

View File

@ -40,7 +40,10 @@ public final class AkkaHttpServerSingletons {
.setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()) .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods())
.build()) .build())
.addOperationMetrics(HttpServerMetrics.get()) .addOperationMetrics(HttpServerMetrics.get())
.addContextCustomizer(HttpServerRoute.create(httpAttributesGetter)); .addContextCustomizer(
HttpServerRoute.builder(httpAttributesGetter)
.setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods())
.build());
if (CommonConfig.get().shouldEmitExperimentalHttpServerMetrics()) { if (CommonConfig.get().shouldEmitExperimentalHttpServerMetrics()) {
builder.addOperationMetrics(HttpServerExperimentalMetrics.get()); builder.addOperationMetrics(HttpServerExperimentalMetrics.get());
} }

View File

@ -35,5 +35,7 @@ abstract class AbstractHttpServerInstrumentationTest
t != ServerEndpoint.EXCEPTION t != ServerEndpoint.EXCEPTION
} }
) )
// instrumentation does not create a span at all
options.disableTestNonStandardHttpMethod
} }
} }

View File

@ -29,5 +29,6 @@ class ArmeriaHttpServerTest extends AbstractArmeriaHttpServerTest {
options.setHasResponseCustomizer( options.setHasResponseCustomizer(
endpoint -> ServerEndpoint.NOT_FOUND != endpoint && ServerEndpoint.EXCEPTION != endpoint); endpoint -> ServerEndpoint.NOT_FOUND != endpoint && ServerEndpoint.EXCEPTION != endpoint);
options.setTestHttpPipelining(false); options.setTestHttpPipelining(false);
options.setResponseCodeOnNonStandardHttpMethod(405);
} }
} }

View File

@ -24,6 +24,7 @@ import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttribut
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerExperimentalMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerExperimentalMetrics;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerMetrics;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerRoute; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerRoute;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerRouteBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractorBuilder; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractorBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor;
@ -62,6 +63,9 @@ public final class ArmeriaTelemetryBuilder {
private final HttpSpanNameExtractorBuilder<RequestContext> httpServerSpanNameExtractorBuilder = private final HttpSpanNameExtractorBuilder<RequestContext> httpServerSpanNameExtractorBuilder =
HttpSpanNameExtractor.builder(ArmeriaHttpServerAttributesGetter.INSTANCE); HttpSpanNameExtractor.builder(ArmeriaHttpServerAttributesGetter.INSTANCE);
private final HttpServerRouteBuilder<RequestContext> httpServerRouteBuilder =
HttpServerRoute.builder(ArmeriaHttpServerAttributesGetter.INSTANCE);
private Function< private Function<
SpanStatusExtractor<RequestContext, RequestLog>, SpanStatusExtractor<RequestContext, RequestLog>,
? extends SpanStatusExtractor<? super RequestContext, ? super RequestLog>> ? extends SpanStatusExtractor<? super RequestContext, ? super RequestLog>>
@ -175,6 +179,7 @@ public final class ArmeriaTelemetryBuilder {
httpServerAttributesExtractorBuilder.setKnownMethods(knownMethods); httpServerAttributesExtractorBuilder.setKnownMethods(knownMethods);
httpClientSpanNameExtractorBuilder.setKnownMethods(knownMethods); httpClientSpanNameExtractorBuilder.setKnownMethods(knownMethods);
httpServerSpanNameExtractorBuilder.setKnownMethods(knownMethods); httpServerSpanNameExtractorBuilder.setKnownMethods(knownMethods);
httpServerRouteBuilder.setKnownMethods(knownMethods);
return this; return this;
} }
@ -233,7 +238,7 @@ public final class ArmeriaTelemetryBuilder {
HttpSpanStatusExtractor.create(serverAttributesGetter))) HttpSpanStatusExtractor.create(serverAttributesGetter)))
.addAttributesExtractor(httpServerAttributesExtractorBuilder.build()) .addAttributesExtractor(httpServerAttributesExtractorBuilder.build())
.addOperationMetrics(HttpServerMetrics.get()) .addOperationMetrics(HttpServerMetrics.get())
.addContextCustomizer(HttpServerRoute.create(serverAttributesGetter)); .addContextCustomizer(httpServerRouteBuilder.build());
if (peerService != null) { if (peerService != null) {
clientInstrumenterBuilder.addAttributesExtractor( clientInstrumenterBuilder.addAttributesExtractor(

View File

@ -9,6 +9,7 @@ import com.linecorp.armeria.server.ServerBuilder;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerTest; import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerTest;
import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions;
import java.util.Collections; import java.util.Collections;
import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.RegisterExtension;
@ -28,4 +29,11 @@ class ArmeriaHttpServerTest extends AbstractArmeriaHttpServerTest {
.build() .build()
.newServiceDecorator()); .newServiceDecorator());
} }
@Override
protected void configure(HttpServerTestOptions options) {
super.configure(options);
// library instrumentation does not create a span at all
options.disableTestNonStandardHttpMethod();
}
} }

View File

@ -178,13 +178,13 @@ public abstract class AbstractArmeriaHttpServerTest extends AbstractHttpServerTe
@Override @Override
protected void configure(HttpServerTestOptions options) { protected void configure(HttpServerTestOptions options) {
options.setExpectedHttpRoute( options.setExpectedHttpRoute(
endpoint -> { (endpoint, method) -> {
if (endpoint == ServerEndpoint.NOT_FOUND) { if (endpoint == ServerEndpoint.NOT_FOUND) {
// TODO(anuraaga): Revisit this when applying instrumenters to more libraries, Armeria // TODO(anuraaga): Revisit this when applying instrumenters to more libraries, Armeria
// currently reports '/*' which is a fallback route. // currently reports '/*' which is a fallback route.
return "/*"; return "/*";
} }
return expectedHttpRoute(endpoint); return expectedHttpRoute(endpoint, method);
}); });
options.setTestPathParam(true); options.setTestPathParam(true);

View File

@ -83,14 +83,14 @@ class DropwizardTest extends HttpServerTest<DropwizardTestSupport> implements Ag
} }
@Override @Override
String expectedHttpRoute(ServerEndpoint endpoint) { String expectedHttpRoute(ServerEndpoint endpoint, String method) {
switch (endpoint) { switch (endpoint) {
case NOT_FOUND: case NOT_FOUND:
return getContextPath() + "/*" return getContextPath() + "/*"
case PATH_PARAM: case PATH_PARAM:
return getContextPath() + "/path/{id}/param" return getContextPath() + "/path/{id}/param"
default: default:
return super.expectedHttpRoute(endpoint) return super.expectedHttpRoute(endpoint, method)
} }
} }

View File

@ -56,6 +56,7 @@ class FinatraServerTest extends AbstractHttpServerTest[HttpServer] {
override def test(endpoint: ServerEndpoint): Boolean = override def test(endpoint: ServerEndpoint): Boolean =
endpoint != ServerEndpoint.NOT_FOUND endpoint != ServerEndpoint.NOT_FOUND
}) })
options.setResponseCodeOnNonStandardHttpMethod(400)
} }
override protected def assertHandlerSpan( override protected def assertHandlerSpan(

View File

@ -65,12 +65,16 @@ configurations.testRuntimeClasspath {
} }
} }
val latestDepTest = findProperty("testLatestDeps") as Boolean
tasks { tasks {
val testStableSemconv by registering(Test::class) { val testStableSemconv by registering(Test::class) {
jvmArgs("-Dotel.semconv-stability.opt-in=http") jvmArgs("-Dotel.semconv-stability.opt-in=http")
} }
withType<Test>().configureEach { withType<Test>().configureEach {
systemProperty("testLatestDeps", latestDepTest)
// required on jdk17 // required on jdk17
jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED") jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED")
jvmArgs("-XX:+IgnoreUnrecognizedVMOptions") jvmArgs("-XX:+IgnoreUnrecognizedVMOptions")

View File

@ -19,6 +19,7 @@ import grails.boot.GrailsApp;
import grails.boot.config.GrailsAutoConfiguration; import grails.boot.config.GrailsAutoConfiguration;
import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.instrumentation.api.internal.HttpConstants;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerTest; import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerTest;
import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension;
@ -42,6 +43,8 @@ import org.springframework.context.ConfigurableApplicationContext;
public class GrailsTest extends AbstractHttpServerTest<ConfigurableApplicationContext> { public class GrailsTest extends AbstractHttpServerTest<ConfigurableApplicationContext> {
static final boolean testLatestDeps = Boolean.getBoolean("testLatestDeps");
@RegisterExtension @RegisterExtension
static final InstrumentationExtension testing = HttpServerInstrumentationExtension.forAgent(); static final InstrumentationExtension testing = HttpServerInstrumentationExtension.forAgent();
@ -64,6 +67,7 @@ public class GrailsTest extends AbstractHttpServerTest<ConfigurableApplicationCo
options.setHasErrorPageSpans( options.setHasErrorPageSpans(
endpoint -> endpoint == ERROR || endpoint == EXCEPTION || endpoint == NOT_FOUND); endpoint -> endpoint == ERROR || endpoint == EXCEPTION || endpoint == NOT_FOUND);
options.setTestPathParam(true); options.setTestPathParam(true);
options.setResponseCodeOnNonStandardHttpMethod(testLatestDeps ? 200 : 501);
} }
@SpringBootApplication @SpringBootApplication
@ -106,7 +110,12 @@ public class GrailsTest extends AbstractHttpServerTest<ConfigurableApplicationCo
} }
@Override @Override
public String expectedHttpRoute(ServerEndpoint endpoint) { public String expectedHttpRoute(ServerEndpoint endpoint, String method) {
if (HttpConstants._OTHER.equals(method)) {
return testLatestDeps
? getContextPath() + "/test" + endpoint.getPath()
: getContextPath() + "/*";
}
if (PATH_PARAM.equals(endpoint)) { if (PATH_PARAM.equals(endpoint)) {
return getContextPath() + "/test/path"; return getContextPath() + "/test/path";
} else if (QUERY_PARAM.equals(endpoint)) { } else if (QUERY_PARAM.equals(endpoint)) {

View File

@ -54,7 +54,10 @@ public final class GrizzlySingletons {
.init(context)) .init(context))
.addContextCustomizer( .addContextCustomizer(
(context, httpRequestPacket, startAttributes) -> GrizzlyErrorHolder.init(context)) (context, httpRequestPacket, startAttributes) -> GrizzlyErrorHolder.init(context))
.addContextCustomizer(HttpServerRoute.create(httpAttributesGetter)) .addContextCustomizer(
HttpServerRoute.builder(httpAttributesGetter)
.setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods())
.build())
.buildServerInstrumenter(HttpRequestHeadersGetter.INSTANCE); .buildServerInstrumenter(HttpRequestHeadersGetter.INSTANCE);
} }

View File

@ -229,7 +229,6 @@ abstract class AbstractJaxRsHttpServerTest<S> extends HttpServerTest<S> implemen
String traceID = null, String traceID = null,
String parentID = null, String parentID = null,
String method = "GET", String method = "GET",
Long responseContentLength = null,
ServerEndpoint endpoint = SUCCESS, ServerEndpoint endpoint = SUCCESS,
String spanID = null) { String spanID = null) {
serverSpan(trace, index, traceID, parentID, spanID, method, serverSpan(trace, index, traceID, parentID, spanID, method,

View File

@ -40,6 +40,8 @@ class KtorServerTracing private constructor(
internal val httpSpanNameExtractorBuilder = HttpSpanNameExtractor.builder(KtorHttpServerAttributesGetter.INSTANCE) internal val httpSpanNameExtractorBuilder = HttpSpanNameExtractor.builder(KtorHttpServerAttributesGetter.INSTANCE)
internal val httpServerRouteBuilder = HttpServerRoute.builder(KtorHttpServerAttributesGetter.INSTANCE)
internal var statusExtractor: internal var statusExtractor:
(SpanStatusExtractor<ApplicationRequest, ApplicationResponse>) -> SpanStatusExtractor<in ApplicationRequest, in ApplicationResponse> = { a -> a } (SpanStatusExtractor<ApplicationRequest, ApplicationResponse>) -> SpanStatusExtractor<in ApplicationRequest, in ApplicationResponse> = { a -> a }
@ -77,6 +79,7 @@ class KtorServerTracing private constructor(
fun setKnownMethods(knownMethods: Set<String>) { fun setKnownMethods(knownMethods: Set<String>) {
httpAttributesExtractorBuilder.setKnownMethods(knownMethods) httpAttributesExtractorBuilder.setKnownMethods(knownMethods)
httpSpanNameExtractorBuilder.setKnownMethods(knownMethods) httpSpanNameExtractorBuilder.setKnownMethods(knownMethods)
httpServerRouteBuilder.setKnownMethods(knownMethods)
} }
internal fun isOpenTelemetryInitialized(): Boolean = this::openTelemetry.isInitialized internal fun isOpenTelemetryInitialized(): Boolean = this::openTelemetry.isInitialized
@ -124,7 +127,7 @@ class KtorServerTracing private constructor(
setSpanStatusExtractor(configuration.statusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter))) setSpanStatusExtractor(configuration.statusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter)))
addAttributesExtractor(configuration.httpAttributesExtractorBuilder.build()) addAttributesExtractor(configuration.httpAttributesExtractorBuilder.build())
addOperationMetrics(HttpServerMetrics.get()) addOperationMetrics(HttpServerMetrics.get())
addContextCustomizer(HttpServerRoute.create(httpAttributesGetter)) addContextCustomizer(configuration.httpServerRouteBuilder.build())
} }
val instrumenter = InstrumenterUtil.buildUpstreamInstrumenter( val instrumenter = InstrumenterUtil.buildUpstreamInstrumenter(

View File

@ -129,10 +129,10 @@ class KtorHttpServerTest : AbstractHttpServerTest<ApplicationEngine>() {
HttpServerTestOptions.DEFAULT_HTTP_ATTRIBUTES - SemanticAttributes.NET_PEER_PORT HttpServerTestOptions.DEFAULT_HTTP_ATTRIBUTES - SemanticAttributes.NET_PEER_PORT
} }
options.setExpectedHttpRoute { options.setExpectedHttpRoute { endpoint, method ->
when (it) { when (endpoint) {
ServerEndpoint.PATH_PARAM -> "/path/{id}/param" ServerEndpoint.PATH_PARAM -> "/path/{id}/param"
else -> expectedHttpRoute(it) else -> expectedHttpRoute(endpoint, method)
} }
} }
// ktor does not have a controller lifecycle so the server span ends immediately when the // ktor does not have a controller lifecycle so the server span ends immediately when the

View File

@ -41,6 +41,8 @@ class KtorServerTracing private constructor(
internal val httpSpanNameExtractorBuilder = HttpSpanNameExtractor.builder(KtorHttpServerAttributesGetter.INSTANCE) internal val httpSpanNameExtractorBuilder = HttpSpanNameExtractor.builder(KtorHttpServerAttributesGetter.INSTANCE)
internal val httpServerRouteBuilder = HttpServerRoute.builder(KtorHttpServerAttributesGetter.INSTANCE)
internal var statusExtractor: internal var statusExtractor:
(SpanStatusExtractor<ApplicationRequest, ApplicationResponse>) -> SpanStatusExtractor<in ApplicationRequest, in ApplicationResponse> = { a -> a } (SpanStatusExtractor<ApplicationRequest, ApplicationResponse>) -> SpanStatusExtractor<in ApplicationRequest, in ApplicationResponse> = { a -> a }
@ -78,6 +80,7 @@ class KtorServerTracing private constructor(
fun setKnownMethods(knownMethods: Set<String>) { fun setKnownMethods(knownMethods: Set<String>) {
httpAttributesExtractorBuilder.setKnownMethods(knownMethods) httpAttributesExtractorBuilder.setKnownMethods(knownMethods)
httpSpanNameExtractorBuilder.setKnownMethods(knownMethods) httpSpanNameExtractorBuilder.setKnownMethods(knownMethods)
httpServerRouteBuilder.setKnownMethods(knownMethods)
} }
internal fun isOpenTelemetryInitialized(): Boolean = this::openTelemetry.isInitialized internal fun isOpenTelemetryInitialized(): Boolean = this::openTelemetry.isInitialized
@ -124,7 +127,7 @@ class KtorServerTracing private constructor(
setSpanStatusExtractor(configuration.statusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter))) setSpanStatusExtractor(configuration.statusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter)))
addAttributesExtractor(configuration.httpAttributesExtractorBuilder.build()) addAttributesExtractor(configuration.httpAttributesExtractorBuilder.build())
addOperationMetrics(HttpServerMetrics.get()) addOperationMetrics(HttpServerMetrics.get())
addContextCustomizer(HttpServerRoute.create(httpAttributesGetter)) addContextCustomizer(configuration.httpServerRouteBuilder.build())
} }
val instrumenter = InstrumenterUtil.buildUpstreamInstrumenter( val instrumenter = InstrumenterUtil.buildUpstreamInstrumenter(

View File

@ -126,10 +126,10 @@ abstract class AbstractKtorHttpServerTest : AbstractHttpServerTest<ApplicationEn
HttpServerTestOptions.DEFAULT_HTTP_ATTRIBUTES - SemanticAttributes.NET_PEER_PORT HttpServerTestOptions.DEFAULT_HTTP_ATTRIBUTES - SemanticAttributes.NET_PEER_PORT
} }
options.setExpectedHttpRoute { options.setExpectedHttpRoute { endpoint, method ->
when (it) { when (endpoint) {
ServerEndpoint.PATH_PARAM -> "/path/{id}/param" ServerEndpoint.PATH_PARAM -> "/path/{id}/param"
else -> expectedHttpRoute(it) else -> expectedHttpRoute(endpoint, method)
} }
} }

View File

@ -39,7 +39,10 @@ public final class LibertyDispatcherSingletons {
.setCapturedResponseHeaders(CommonConfig.get().getServerResponseHeaders()) .setCapturedResponseHeaders(CommonConfig.get().getServerResponseHeaders())
.setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()) .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods())
.build()) .build())
.addContextCustomizer(HttpServerRoute.create(httpAttributesGetter)) .addContextCustomizer(
HttpServerRoute.builder(httpAttributesGetter)
.setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods())
.build())
.addOperationMetrics(HttpServerMetrics.get()); .addOperationMetrics(HttpServerMetrics.get());
if (CommonConfig.get().shouldEmitExperimentalHttpServerMetrics()) { if (CommonConfig.get().shouldEmitExperimentalHttpServerMetrics()) {
builder.addOperationMetrics(HttpServerExperimentalMetrics.get()); builder.addOperationMetrics(HttpServerExperimentalMetrics.get());

View File

@ -49,7 +49,10 @@ final class NettyServerSingletons {
builder builder
.addContextCustomizer( .addContextCustomizer(
(context, requestAndChannel, startAttributes) -> NettyErrorHolder.init(context)) (context, requestAndChannel, startAttributes) -> NettyErrorHolder.init(context))
.addContextCustomizer(HttpServerRoute.create(httpServerAttributesGetter)) .addContextCustomizer(
HttpServerRoute.builder(httpServerAttributesGetter)
.setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods())
.build())
.buildServerInstrumenter(NettyHeadersGetter.INSTANCE); .buildServerInstrumenter(NettyHeadersGetter.INSTANCE);
} }

View File

@ -14,6 +14,7 @@ import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttribut
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerExperimentalMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerExperimentalMetrics;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerMetrics;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerRoute; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerRoute;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerRouteBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractorBuilder; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractorBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor;
@ -33,6 +34,7 @@ public final class NettyServerInstrumenterFactory {
Consumer<HttpServerAttributesExtractorBuilder<HttpRequestAndChannel, HttpResponse>> Consumer<HttpServerAttributesExtractorBuilder<HttpRequestAndChannel, HttpResponse>>
extractorConfigurer, extractorConfigurer,
Consumer<HttpSpanNameExtractorBuilder<HttpRequestAndChannel>> spanNameExtractorConfigurer, Consumer<HttpSpanNameExtractorBuilder<HttpRequestAndChannel>> spanNameExtractorConfigurer,
Consumer<HttpServerRouteBuilder<HttpRequestAndChannel>> httpServerRouteConfigurer,
boolean emitExperimentalHttpServerMetrics) { boolean emitExperimentalHttpServerMetrics) {
NettyHttpServerAttributesGetter httpAttributesGetter = new NettyHttpServerAttributesGetter(); NettyHttpServerAttributesGetter httpAttributesGetter = new NettyHttpServerAttributesGetter();
@ -55,9 +57,13 @@ public final class NettyServerInstrumenterFactory {
builder.addOperationMetrics(HttpServerExperimentalMetrics.get()); builder.addOperationMetrics(HttpServerExperimentalMetrics.get());
} }
HttpServerRouteBuilder<HttpRequestAndChannel> httpServerRouteBuilder =
HttpServerRoute.builder(httpAttributesGetter);
httpServerRouteConfigurer.accept(httpServerRouteBuilder);
return builder return builder
.addContextCustomizer((context, request, attributes) -> NettyErrorHolder.init(context)) .addContextCustomizer((context, request, attributes) -> NettyErrorHolder.init(context))
.addContextCustomizer(HttpServerRoute.create(httpAttributesGetter)) .addContextCustomizer(httpServerRouteBuilder.build())
.buildServerInstrumenter(HttpRequestHeadersGetter.INSTANCE); .buildServerInstrumenter(HttpRequestHeadersGetter.INSTANCE);
} }

View File

@ -24,6 +24,7 @@ public final class NettyServerSingletons {
.setCapturedResponseHeaders(CommonConfig.get().getServerResponseHeaders()) .setCapturedResponseHeaders(CommonConfig.get().getServerResponseHeaders())
.setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()), .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()),
builder -> builder.setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()), builder -> builder.setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()),
builder -> builder.setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()),
CommonConfig.get().shouldEmitExperimentalHttpServerMetrics()); CommonConfig.get().shouldEmitExperimentalHttpServerMetrics());
public static Instrumenter<HttpRequestAndChannel, HttpResponse> instrumenter() { public static Instrumenter<HttpRequestAndChannel, HttpResponse> instrumenter() {

View File

@ -24,6 +24,7 @@ public final class NettyServerSingletons {
.setCapturedResponseHeaders(CommonConfig.get().getServerResponseHeaders()) .setCapturedResponseHeaders(CommonConfig.get().getServerResponseHeaders())
.setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()), .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()),
builder -> builder.setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()), builder -> builder.setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()),
builder -> builder.setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()),
CommonConfig.get().shouldEmitExperimentalHttpServerMetrics()); CommonConfig.get().shouldEmitExperimentalHttpServerMetrics());
public static Instrumenter<HttpRequestAndChannel, HttpResponse> instrumenter() { public static Instrumenter<HttpRequestAndChannel, HttpResponse> instrumenter() {

View File

@ -9,6 +9,7 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue;
import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpResponse;
import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttributesExtractorBuilder; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttributesExtractorBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerRouteBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractorBuilder; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractorBuilder;
import io.opentelemetry.instrumentation.netty.v4.common.HttpRequestAndChannel; import io.opentelemetry.instrumentation.netty.v4.common.HttpRequestAndChannel;
import io.opentelemetry.instrumentation.netty.v4.common.internal.server.NettyServerInstrumenterFactory; import io.opentelemetry.instrumentation.netty.v4.common.internal.server.NettyServerInstrumenterFactory;
@ -25,6 +26,8 @@ public final class NettyServerTelemetryBuilder {
extractorConfigurer = builder -> {}; extractorConfigurer = builder -> {};
private Consumer<HttpSpanNameExtractorBuilder<HttpRequestAndChannel>> private Consumer<HttpSpanNameExtractorBuilder<HttpRequestAndChannel>>
spanNameExtractorConfigurer = builder -> {}; spanNameExtractorConfigurer = builder -> {};
private Consumer<HttpServerRouteBuilder<HttpRequestAndChannel>> httpServerRouteConfigurer =
builder -> {};
private boolean emitExperimentalHttpServerMetrics = false; private boolean emitExperimentalHttpServerMetrics = false;
NettyServerTelemetryBuilder(OpenTelemetry openTelemetry) { NettyServerTelemetryBuilder(OpenTelemetry openTelemetry) {
@ -78,6 +81,8 @@ public final class NettyServerTelemetryBuilder {
extractorConfigurer.andThen(builder -> builder.setKnownMethods(knownMethods)); extractorConfigurer.andThen(builder -> builder.setKnownMethods(knownMethods));
spanNameExtractorConfigurer = spanNameExtractorConfigurer =
spanNameExtractorConfigurer.andThen(builder -> builder.setKnownMethods(knownMethods)); spanNameExtractorConfigurer.andThen(builder -> builder.setKnownMethods(knownMethods));
httpServerRouteConfigurer =
httpServerRouteConfigurer.andThen(builder -> builder.setKnownMethods(knownMethods));
return this; return this;
} }
@ -102,6 +107,7 @@ public final class NettyServerTelemetryBuilder {
"io.opentelemetry.netty-4.1", "io.opentelemetry.netty-4.1",
extractorConfigurer, extractorConfigurer,
spanNameExtractorConfigurer, spanNameExtractorConfigurer,
httpServerRouteConfigurer,
emitExperimentalHttpServerMetrics)); emitExperimentalHttpServerMetrics));
} }
} }

View File

@ -115,4 +115,9 @@ class PlayServerTest extends HttpServerTest<Server> implements AgentTestTrait {
attributes.remove(SemanticAttributes.HTTP_ROUTE) attributes.remove(SemanticAttributes.HTTP_ROUTE)
attributes attributes
} }
@Override
int getResponseCodeOnNonStandardHttpMethod() {
404
}
} }

View File

@ -105,4 +105,10 @@ class PlayServerTest extends HttpServerTest<Server> implements AgentTestTrait {
Set<AttributeKey<?>> httpAttributes(ServerEndpoint endpoint) { Set<AttributeKey<?>> httpAttributes(ServerEndpoint endpoint) {
[] []
} }
@Override
boolean testNonStandardHttpMethod() {
// instrumentation does not create a server span at all
false
}
} }

View File

@ -153,7 +153,7 @@ abstract class AbstractRatpackHttpServerTest extends HttpServerTest<RatpackServe
} }
@Override @Override
String expectedHttpRoute(ServerEndpoint endpoint) { String expectedHttpRoute(ServerEndpoint endpoint, String method) {
return endpoint.status == 404 ? "/" : endpoint == PATH_PARAM ? "/path/:id/param" : endpoint.path return endpoint.status == 404 ? "/" : endpoint == PATH_PARAM ? "/path/:id/param" : endpoint.path
} }
} }

View File

@ -19,6 +19,7 @@ import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttribut
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerExperimentalMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerExperimentalMetrics;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerMetrics;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerRoute; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerRoute;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerRouteBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractorBuilder; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractorBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor;
@ -51,6 +52,9 @@ public final class RatpackTelemetryBuilder {
private final HttpSpanNameExtractorBuilder<Request> httpServerSpanNameExtractorBuilder = private final HttpSpanNameExtractorBuilder<Request> httpServerSpanNameExtractorBuilder =
HttpSpanNameExtractor.builder(RatpackHttpAttributesGetter.INSTANCE); HttpSpanNameExtractor.builder(RatpackHttpAttributesGetter.INSTANCE);
private final HttpServerRouteBuilder<Request> httpServerRouteBuilder =
HttpServerRoute.builder(RatpackHttpAttributesGetter.INSTANCE);
private final List<AttributesExtractor<? super RequestSpec, ? super HttpResponse>> private final List<AttributesExtractor<? super RequestSpec, ? super HttpResponse>>
additionalHttpClientExtractors = new ArrayList<>(); additionalHttpClientExtractors = new ArrayList<>();
@ -143,6 +147,7 @@ public final class RatpackTelemetryBuilder {
httpServerAttributesExtractorBuilder.setKnownMethods(knownMethods); httpServerAttributesExtractorBuilder.setKnownMethods(knownMethods);
httpClientSpanNameExtractorBuilder.setKnownMethods(knownMethods); httpClientSpanNameExtractorBuilder.setKnownMethods(knownMethods);
httpServerSpanNameExtractorBuilder.setKnownMethods(knownMethods); httpServerSpanNameExtractorBuilder.setKnownMethods(knownMethods);
httpServerRouteBuilder.setKnownMethods(knownMethods);
return this; return this;
} }
@ -187,7 +192,7 @@ public final class RatpackTelemetryBuilder {
.addAttributesExtractor(httpServerAttributesExtractorBuilder.build()) .addAttributesExtractor(httpServerAttributesExtractorBuilder.build())
.addAttributesExtractors(additionalExtractors) .addAttributesExtractors(additionalExtractors)
.addOperationMetrics(HttpServerMetrics.get()) .addOperationMetrics(HttpServerMetrics.get())
.addContextCustomizer(HttpServerRoute.create(httpAttributes)); .addContextCustomizer(httpServerRouteBuilder.build());
if (emitExperimentalHttpServerMetrics) { if (emitExperimentalHttpServerMetrics) {
builder.addOperationMetrics(HttpServerExperimentalMetrics.get()); builder.addOperationMetrics(HttpServerExperimentalMetrics.get());
} }

View File

@ -14,12 +14,12 @@ import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint
class RestletServerTest extends AbstractRestletServerTest implements AgentTestTrait { class RestletServerTest extends AbstractRestletServerTest implements AgentTestTrait {
@Override @Override
String expectedHttpRoute(ServerEndpoint endpoint) { String expectedHttpRoute(ServerEndpoint endpoint, String method) {
switch (endpoint) { switch (endpoint) {
case NOT_FOUND: case NOT_FOUND:
return getContextPath() + "/" return getContextPath() + "/"
default: default:
return super.expectedHttpRoute(endpoint) return super.expectedHttpRoute(endpoint, method)
} }
} }

View File

@ -15,6 +15,7 @@ import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttribut
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerExperimentalMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerExperimentalMetrics;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerMetrics;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerRoute; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerRoute;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerRouteBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractorBuilder; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractorBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor;
@ -37,6 +38,8 @@ public final class RestletTelemetryBuilder {
HttpServerAttributesExtractor.builder(RestletHttpAttributesGetter.INSTANCE); HttpServerAttributesExtractor.builder(RestletHttpAttributesGetter.INSTANCE);
private final HttpSpanNameExtractorBuilder<Request> httpSpanNameExtractorBuilder = private final HttpSpanNameExtractorBuilder<Request> httpSpanNameExtractorBuilder =
HttpSpanNameExtractor.builder(RestletHttpAttributesGetter.INSTANCE); HttpSpanNameExtractor.builder(RestletHttpAttributesGetter.INSTANCE);
private final HttpServerRouteBuilder<Request> httpServerRouteBuilder =
HttpServerRoute.builder(RestletHttpAttributesGetter.INSTANCE);
private boolean emitExperimentalHttpServerMetrics = false; private boolean emitExperimentalHttpServerMetrics = false;
RestletTelemetryBuilder(OpenTelemetry openTelemetry) { RestletTelemetryBuilder(OpenTelemetry openTelemetry) {
@ -93,6 +96,7 @@ public final class RestletTelemetryBuilder {
public RestletTelemetryBuilder setKnownMethods(Set<String> knownMethods) { public RestletTelemetryBuilder setKnownMethods(Set<String> knownMethods) {
httpAttributesExtractorBuilder.setKnownMethods(knownMethods); httpAttributesExtractorBuilder.setKnownMethods(knownMethods);
httpSpanNameExtractorBuilder.setKnownMethods(knownMethods); httpSpanNameExtractorBuilder.setKnownMethods(knownMethods);
httpServerRouteBuilder.setKnownMethods(knownMethods);
return this; return this;
} }
@ -122,7 +126,7 @@ public final class RestletTelemetryBuilder {
.setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter)) .setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter))
.addAttributesExtractor(httpAttributesExtractorBuilder.build()) .addAttributesExtractor(httpAttributesExtractorBuilder.build())
.addAttributesExtractors(additionalExtractors) .addAttributesExtractors(additionalExtractors)
.addContextCustomizer(HttpServerRoute.create(httpAttributesGetter)) .addContextCustomizer(httpServerRouteBuilder.build())
.addOperationMetrics(HttpServerMetrics.get()); .addOperationMetrics(HttpServerMetrics.get());
if (emitExperimentalHttpServerMetrics) { if (emitExperimentalHttpServerMetrics) {
builder.addOperationMetrics(HttpServerExperimentalMetrics.get()); builder.addOperationMetrics(HttpServerExperimentalMetrics.get());

View File

@ -168,14 +168,14 @@ abstract class AbstractRestletServerTest extends HttpServerTest<Server> {
} }
@Override @Override
String expectedHttpRoute(ServerEndpoint endpoint) { String expectedHttpRoute(ServerEndpoint endpoint, String method) {
switch (endpoint) { switch (endpoint) {
case PATH_PARAM: case PATH_PARAM:
return getContextPath() + "/path/{id}/param" return getContextPath() + "/path/{id}/param"
case NOT_FOUND: case NOT_FOUND:
return getContextPath() + "/*" return getContextPath() + "/*"
default: default:
return super.expectedHttpRoute(endpoint) return super.expectedHttpRoute(endpoint, method)
} }
} }

View File

@ -63,14 +63,14 @@ abstract class AbstractServletServerTest extends HttpServerTest<Server> {
} }
@Override @Override
String expectedHttpRoute(ServerEndpoint endpoint) { String expectedHttpRoute(ServerEndpoint endpoint, String method) {
switch (endpoint) { switch (endpoint) {
case PATH_PARAM: case PATH_PARAM:
return getContextPath() + "/path/{id}/param" return getContextPath() + "/path/{id}/param"
case NOT_FOUND: case NOT_FOUND:
return getContextPath() + "/*" return getContextPath() + "/*"
default: default:
return super.expectedHttpRoute(endpoint) return super.expectedHttpRoute(endpoint, method)
} }
} }

View File

@ -8,6 +8,7 @@ package io.opentelemetry.javaagent.instrumentation.restlet.v2_0;
import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerRoute;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerRouteGetter; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerRouteGetter;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor;
import io.opentelemetry.instrumentation.restlet.v2_0.internal.RestletHttpAttributesGetter; import io.opentelemetry.instrumentation.restlet.v2_0.internal.RestletHttpAttributesGetter;
@ -31,6 +32,9 @@ public final class RestletSingletons {
HttpSpanNameExtractor.builder(RestletHttpAttributesGetter.INSTANCE) HttpSpanNameExtractor.builder(RestletHttpAttributesGetter.INSTANCE)
.setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()) .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods())
.build(), .build(),
HttpServerRoute.builder(RestletHttpAttributesGetter.INSTANCE)
.setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods())
.build(),
Collections.emptyList(), Collections.emptyList(),
CommonConfig.get().shouldEmitExperimentalHttpServerMetrics()); CommonConfig.get().shouldEmitExperimentalHttpServerMetrics());

View File

@ -14,12 +14,12 @@ import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint
class RestletServerTest extends AbstractRestletServerTest implements AgentTestTrait { class RestletServerTest extends AbstractRestletServerTest implements AgentTestTrait {
@Override @Override
String expectedHttpRoute(ServerEndpoint endpoint) { String expectedHttpRoute(ServerEndpoint endpoint, String method) {
switch (endpoint) { switch (endpoint) {
case NOT_FOUND: case NOT_FOUND:
return getContextPath() + "/" return getContextPath() + "/"
default: default:
return super.expectedHttpRoute(endpoint) return super.expectedHttpRoute(endpoint, method)
} }
} }

View File

@ -11,6 +11,8 @@ import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttributesExtractorBuilder; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttributesExtractorBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerRoute;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerRouteBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractorBuilder; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractorBuilder;
import io.opentelemetry.instrumentation.restlet.v2_0.internal.RestletHttpAttributesGetter; import io.opentelemetry.instrumentation.restlet.v2_0.internal.RestletHttpAttributesGetter;
@ -32,6 +34,9 @@ public final class RestletTelemetryBuilder {
HttpServerAttributesExtractor.builder(RestletHttpAttributesGetter.INSTANCE); HttpServerAttributesExtractor.builder(RestletHttpAttributesGetter.INSTANCE);
private final HttpSpanNameExtractorBuilder<Request> httpSpanNameExtractorBuilder = private final HttpSpanNameExtractorBuilder<Request> httpSpanNameExtractorBuilder =
HttpSpanNameExtractor.builder(RestletHttpAttributesGetter.INSTANCE); HttpSpanNameExtractor.builder(RestletHttpAttributesGetter.INSTANCE);
private final HttpServerRouteBuilder<Request> httpServerRouteBuilder =
HttpServerRoute.builder(RestletHttpAttributesGetter.INSTANCE);
private boolean emitExperimentalHttpServerMetrics = false; private boolean emitExperimentalHttpServerMetrics = false;
RestletTelemetryBuilder(OpenTelemetry openTelemetry) { RestletTelemetryBuilder(OpenTelemetry openTelemetry) {
@ -88,6 +93,7 @@ public final class RestletTelemetryBuilder {
public RestletTelemetryBuilder setKnownMethods(Set<String> knownMethods) { public RestletTelemetryBuilder setKnownMethods(Set<String> knownMethods) {
httpAttributesExtractorBuilder.setKnownMethods(knownMethods); httpAttributesExtractorBuilder.setKnownMethods(knownMethods);
httpSpanNameExtractorBuilder.setKnownMethods(knownMethods); httpSpanNameExtractorBuilder.setKnownMethods(knownMethods);
httpServerRouteBuilder.setKnownMethods(knownMethods);
return this; return this;
} }
@ -114,6 +120,7 @@ public final class RestletTelemetryBuilder {
openTelemetry, openTelemetry,
httpAttributesExtractorBuilder.build(), httpAttributesExtractorBuilder.build(),
httpSpanNameExtractorBuilder.build(), httpSpanNameExtractorBuilder.build(),
httpServerRouteBuilder.build(),
additionalExtractors, additionalExtractors,
emitExperimentalHttpServerMetrics); emitExperimentalHttpServerMetrics);

View File

@ -7,12 +7,12 @@ package io.opentelemetry.instrumentation.restlet.v2_0.internal;
import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.ContextCustomizer;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerExperimentalMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerExperimentalMetrics;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerMetrics;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerRoute;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor;
import java.util.List; import java.util.List;
import org.restlet.Request; import org.restlet.Request;
@ -30,6 +30,7 @@ public final class RestletInstrumenterFactory {
OpenTelemetry openTelemetry, OpenTelemetry openTelemetry,
AttributesExtractor<Request, Response> httpServerAttributesExtractor, AttributesExtractor<Request, Response> httpServerAttributesExtractor,
SpanNameExtractor<Request> httpServerSpanNameExtractor, SpanNameExtractor<Request> httpServerSpanNameExtractor,
ContextCustomizer<Request> httpServerRoute,
List<AttributesExtractor<Request, Response>> additionalExtractors, List<AttributesExtractor<Request, Response>> additionalExtractors,
boolean emitExperimentalHttpServerMetrics) { boolean emitExperimentalHttpServerMetrics) {
@ -41,7 +42,7 @@ public final class RestletInstrumenterFactory {
.setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter)) .setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter))
.addAttributesExtractor(httpServerAttributesExtractor) .addAttributesExtractor(httpServerAttributesExtractor)
.addAttributesExtractors(additionalExtractors) .addAttributesExtractors(additionalExtractors)
.addContextCustomizer(HttpServerRoute.create(httpAttributesGetter)) .addContextCustomizer(httpServerRoute)
.addOperationMetrics(HttpServerMetrics.get()); .addOperationMetrics(HttpServerMetrics.get());
if (emitExperimentalHttpServerMetrics) { if (emitExperimentalHttpServerMetrics) {
builder.addOperationMetrics(HttpServerExperimentalMetrics.get()); builder.addOperationMetrics(HttpServerExperimentalMetrics.get());

View File

@ -176,14 +176,14 @@ abstract class AbstractRestletServerTest extends HttpServerTest<Server> {
} }
@Override @Override
String expectedHttpRoute(ServerEndpoint endpoint) { String expectedHttpRoute(ServerEndpoint endpoint, String method) {
switch (endpoint) { switch (endpoint) {
case PATH_PARAM: case PATH_PARAM:
return getContextPath() + "/path/{id}/param" return getContextPath() + "/path/{id}/param"
case NOT_FOUND: case NOT_FOUND:
return getContextPath() + "/*" return getContextPath() + "/*"
default: default:
return super.expectedHttpRoute(endpoint) return super.expectedHttpRoute(endpoint, method)
} }
} }

View File

@ -6,12 +6,16 @@
package io.opentelemetry.javaagent.instrumentation.servlet.v2_2; package io.opentelemetry.javaagent.instrumentation.servlet.v2_2;
import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor;
import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig;
import io.opentelemetry.javaagent.instrumentation.servlet.ServletAccessor; import io.opentelemetry.javaagent.instrumentation.servlet.ServletAccessor;
import io.opentelemetry.javaagent.instrumentation.servlet.ServletRequestContext; import io.opentelemetry.javaagent.instrumentation.servlet.ServletRequestContext;
import java.util.Set;
public class Servlet2SpanNameExtractor<REQUEST, RESPONSE> public class Servlet2SpanNameExtractor<REQUEST, RESPONSE>
implements SpanNameExtractor<ServletRequestContext<REQUEST>> { implements SpanNameExtractor<ServletRequestContext<REQUEST>> {
private final ServletAccessor<REQUEST, RESPONSE> accessor; private final ServletAccessor<REQUEST, RESPONSE> accessor;
private final Set<String> knownMethods = CommonConfig.get().getKnownHttpRequestMethods();
public Servlet2SpanNameExtractor(ServletAccessor<REQUEST, RESPONSE> accessor) { public Servlet2SpanNameExtractor(ServletAccessor<REQUEST, RESPONSE> accessor) {
this.accessor = accessor; this.accessor = accessor;
@ -22,7 +26,12 @@ public class Servlet2SpanNameExtractor<REQUEST, RESPONSE>
REQUEST request = requestContext.request(); REQUEST request = requestContext.request();
String method = accessor.getRequestMethod(request); String method = accessor.getRequestMethod(request);
String servletPath = accessor.getRequestServletPath(request); String servletPath = accessor.getRequestServletPath(request);
if (method != null) { if (method == null) {
return "HTTP";
}
if (!knownMethods.contains(method)) {
method = "HTTP";
}
if (servletPath.isEmpty()) { if (servletPath.isEmpty()) {
return method; return method;
} }
@ -32,6 +41,4 @@ public class Servlet2SpanNameExtractor<REQUEST, RESPONSE>
} }
return method + " " + contextPath + servletPath; return method + " " + contextPath + servletPath;
} }
return "HTTP request";
}
} }

View File

@ -4,6 +4,7 @@
*/ */
import io.opentelemetry.api.common.AttributeKey import io.opentelemetry.api.common.AttributeKey
import io.opentelemetry.instrumentation.api.internal.HttpConstants
import io.opentelemetry.instrumentation.test.AgentTestTrait import io.opentelemetry.instrumentation.test.AgentTestTrait
import io.opentelemetry.instrumentation.test.asserts.TraceAssert import io.opentelemetry.instrumentation.test.asserts.TraceAssert
import io.opentelemetry.instrumentation.test.base.HttpServerTest import io.opentelemetry.instrumentation.test.base.HttpServerTest
@ -83,6 +84,9 @@ class JettyServlet2Test extends HttpServerTest<Server> implements AgentTestTrait
@Override @Override
String expectedServerSpanName(ServerEndpoint endpoint, String method, @Nullable String route) { String expectedServerSpanName(ServerEndpoint endpoint, String method, @Nullable String route) {
if (method == HttpConstants._OTHER) {
return "HTTP " + endpoint.resolvePath(address).path
}
switch (endpoint) { switch (endpoint) {
case NOT_FOUND: case NOT_FOUND:
return method return method

View File

@ -4,11 +4,13 @@
*/ */
import io.opentelemetry.api.trace.SpanKind import io.opentelemetry.api.trace.SpanKind
import io.opentelemetry.instrumentation.api.internal.HttpConstants
import io.opentelemetry.instrumentation.test.AgentTestTrait import io.opentelemetry.instrumentation.test.AgentTestTrait
import io.opentelemetry.instrumentation.test.asserts.TraceAssert import io.opentelemetry.instrumentation.test.asserts.TraceAssert
import io.opentelemetry.instrumentation.test.base.HttpServerTest import io.opentelemetry.instrumentation.test.base.HttpServerTest
import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint
import io.opentelemetry.javaagent.bootstrap.servlet.ExperimentalSnippetHolder import io.opentelemetry.javaagent.bootstrap.servlet.ExperimentalSnippetHolder
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
import javax.servlet.Servlet import javax.servlet.Servlet
@ -82,12 +84,19 @@ abstract class AbstractServlet3Test<SERVER, CONTEXT> extends HttpServerTest<SERV
} }
@Override @Override
String expectedHttpRoute(ServerEndpoint endpoint) { String expectedHttpRoute(ServerEndpoint endpoint, String method) {
// no need to compute route if we're not expecting it
if (!httpAttributes(endpoint).contains(SemanticAttributes.HTTP_ROUTE)) {
return null
}
if (method == HttpConstants._OTHER) {
return endpoint.resolvePath(address).path
}
switch (endpoint) { switch (endpoint) {
case NOT_FOUND: case NOT_FOUND:
return getContextPath() + "/*" return getContextPath() + "/*"
default: default:
return super.expectedHttpRoute(endpoint) return super.expectedHttpRoute(endpoint, method)
} }
} }
@ -147,7 +156,7 @@ abstract class AbstractServlet3Test<SERVER, CONTEXT> extends HttpServerTest<SERV
cleanup: cleanup:
ExperimentalSnippetHolder.setSnippet("") ExperimentalSnippetHolder.setSnippet("")
def expectedRoute = expectedHttpRoute(HTML_SERVLET_OUTPUT_STREAM) def expectedRoute = expectedHttpRoute(HTML_SERVLET_OUTPUT_STREAM, "GET")
assertTraces(1) { assertTraces(1) {
trace(0, 2) { trace(0, 2) {
span(0) { span(0) {
@ -190,7 +199,7 @@ abstract class AbstractServlet3Test<SERVER, CONTEXT> extends HttpServerTest<SERV
cleanup: cleanup:
ExperimentalSnippetHolder.setSnippet("") ExperimentalSnippetHolder.setSnippet("")
def expectedRoute = expectedHttpRoute(HTML_PRINT_WRITER) def expectedRoute = expectedHttpRoute(HTML_PRINT_WRITER, "GET")
assertTraces(1) { assertTraces(1) {
trace(0, 2) { trace(0, 2) {
span(0) { span(0) {

View File

@ -150,7 +150,7 @@ abstract class TomcatServlet3Test extends AbstractServlet3Test<Tomcat, Context>
(0..count - 1).each { (0..count - 1).each {
trace(it, 2) { trace(it, 2) {
serverSpan(it, 0, null, null, "GET", ACCESS_LOG_SUCCESS.body.length(), ACCESS_LOG_SUCCESS) serverSpan(it, 0, null, null, "GET", ACCESS_LOG_SUCCESS)
controllerSpan(it, 1, span(0)) controllerSpan(it, 1, span(0))
} }
@ -182,7 +182,7 @@ abstract class TomcatServlet3Test extends AbstractServlet3Test<Tomcat, Context>
} }
assertTraces(1) { assertTraces(1) {
trace(0, spanCount) { trace(0, spanCount) {
serverSpan(it, 0, null, null, method, response.content().length(), ACCESS_LOG_ERROR) serverSpan(it, 0, null, null, method, ACCESS_LOG_ERROR)
def spanIndex = 1 def spanIndex = 1
controllerSpan(it, spanIndex, span(spanIndex - 1)) controllerSpan(it, spanIndex, span(spanIndex - 1))
spanIndex++ spanIndex++

View File

@ -4,11 +4,13 @@
*/ */
import io.opentelemetry.api.trace.SpanKind import io.opentelemetry.api.trace.SpanKind
import io.opentelemetry.instrumentation.api.internal.HttpConstants
import io.opentelemetry.instrumentation.test.AgentTestTrait import io.opentelemetry.instrumentation.test.AgentTestTrait
import io.opentelemetry.instrumentation.test.asserts.TraceAssert import io.opentelemetry.instrumentation.test.asserts.TraceAssert
import io.opentelemetry.instrumentation.test.base.HttpServerTest import io.opentelemetry.instrumentation.test.base.HttpServerTest
import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint
import io.opentelemetry.javaagent.bootstrap.servlet.ExperimentalSnippetHolder import io.opentelemetry.javaagent.bootstrap.servlet.ExperimentalSnippetHolder
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
import jakarta.servlet.Servlet import jakarta.servlet.Servlet
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_REQUIRED import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_REQUIRED
@ -91,12 +93,19 @@ abstract class AbstractServlet5Test<SERVER, CONTEXT> extends HttpServerTest<SERV
} }
@Override @Override
String expectedHttpRoute(ServerEndpoint endpoint) { String expectedHttpRoute(ServerEndpoint endpoint, String method) {
// no need to compute route if we're not expecting it
if (!httpAttributes(endpoint).contains(SemanticAttributes.HTTP_ROUTE)) {
return null
}
if (method == HttpConstants._OTHER) {
return endpoint.resolvePath(address).path
}
switch (endpoint) { switch (endpoint) {
case NOT_FOUND: case NOT_FOUND:
return getContextPath() + "/*" return getContextPath() + "/*"
default: default:
return super.expectedHttpRoute(endpoint) return super.expectedHttpRoute(endpoint, method)
} }
} }
@ -147,7 +156,7 @@ abstract class AbstractServlet5Test<SERVER, CONTEXT> extends HttpServerTest<SERV
cleanup: cleanup:
ExperimentalSnippetHolder.setSnippet("") ExperimentalSnippetHolder.setSnippet("")
def expectedRoute = expectedHttpRoute(HTML_SERVLET_OUTPUT_STREAM) def expectedRoute = expectedHttpRoute(HTML_SERVLET_OUTPUT_STREAM, "GET")
assertTraces(1) { assertTraces(1) {
trace(0, 2) { trace(0, 2) {
span(0) { span(0) {
@ -190,7 +199,7 @@ abstract class AbstractServlet5Test<SERVER, CONTEXT> extends HttpServerTest<SERV
cleanup: cleanup:
ExperimentalSnippetHolder.setSnippet("") ExperimentalSnippetHolder.setSnippet("")
def expectedRoute = expectedHttpRoute(HTML_PRINT_WRITER) def expectedRoute = expectedHttpRoute(HTML_PRINT_WRITER, "GET")
assertTraces(1) { assertTraces(1) {
trace(0, 2) { trace(0, 2) {
span(0) { span(0) {

View File

@ -150,7 +150,7 @@ abstract class TomcatServlet5Test extends AbstractServlet5Test<Tomcat, Context>
(0..count - 1).each { (0..count - 1).each {
trace(it, 2) { trace(it, 2) {
serverSpan(it, 0, null, null, "GET", ACCESS_LOG_SUCCESS.body.length(), ACCESS_LOG_SUCCESS) serverSpan(it, 0, null, null, "GET", ACCESS_LOG_SUCCESS)
controllerSpan(it, 1, span(0)) controllerSpan(it, 1, span(0))
} }
@ -182,7 +182,7 @@ abstract class TomcatServlet5Test extends AbstractServlet5Test<Tomcat, Context>
} }
assertTraces(1) { assertTraces(1) {
trace(0, spanCount) { trace(0, spanCount) {
serverSpan(it, 0, null, null, method, response.content().length(), ACCESS_LOG_ERROR) serverSpan(it, 0, null, null, method, ACCESS_LOG_ERROR)
def spanIndex = 1 def spanIndex = 1
controllerSpan(it, spanIndex, span(spanIndex - 1)) controllerSpan(it, spanIndex, span(spanIndex - 1))
spanIndex++ spanIndex++

View File

@ -66,7 +66,10 @@ public final class ServletInstrumenterBuilder<REQUEST, RESPONSE> {
.build()) .build())
.addAttributesExtractor(additionalAttributesExtractor) .addAttributesExtractor(additionalAttributesExtractor)
.addOperationMetrics(HttpServerMetrics.get()) .addOperationMetrics(HttpServerMetrics.get())
.addContextCustomizer(HttpServerRoute.create(httpAttributesGetter)); .addContextCustomizer(
HttpServerRoute.builder(httpAttributesGetter)
.setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods())
.build());
if (ServletRequestParametersExtractor.enabled()) { if (ServletRequestParametersExtractor.enabled()) {
AttributesExtractor<ServletRequestContext<REQUEST>, ServletResponseContext<RESPONSE>> AttributesExtractor<ServletRequestContext<REQUEST>, ServletResponseContext<RESPONSE>>
requestParametersExtractor = new ServletRequestParametersExtractor<>(accessor); requestParametersExtractor = new ServletRequestParametersExtractor<>(accessor);

View File

@ -72,6 +72,6 @@ public class ImmediateHandlerSpringWebFluxServerTest extends HandlerSpringWebFlu
assertThat(response.contentUtf8()).isEqualTo(NESTED_PATH.getBody()); assertThat(response.contentUtf8()).isEqualTo(NESTED_PATH.getBody());
assertResponseHasCustomizedHeaders(response, NESTED_PATH, null); assertResponseHasCustomizedHeaders(response, NESTED_PATH, null);
assertTheTraces(1, null, null, null, method, NESTED_PATH, response); assertTheTraces(1, null, null, null, method, NESTED_PATH);
} }
} }

View File

@ -49,7 +49,7 @@ public abstract class SpringWebFluxServerTest
} }
@Override @Override
public String expectedHttpRoute(ServerEndpoint endpoint) { public String expectedHttpRoute(ServerEndpoint endpoint, String method) {
if (endpoint.equals(PATH_PARAM)) { if (endpoint.equals(PATH_PARAM)) {
return getContextPath() + "/path/{id}/param"; return getContextPath() + "/path/{id}/param";
} else if (endpoint.equals(NOT_FOUND)) { } else if (endpoint.equals(NOT_FOUND)) {
@ -57,7 +57,7 @@ public abstract class SpringWebFluxServerTest
} else if (endpoint.equals(NESTED_PATH)) { } else if (endpoint.equals(NESTED_PATH)) {
return "/nestedPath/hello/world"; return "/nestedPath/hello/world";
} }
return super.expectedHttpRoute(endpoint); return super.expectedHttpRoute(endpoint, method);
} }
@Override @Override

View File

@ -16,6 +16,7 @@ import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttribut
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerExperimentalMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerExperimentalMetrics;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerMetrics;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerRoute; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerRoute;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerRouteBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractorBuilder; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractorBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor;
@ -44,6 +45,8 @@ public final class SpringWebfluxTelemetryBuilder {
HttpServerAttributesExtractor.builder(WebfluxServerHttpAttributesGetter.INSTANCE); HttpServerAttributesExtractor.builder(WebfluxServerHttpAttributesGetter.INSTANCE);
private final HttpSpanNameExtractorBuilder<ServerWebExchange> httpServerSpanNameExtractorBuilder = private final HttpSpanNameExtractorBuilder<ServerWebExchange> httpServerSpanNameExtractorBuilder =
HttpSpanNameExtractor.builder(WebfluxServerHttpAttributesGetter.INSTANCE); HttpSpanNameExtractor.builder(WebfluxServerHttpAttributesGetter.INSTANCE);
private final HttpServerRouteBuilder<ServerWebExchange> httpServerRouteBuilder =
HttpServerRoute.builder(WebfluxServerHttpAttributesGetter.INSTANCE);
private Consumer<HttpClientAttributesExtractorBuilder<ClientRequest, ClientResponse>> private Consumer<HttpClientAttributesExtractorBuilder<ClientRequest, ClientResponse>>
clientExtractorConfigurer = builder -> {}; clientExtractorConfigurer = builder -> {};
@ -167,6 +170,7 @@ public final class SpringWebfluxTelemetryBuilder {
clientSpanNameExtractorConfigurer.andThen(builder -> builder.setKnownMethods(knownMethods)); clientSpanNameExtractorConfigurer.andThen(builder -> builder.setKnownMethods(knownMethods));
httpServerAttributesExtractorBuilder.setKnownMethods(knownMethods); httpServerAttributesExtractorBuilder.setKnownMethods(knownMethods);
httpServerSpanNameExtractorBuilder.setKnownMethods(knownMethods); httpServerSpanNameExtractorBuilder.setKnownMethods(knownMethods);
httpServerRouteBuilder.setKnownMethods(knownMethods);
return this; return this;
} }
@ -227,7 +231,7 @@ public final class SpringWebfluxTelemetryBuilder {
.setSpanStatusExtractor(HttpSpanStatusExtractor.create(getter)) .setSpanStatusExtractor(HttpSpanStatusExtractor.create(getter))
.addAttributesExtractor(httpServerAttributesExtractorBuilder.build()) .addAttributesExtractor(httpServerAttributesExtractorBuilder.build())
.addAttributesExtractors(serverAdditionalExtractors) .addAttributesExtractors(serverAdditionalExtractors)
.addContextCustomizer(HttpServerRoute.create(getter)) .addContextCustomizer(httpServerRouteBuilder.build())
.addOperationMetrics(HttpServerMetrics.get()); .addOperationMetrics(HttpServerMetrics.get());
if (emitExperimentalHttpServerMetrics) { if (emitExperimentalHttpServerMetrics) {
builder.addOperationMetrics(HttpServerExperimentalMetrics.get()); builder.addOperationMetrics(HttpServerExperimentalMetrics.get());

View File

@ -38,11 +38,11 @@ public final class SpringWebfluxServerInstrumentationTest
options.setExpectedException(new RuntimeException(ServerEndpoint.EXCEPTION.getBody())); options.setExpectedException(new RuntimeException(ServerEndpoint.EXCEPTION.getBody()));
options.setExpectedHttpRoute( options.setExpectedHttpRoute(
endpoint -> { (endpoint, method) -> {
if (endpoint == ServerEndpoint.PATH_PARAM) { if (endpoint == ServerEndpoint.PATH_PARAM) {
return CONTEXT_PATH + "/path/{id}/param"; return CONTEXT_PATH + "/path/{id}/param";
} }
return expectedHttpRoute(endpoint); return expectedHttpRoute(endpoint, method);
}); });
} }
} }

View File

@ -16,6 +16,7 @@ import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttribut
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerExperimentalMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerExperimentalMetrics;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerMetrics;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerRoute; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerRoute;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerRouteBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractorBuilder; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractorBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor;
@ -40,6 +41,8 @@ public final class SpringWebMvcTelemetryBuilder {
HttpServerAttributesExtractor.builder(SpringWebMvcHttpAttributesGetter.INSTANCE); HttpServerAttributesExtractor.builder(SpringWebMvcHttpAttributesGetter.INSTANCE);
private final HttpSpanNameExtractorBuilder<HttpServletRequest> httpSpanNameExtractorBuilder = private final HttpSpanNameExtractorBuilder<HttpServletRequest> httpSpanNameExtractorBuilder =
HttpSpanNameExtractor.builder(SpringWebMvcHttpAttributesGetter.INSTANCE); HttpSpanNameExtractor.builder(SpringWebMvcHttpAttributesGetter.INSTANCE);
private final HttpServerRouteBuilder<HttpServletRequest> httpServerRouteBuilder =
HttpServerRoute.builder(SpringWebMvcHttpAttributesGetter.INSTANCE);
@Nullable @Nullable
private Function< private Function<
@ -114,6 +117,7 @@ public final class SpringWebMvcTelemetryBuilder {
public SpringWebMvcTelemetryBuilder setKnownMethods(Set<String> knownMethods) { public SpringWebMvcTelemetryBuilder setKnownMethods(Set<String> knownMethods) {
httpAttributesExtractorBuilder.setKnownMethods(knownMethods); httpAttributesExtractorBuilder.setKnownMethods(knownMethods);
httpSpanNameExtractorBuilder.setKnownMethods(knownMethods); httpSpanNameExtractorBuilder.setKnownMethods(knownMethods);
httpServerRouteBuilder.setKnownMethods(knownMethods);
return this; return this;
} }
@ -151,7 +155,7 @@ public final class SpringWebMvcTelemetryBuilder {
.setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter)) .setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter))
.addAttributesExtractor(httpAttributesExtractorBuilder.build()) .addAttributesExtractor(httpAttributesExtractorBuilder.build())
.addAttributesExtractors(additionalExtractors) .addAttributesExtractors(additionalExtractors)
.addContextCustomizer(HttpServerRoute.create(httpAttributesGetter)) .addContextCustomizer(httpServerRouteBuilder.build())
.addOperationMetrics(HttpServerMetrics.get()); .addOperationMetrics(HttpServerMetrics.get());
if (emitExperimentalHttpServerMetrics) { if (emitExperimentalHttpServerMetrics) {
builder.addOperationMetrics(HttpServerExperimentalMetrics.get()); builder.addOperationMetrics(HttpServerExperimentalMetrics.get());

View File

@ -38,11 +38,11 @@ class WebMvcHttpServerTest extends AbstractHttpServerTest<ConfigurableApplicatio
options.setTestException(false); options.setTestException(false);
options.setExpectedHttpRoute( options.setExpectedHttpRoute(
endpoint -> { (endpoint, method) -> {
if (endpoint == ServerEndpoint.PATH_PARAM) { if (endpoint == ServerEndpoint.PATH_PARAM) {
return CONTEXT_PATH + "/path/{id}/param"; return CONTEXT_PATH + "/path/{id}/param";
} }
return expectedHttpRoute(endpoint); return expectedHttpRoute(endpoint, method);
}); });
} }
} }

View File

@ -16,6 +16,7 @@ import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttribut
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerExperimentalMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerExperimentalMetrics;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerMetrics; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerMetrics;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerRoute; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerRoute;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerRouteBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractorBuilder; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractorBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor;
@ -40,6 +41,8 @@ public final class SpringWebMvcTelemetryBuilder {
HttpServerAttributesExtractor.builder(SpringWebMvcHttpAttributesGetter.INSTANCE); HttpServerAttributesExtractor.builder(SpringWebMvcHttpAttributesGetter.INSTANCE);
private final HttpSpanNameExtractorBuilder<HttpServletRequest> httpSpanNameExtractorBuilder = private final HttpSpanNameExtractorBuilder<HttpServletRequest> httpSpanNameExtractorBuilder =
HttpSpanNameExtractor.builder(SpringWebMvcHttpAttributesGetter.INSTANCE); HttpSpanNameExtractor.builder(SpringWebMvcHttpAttributesGetter.INSTANCE);
private final HttpServerRouteBuilder<HttpServletRequest> httpServerRouteBuilder =
HttpServerRoute.builder(SpringWebMvcHttpAttributesGetter.INSTANCE);
@Nullable @Nullable
private Function< private Function<
@ -114,6 +117,7 @@ public final class SpringWebMvcTelemetryBuilder {
public SpringWebMvcTelemetryBuilder setKnownMethods(Set<String> knownMethods) { public SpringWebMvcTelemetryBuilder setKnownMethods(Set<String> knownMethods) {
httpAttributesExtractorBuilder.setKnownMethods(knownMethods); httpAttributesExtractorBuilder.setKnownMethods(knownMethods);
httpSpanNameExtractorBuilder.setKnownMethods(knownMethods); httpSpanNameExtractorBuilder.setKnownMethods(knownMethods);
httpServerRouteBuilder.setKnownMethods(knownMethods);
return this; return this;
} }
@ -151,7 +155,7 @@ public final class SpringWebMvcTelemetryBuilder {
.setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter)) .setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter))
.addAttributesExtractor(httpAttributesExtractorBuilder.build()) .addAttributesExtractor(httpAttributesExtractorBuilder.build())
.addAttributesExtractors(additionalExtractors) .addAttributesExtractors(additionalExtractors)
.addContextCustomizer(HttpServerRoute.create(httpAttributesGetter)) .addContextCustomizer(httpServerRouteBuilder.build())
.addOperationMetrics(HttpServerMetrics.get()); .addOperationMetrics(HttpServerMetrics.get());
if (emitExperimentalHttpServerMetrics) { if (emitExperimentalHttpServerMetrics) {
builder.addOperationMetrics(HttpServerExperimentalMetrics.get()); builder.addOperationMetrics(HttpServerExperimentalMetrics.get());

View File

@ -38,11 +38,11 @@ class WebMvcHttpServerTest extends AbstractHttpServerTest<ConfigurableApplicatio
options.setTestException(false); options.setTestException(false);
options.setExpectedHttpRoute( options.setExpectedHttpRoute(
endpoint -> { (endpoint, method) -> {
if (endpoint == ServerEndpoint.PATH_PARAM) { if (endpoint == ServerEndpoint.PATH_PARAM) {
return CONTEXT_PATH + "/path/{id}/param"; return CONTEXT_PATH + "/path/{id}/param";
} }
return expectedHttpRoute(endpoint); return expectedHttpRoute(endpoint, method);
}); });
} }
} }

View File

@ -85,7 +85,7 @@ abstract class AbstractSpringBootBasedTest extends HttpServerTest<ConfigurableAp
} }
@Override @Override
String expectedHttpRoute(ServerEndpoint endpoint) { String expectedHttpRoute(ServerEndpoint endpoint, String method) {
switch (endpoint) { switch (endpoint) {
case PATH_PARAM: case PATH_PARAM:
return getContextPath() + "/path/{id}/param" return getContextPath() + "/path/{id}/param"
@ -94,7 +94,7 @@ abstract class AbstractSpringBootBasedTest extends HttpServerTest<ConfigurableAp
case LOGIN: case LOGIN:
return getContextPath() + "/*" return getContextPath() + "/*"
default: default:
return super.expectedHttpRoute(endpoint) return super.expectedHttpRoute(endpoint, method)
} }
} }
@ -113,7 +113,7 @@ abstract class AbstractSpringBootBasedTest extends HttpServerTest<ConfigurableAp
and: and:
assertTraces(1) { assertTraces(1) {
trace(0, 3) { trace(0, 3) {
serverSpan(it, 0, null, null, "GET", null, AUTH_ERROR) serverSpan(it, 0, null, null, "GET", AUTH_ERROR)
sendErrorSpan(it, 1, span(0)) sendErrorSpan(it, 1, span(0))
errorPageSpans(it, 2, null) errorPageSpans(it, 2, null)
} }
@ -140,7 +140,7 @@ abstract class AbstractSpringBootBasedTest extends HttpServerTest<ConfigurableAp
and: and:
assertTraces(1) { assertTraces(1) {
trace(0, 2) { trace(0, 2) {
serverSpan(it, 0, null, null, "POST", response.contentUtf8().length(), LOGIN) serverSpan(it, 0, null, null, "POST", LOGIN)
redirectSpan(it, 1, span(0)) redirectSpan(it, 1, span(0))
} }
} }

View File

@ -83,14 +83,14 @@ abstract class AbstractServletFilterTest extends HttpServerTest<ConfigurableAppl
} }
@Override @Override
String expectedHttpRoute(ServerEndpoint endpoint) { String expectedHttpRoute(ServerEndpoint endpoint, String method) {
switch (endpoint) { switch (endpoint) {
case PATH_PARAM: case PATH_PARAM:
return getContextPath() + "/path/{id}/param" return getContextPath() + "/path/{id}/param"
case NOT_FOUND: case NOT_FOUND:
return getContextPath() + "/**" return getContextPath() + "/**"
default: default:
return super.expectedHttpRoute(endpoint) return super.expectedHttpRoute(endpoint, method)
} }
} }

View File

@ -65,14 +65,14 @@ class Struts2ActionSpanTest extends HttpServerTest<Server> implements AgentTestT
} }
} }
String expectedHttpRoute(ServerEndpoint endpoint) { String expectedHttpRoute(ServerEndpoint endpoint, String method) {
switch (endpoint) { switch (endpoint) {
case PATH_PARAM: case PATH_PARAM:
return getContextPath() + "/path/{id}/param" return getContextPath() + "/path/{id}/param"
case NOT_FOUND: case NOT_FOUND:
return getContextPath() + "/*" return getContextPath() + "/*"
default: default:
return super.expectedHttpRoute(endpoint) return super.expectedHttpRoute(endpoint, method)
} }
} }

View File

@ -5,7 +5,7 @@
package io.opentelemetry.javaagent.instrumentation.tomcat.v10_0 package io.opentelemetry.javaagent.instrumentation.tomcat.v10_0
import io.opentelemetry.instrumentation.api.internal.HttpConstants
import io.opentelemetry.instrumentation.test.AgentTestTrait import io.opentelemetry.instrumentation.test.AgentTestTrait
import io.opentelemetry.instrumentation.test.asserts.TraceAssert import io.opentelemetry.instrumentation.test.asserts.TraceAssert
import io.opentelemetry.instrumentation.test.base.HttpServerTest import io.opentelemetry.instrumentation.test.base.HttpServerTest
@ -107,12 +107,15 @@ class TomcatAsyncTest extends HttpServerTest<Tomcat> implements AgentTestTrait {
} }
@Override @Override
String expectedHttpRoute(ServerEndpoint endpoint) { String expectedHttpRoute(ServerEndpoint endpoint, String method) {
if (method == HttpConstants._OTHER) {
return getContextPath() + endpoint.path
}
switch (endpoint) { switch (endpoint) {
case NOT_FOUND: case NOT_FOUND:
return getContextPath() + "/*" return getContextPath() + "/*"
default: default:
return super.expectedHttpRoute(endpoint) return super.expectedHttpRoute(endpoint, method)
} }
} }

View File

@ -5,7 +5,7 @@
package io.opentelemetry.javaagent.instrumentation.tomcat.v10_0 package io.opentelemetry.javaagent.instrumentation.tomcat.v10_0
import io.opentelemetry.instrumentation.api.internal.HttpConstants
import io.opentelemetry.instrumentation.test.AgentTestTrait import io.opentelemetry.instrumentation.test.AgentTestTrait
import io.opentelemetry.instrumentation.test.asserts.TraceAssert import io.opentelemetry.instrumentation.test.asserts.TraceAssert
import io.opentelemetry.instrumentation.test.base.HttpServerTest import io.opentelemetry.instrumentation.test.base.HttpServerTest
@ -55,6 +55,14 @@ class TomcatHandlerTest extends HttpServerTest<Tomcat> implements AgentTestTrait
true true
} }
@Override
String expectedHttpRoute(ServerEndpoint endpoint, String method) {
if (method == HttpConstants._OTHER) {
return getContextPath() + endpoint.path
}
return super.expectedHttpRoute(endpoint, method)
}
@Override @Override
Tomcat startServer(int port) { Tomcat startServer(int port) {
Tomcat tomcat = new Tomcat() Tomcat tomcat = new Tomcat()

View File

@ -5,7 +5,7 @@
package io.opentelemetry.javaagent.instrumentation.tomcat.v7_0 package io.opentelemetry.javaagent.instrumentation.tomcat.v7_0
import io.opentelemetry.instrumentation.api.internal.HttpConstants
import io.opentelemetry.instrumentation.test.AgentTestTrait import io.opentelemetry.instrumentation.test.AgentTestTrait
import io.opentelemetry.instrumentation.test.asserts.TraceAssert import io.opentelemetry.instrumentation.test.asserts.TraceAssert
import io.opentelemetry.instrumentation.test.base.HttpServerTest import io.opentelemetry.instrumentation.test.base.HttpServerTest
@ -107,12 +107,15 @@ class TomcatAsyncTest extends HttpServerTest<Tomcat> implements AgentTestTrait {
} }
@Override @Override
String expectedHttpRoute(ServerEndpoint endpoint) { String expectedHttpRoute(ServerEndpoint endpoint, String method) {
if (method == HttpConstants._OTHER) {
return getContextPath() + endpoint.path
}
switch (endpoint) { switch (endpoint) {
case NOT_FOUND: case NOT_FOUND:
return getContextPath() + "/*" return getContextPath() + "/*"
default: default:
return super.expectedHttpRoute(endpoint) return super.expectedHttpRoute(endpoint, method)
} }
} }

View File

@ -5,6 +5,7 @@
package io.opentelemetry.javaagent.instrumentation.tomcat.v7_0 package io.opentelemetry.javaagent.instrumentation.tomcat.v7_0
import io.opentelemetry.instrumentation.api.internal.HttpConstants
import io.opentelemetry.instrumentation.test.AgentTestTrait import io.opentelemetry.instrumentation.test.AgentTestTrait
import io.opentelemetry.instrumentation.test.asserts.TraceAssert import io.opentelemetry.instrumentation.test.asserts.TraceAssert
import io.opentelemetry.instrumentation.test.base.HttpServerTest import io.opentelemetry.instrumentation.test.base.HttpServerTest
@ -54,6 +55,14 @@ class TomcatHandlerTest extends HttpServerTest<Tomcat> implements AgentTestTrait
true true
} }
@Override
String expectedHttpRoute(ServerEndpoint endpoint, String method) {
if (method == HttpConstants._OTHER) {
return getContextPath() + endpoint.path
}
return super.expectedHttpRoute(endpoint, method)
}
@Override @Override
Tomcat startServer(int port) { Tomcat startServer(int port) {
Tomcat tomcat = new Tomcat() Tomcat tomcat = new Tomcat()

View File

@ -44,7 +44,10 @@ public final class TomcatInstrumenterFactory {
.setCapturedResponseHeaders(CommonConfig.get().getServerResponseHeaders()) .setCapturedResponseHeaders(CommonConfig.get().getServerResponseHeaders())
.setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()) .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods())
.build()) .build())
.addContextCustomizer(HttpServerRoute.create(httpAttributesGetter)) .addContextCustomizer(
HttpServerRoute.builder(httpAttributesGetter)
.setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods())
.build())
.addContextCustomizer( .addContextCustomizer(
(context, request, attributes) -> (context, request, attributes) ->
new AppServerBridge.Builder() new AppServerBridge.Builder()

View File

@ -41,7 +41,10 @@ public final class UndertowSingletons {
.setCapturedResponseHeaders(CommonConfig.get().getServerResponseHeaders()) .setCapturedResponseHeaders(CommonConfig.get().getServerResponseHeaders())
.setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods()) .setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods())
.build()) .build())
.addContextCustomizer(HttpServerRoute.create(httpAttributesGetter)) .addContextCustomizer(
HttpServerRoute.builder(httpAttributesGetter)
.setKnownMethods(CommonConfig.get().getKnownHttpRequestMethods())
.build())
.addContextCustomizer( .addContextCustomizer(
(context, request, attributes) -> { (context, request, attributes) -> {
// span is ended when counter reaches 0, we start from 2 which accounts for the // span is ended when counter reaches 0, we start from 2 which accounts for the

View File

@ -9,6 +9,8 @@ import io.opentelemetry.api.common.AttributeKey
import io.opentelemetry.api.trace.Span import io.opentelemetry.api.trace.Span
import io.opentelemetry.api.trace.SpanId import io.opentelemetry.api.trace.SpanId
import io.opentelemetry.api.trace.SpanKind import io.opentelemetry.api.trace.SpanKind
import io.opentelemetry.instrumentation.api.internal.HttpConstants
import io.opentelemetry.instrumentation.api.internal.SemconvStability
import io.opentelemetry.instrumentation.test.InstrumentationSpecification import io.opentelemetry.instrumentation.test.InstrumentationSpecification
import io.opentelemetry.instrumentation.test.asserts.TraceAssert import io.opentelemetry.instrumentation.test.asserts.TraceAssert
import io.opentelemetry.instrumentation.testing.GlobalTraceUtil import io.opentelemetry.instrumentation.testing.GlobalTraceUtil
@ -19,7 +21,6 @@ import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions
import io.opentelemetry.sdk.trace.data.SpanData import io.opentelemetry.sdk.trace.data.SpanData
import io.opentelemetry.semconv.SemanticAttributes import io.opentelemetry.semconv.SemanticAttributes
import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpRequest import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpRequest
import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpResponse
import io.opentelemetry.testing.internal.armeria.common.HttpMethod import io.opentelemetry.testing.internal.armeria.common.HttpMethod
import spock.lang.Shared import spock.lang.Shared
import spock.lang.Unroll import spock.lang.Unroll
@ -49,15 +50,22 @@ abstract class HttpServerTest<SERVER> extends InstrumentationSpecification imple
} }
String expectedServerSpanName(ServerEndpoint endpoint, String method, @Nullable String route) { String expectedServerSpanName(ServerEndpoint endpoint, String method, @Nullable String route) {
if (method == HttpConstants._OTHER) {
method = "HTTP"
}
return route == null ? method : method + " " + route return route == null ? method : method + " " + route
} }
String expectedHttpRoute(ServerEndpoint endpoint) { String expectedHttpRoute(ServerEndpoint endpoint, String method) {
// no need to compute route if we're not expecting it // no need to compute route if we're not expecting it
if (!httpAttributes(endpoint).contains(SemanticAttributes.HTTP_ROUTE)) { if (!httpAttributes(endpoint).contains(SemanticAttributes.HTTP_ROUTE)) {
return null return null
} }
if (method == HttpConstants._OTHER) {
return null
}
switch (endpoint) { switch (endpoint) {
case NOT_FOUND: case NOT_FOUND:
return null return null
@ -96,6 +104,10 @@ abstract class HttpServerTest<SERVER> extends InstrumentationSpecification imple
1 1
} }
int getResponseCodeOnNonStandardHttpMethod() {
SUCCESS.status
}
boolean hasErrorPageSpans(ServerEndpoint endpoint) { boolean hasErrorPageSpans(ServerEndpoint endpoint) {
false false
} }
@ -148,6 +160,10 @@ abstract class HttpServerTest<SERVER> extends InstrumentationSpecification imple
true true
} }
boolean testNonStandardHttpMethod() {
true
}
boolean verifyServerSpanEndTime() { boolean verifyServerSpanEndTime() {
return true return true
} }
@ -199,8 +215,8 @@ abstract class HttpServerTest<SERVER> extends InstrumentationSpecification imple
options.expectedServerSpanNameMapper = { endpoint, method, route -> options.expectedServerSpanNameMapper = { endpoint, method, route ->
HttpServerTest.this.expectedServerSpanName(endpoint, method, route) HttpServerTest.this.expectedServerSpanName(endpoint, method, route)
} }
options.expectedHttpRoute = { endpoint -> options.expectedHttpRoute = { endpoint, method ->
HttpServerTest.this.expectedHttpRoute(endpoint) HttpServerTest.this.expectedHttpRoute(endpoint, method)
} }
options.contextPath = getContextPath() options.contextPath = getContextPath()
options.httpAttributes = { endpoint -> options.httpAttributes = { endpoint ->
@ -219,6 +235,7 @@ abstract class HttpServerTest<SERVER> extends InstrumentationSpecification imple
options.metricsInstrumentationName = { options.metricsInstrumentationName = {
HttpServerTest.this.getMetricsInstrumentationName() HttpServerTest.this.getMetricsInstrumentationName()
} }
options.responseCodeOnNonStandardHttpMethod = getResponseCodeOnNonStandardHttpMethod()
options.testRedirect = testRedirect() options.testRedirect = testRedirect()
options.testError = testError() options.testError = testError()
@ -229,6 +246,9 @@ abstract class HttpServerTest<SERVER> extends InstrumentationSpecification imple
options.testCaptureHttpHeaders = testCapturedHttpHeaders() options.testCaptureHttpHeaders = testCapturedHttpHeaders()
options.testCaptureRequestParameters = testCapturedRequestParameters() options.testCaptureRequestParameters = testCapturedRequestParameters()
options.testHttpPipelining = testHttpPipelining() options.testHttpPipelining = testHttpPipelining()
if (!testNonStandardHttpMethod()) {
options.disableTestNonStandardHttpMethod()
}
} }
// Override trace assertion method. We can call java assertions from groovy but not the other // Override trace assertion method. We can call java assertions from groovy but not the other
@ -241,9 +261,8 @@ abstract class HttpServerTest<SERVER> extends InstrumentationSpecification imple
String parentId, String parentId,
String spanId, String spanId,
String method, String method,
ServerEndpoint endpoint, ServerEndpoint endpoint) {
AggregatedHttpResponse response) { HttpServerTest.this.assertTheTraces(size, traceId, parentId, spanId, method, endpoint)
HttpServerTest.this.assertTheTraces(size, traceId, parentId, spanId, method, endpoint, response)
} }
@Override @Override
@ -337,6 +356,13 @@ abstract class HttpServerTest<SERVER> extends InstrumentationSpecification imple
junitTest.httpPipelining() junitTest.httpPipelining()
} }
def "non standard http method"() {
assumeTrue(SemconvStability.emitStableHttpSemconv())
assumeTrue(testNonStandardHttpMethod())
expect:
junitTest.requestWithNonStandardHttpMethod()
}
void assertHighConcurrency(int count) { void assertHighConcurrency(int count) {
def endpoint = INDEXED_CHILD def endpoint = INDEXED_CHILD
assertTraces(count) { assertTraces(count) {
@ -372,7 +398,7 @@ abstract class HttpServerTest<SERVER> extends InstrumentationSpecification imple
//FIXME: add tests for POST with large/chunked data //FIXME: add tests for POST with large/chunked data
void assertTheTraces(int size, String traceID = null, String parentID = null, String spanID = null, String method = "GET", ServerEndpoint endpoint = SUCCESS, AggregatedHttpResponse response = null) { void assertTheTraces(int size, String traceID = null, String parentID = null, String spanID = null, String method = "GET", ServerEndpoint endpoint = SUCCESS) {
def spanCount = 1 // server span def spanCount = 1 // server span
if (hasResponseSpan(endpoint)) { if (hasResponseSpan(endpoint)) {
spanCount++ spanCount++
@ -398,7 +424,7 @@ abstract class HttpServerTest<SERVER> extends InstrumentationSpecification imple
assert it.span(0).endEpochNanos - it.span(index).endEpochNanos >= 0 assert it.span(0).endEpochNanos - it.span(index).endEpochNanos >= 0
} }
} }
serverSpan(it, spanIndex++, traceID, parentID, method, response?.content()?.length(), endpoint, spanID) this.serverSpan(it, spanIndex++, traceID, parentID, method, endpoint, spanID)
if (hasHandlerSpan(endpoint)) { if (hasHandlerSpan(endpoint)) {
handlerSpan(it, spanIndex++, span(0), method, endpoint) handlerSpan(it, spanIndex++, span(0), method, endpoint)
} }
@ -471,10 +497,10 @@ abstract class HttpServerTest<SERVER> extends InstrumentationSpecification imple
} }
} }
void serverSpan(TraceAssert trace, int index, String traceID = null, String parentID = null, String method = "GET", Long responseContentLength = null, ServerEndpoint endpoint = SUCCESS, String spanID = null) { void serverSpan(TraceAssert trace, int index, String traceID = null, String parentID = null, String method = "GET", ServerEndpoint endpoint = SUCCESS, String spanID = null) {
trace.assertedIndexes.add(index) trace.assertedIndexes.add(index)
def spanData = trace.span(index) def spanData = trace.span(index)
def assertion = junitTest.assertServerSpan(OpenTelemetryAssertions.assertThat(spanData), method, endpoint) def assertion = junitTest.assertServerSpan(OpenTelemetryAssertions.assertThat(spanData), method, endpoint, endpoint.status)
if (parentID == null) { if (parentID == null) {
assertion.hasParentSpanId(SpanId.invalid) assertion.hasParentSpanId(SpanId.invalid)
} else { } else {

View File

@ -27,8 +27,10 @@ import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.context.Context; import io.opentelemetry.context.Context;
import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.context.propagation.TextMapPropagator;
import io.opentelemetry.context.propagation.TextMapSetter; import io.opentelemetry.context.propagation.TextMapSetter;
import io.opentelemetry.instrumentation.api.instrumenter.http.internal.HttpAttributes;
import io.opentelemetry.instrumentation.api.instrumenter.network.internal.NetworkAttributes; import io.opentelemetry.instrumentation.api.instrumenter.network.internal.NetworkAttributes;
import io.opentelemetry.instrumentation.api.instrumenter.url.internal.UrlAttributes; import io.opentelemetry.instrumentation.api.instrumenter.url.internal.UrlAttributes;
import io.opentelemetry.instrumentation.api.internal.HttpConstants;
import io.opentelemetry.instrumentation.api.internal.SemconvStability; import io.opentelemetry.instrumentation.api.internal.SemconvStability;
import io.opentelemetry.instrumentation.testing.GlobalTraceUtil; import io.opentelemetry.instrumentation.testing.GlobalTraceUtil;
import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; import io.opentelemetry.sdk.testing.assertj.SpanDataAssert;
@ -139,7 +141,7 @@ public abstract class AbstractHttpServerTest<SERVER> extends AbstractHttpServerU
assertResponseHasCustomizedHeaders(response, SUCCESS, null); assertResponseHasCustomizedHeaders(response, SUCCESS, null);
} }
assertTheTraces(count, null, null, null, method, SUCCESS, responses.get(0)); assertTheTraces(count, null, null, null, method, SUCCESS);
} }
@Test @Test
@ -160,7 +162,7 @@ public abstract class AbstractHttpServerTest<SERVER> extends AbstractHttpServerU
assertThat(response.contentUtf8()).isEqualTo(SUCCESS.getBody()); assertThat(response.contentUtf8()).isEqualTo(SUCCESS.getBody());
String spanId = assertResponseHasCustomizedHeaders(response, SUCCESS, traceId); String spanId = assertResponseHasCustomizedHeaders(response, SUCCESS, traceId);
assertTheTraces(1, traceId, parentId, spanId, "GET", SUCCESS, response); assertTheTraces(1, traceId, parentId, spanId, "GET", SUCCESS);
} }
@Test @Test
@ -179,7 +181,7 @@ public abstract class AbstractHttpServerTest<SERVER> extends AbstractHttpServerU
assertThat(response.contentUtf8()).isEqualTo(SUCCESS.getBody()); assertThat(response.contentUtf8()).isEqualTo(SUCCESS.getBody());
String spanId = assertResponseHasCustomizedHeaders(response, SUCCESS, traceId); String spanId = assertResponseHasCustomizedHeaders(response, SUCCESS, traceId);
assertTheTraces(1, traceId, parentId, spanId, "GET", SUCCESS, response); assertTheTraces(1, traceId, parentId, spanId, "GET", SUCCESS);
} }
@ParameterizedTest @ParameterizedTest
@ -193,7 +195,7 @@ public abstract class AbstractHttpServerTest<SERVER> extends AbstractHttpServerU
assertThat(response.contentUtf8()).isEqualTo(endpoint.getBody()); assertThat(response.contentUtf8()).isEqualTo(endpoint.getBody());
String spanId = assertResponseHasCustomizedHeaders(response, endpoint, null); String spanId = assertResponseHasCustomizedHeaders(response, endpoint, null);
assertTheTraces(1, null, null, spanId, method, endpoint, response); assertTheTraces(1, null, null, spanId, method, endpoint);
} }
private static Stream<ServerEndpoint> provideServerEndpoints() { private static Stream<ServerEndpoint> provideServerEndpoints() {
@ -217,7 +219,7 @@ public abstract class AbstractHttpServerTest<SERVER> extends AbstractHttpServerU
.isEqualTo(address.resolve(REDIRECT.getBody()).toString())); .isEqualTo(address.resolve(REDIRECT.getBody()).toString()));
String spanId = assertResponseHasCustomizedHeaders(response, REDIRECT, null); String spanId = assertResponseHasCustomizedHeaders(response, REDIRECT, null);
assertTheTraces(1, null, null, spanId, method, REDIRECT, response); assertTheTraces(1, null, null, spanId, method, REDIRECT);
} }
@Test @Test
@ -234,7 +236,7 @@ public abstract class AbstractHttpServerTest<SERVER> extends AbstractHttpServerU
} }
String spanId = assertResponseHasCustomizedHeaders(response, ERROR, null); String spanId = assertResponseHasCustomizedHeaders(response, ERROR, null);
assertTheTraces(1, null, null, spanId, method, ERROR, response); assertTheTraces(1, null, null, spanId, method, ERROR);
} }
@Test @Test
@ -252,7 +254,7 @@ public abstract class AbstractHttpServerTest<SERVER> extends AbstractHttpServerU
assertThat(response.status().code()).isEqualTo(EXCEPTION.getStatus()); assertThat(response.status().code()).isEqualTo(EXCEPTION.getStatus());
String spanId = assertResponseHasCustomizedHeaders(response, EXCEPTION, null); String spanId = assertResponseHasCustomizedHeaders(response, EXCEPTION, null);
assertTheTraces(1, null, null, spanId, method, EXCEPTION, response); assertTheTraces(1, null, null, spanId, method, EXCEPTION);
} finally { } finally {
Awaitility.reset(); Awaitility.reset();
} }
@ -269,7 +271,7 @@ public abstract class AbstractHttpServerTest<SERVER> extends AbstractHttpServerU
assertThat(response.status().code()).isEqualTo(NOT_FOUND.getStatus()); assertThat(response.status().code()).isEqualTo(NOT_FOUND.getStatus());
String spanId = assertResponseHasCustomizedHeaders(response, NOT_FOUND, null); String spanId = assertResponseHasCustomizedHeaders(response, NOT_FOUND, null);
assertTheTraces(1, null, null, spanId, method, NOT_FOUND, response); assertTheTraces(1, null, null, spanId, method, NOT_FOUND);
} }
@Test @Test
@ -284,7 +286,7 @@ public abstract class AbstractHttpServerTest<SERVER> extends AbstractHttpServerU
assertThat(response.contentUtf8()).isEqualTo(PATH_PARAM.getBody()); assertThat(response.contentUtf8()).isEqualTo(PATH_PARAM.getBody());
String spanId = assertResponseHasCustomizedHeaders(response, PATH_PARAM, null); String spanId = assertResponseHasCustomizedHeaders(response, PATH_PARAM, null);
assertTheTraces(1, null, null, spanId, method, PATH_PARAM, response); assertTheTraces(1, null, null, spanId, method, PATH_PARAM);
} }
@Test @Test
@ -303,7 +305,7 @@ public abstract class AbstractHttpServerTest<SERVER> extends AbstractHttpServerU
assertThat(response.headers().get("X-Test-Response")).isEqualTo("test"); assertThat(response.headers().get("X-Test-Response")).isEqualTo("test");
String spanId = assertResponseHasCustomizedHeaders(response, CAPTURE_HEADERS, null); String spanId = assertResponseHasCustomizedHeaders(response, CAPTURE_HEADERS, null);
assertTheTraces(1, null, null, spanId, "GET", CAPTURE_HEADERS, response); assertTheTraces(1, null, null, spanId, "GET", CAPTURE_HEADERS);
} }
@Test @Test
@ -323,7 +325,7 @@ public abstract class AbstractHttpServerTest<SERVER> extends AbstractHttpServerU
assertThat(response.contentUtf8()).isEqualTo(CAPTURE_PARAMETERS.getBody()); assertThat(response.contentUtf8()).isEqualTo(CAPTURE_PARAMETERS.getBody());
String spanId = assertResponseHasCustomizedHeaders(response, CAPTURE_PARAMETERS, null); String spanId = assertResponseHasCustomizedHeaders(response, CAPTURE_PARAMETERS, null);
assertTheTraces(1, null, null, spanId, "POST", CAPTURE_PARAMETERS, response); assertTheTraces(1, null, null, spanId, "POST", CAPTURE_PARAMETERS);
} }
@Test @Test
@ -339,7 +341,8 @@ public abstract class AbstractHttpServerTest<SERVER> extends AbstractHttpServerU
testing.waitAndAssertTraces( testing.waitAndAssertTraces(
trace -> { trace -> {
instrumentationName.set(trace.getSpan(0).getInstrumentationScopeInfo().getName()); instrumentationName.set(trace.getSpan(0).getInstrumentationScopeInfo().getName());
trace.anySatisfy(spanData -> assertServerSpan(assertThat(spanData), method, SUCCESS)); trace.anySatisfy(
spanData -> assertServerSpan(assertThat(spanData), method, SUCCESS, SUCCESS.status));
}); });
String durationInstrumentName = String durationInstrumentName =
@ -478,6 +481,54 @@ public abstract class AbstractHttpServerTest<SERVER> extends AbstractHttpServerU
} }
} }
@Test
void requestWithNonStandardHttpMethod() throws InterruptedException {
assumeTrue(SemconvStability.emitStableHttpSemconv());
assumeTrue(options.testNonStandardHttpMethod);
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = buildBootstrap(eventLoopGroup);
Channel channel = bootstrap.connect(address.getHost(), port).sync().channel();
String method = "TEST";
DefaultFullHttpRequest request =
new DefaultFullHttpRequest(
HttpVersion.HTTP_1_1,
new io.opentelemetry.testing.internal.io.netty.handler.codec.http.HttpMethod(method),
SUCCESS.resolvePath(address).getPath(),
Unpooled.EMPTY_BUFFER);
request.headers().set(HttpHeaderNames.HOST, address.getHost() + ":" + port);
request.headers().set(HttpHeaderNames.USER_AGENT, TEST_USER_AGENT);
request.headers().set(HttpHeaderNames.X_FORWARDED_FOR, TEST_CLIENT_IP);
testing
.getOpenTelemetry()
.getPropagators()
.getTextMapPropagator()
.inject(
Context.current(),
request,
(carrier, key, value) -> carrier.headers().set(key, value));
channel.writeAndFlush(request);
// TODO: add stricter assertions; could be difficult with the groovy code still in place
// though
testing.waitAndAssertTraces(
trace ->
trace.anySatisfy(
span ->
assertServerSpan(
assertThat(span),
HttpConstants._OTHER,
SUCCESS,
options.responseCodeOnNonStandardHttpMethod)
.hasAttribute(HttpAttributes.HTTP_REQUEST_METHOD_ORIGINAL, method)));
} finally {
eventLoopGroup.shutdownGracefully().await(10, TimeUnit.SECONDS);
}
}
private static Bootstrap buildBootstrap(EventLoopGroup eventLoopGroup) { private static Bootstrap buildBootstrap(EventLoopGroup eventLoopGroup) {
Bootstrap bootstrap = new Bootstrap(); Bootstrap bootstrap = new Bootstrap();
bootstrap bootstrap
@ -566,8 +617,7 @@ public abstract class AbstractHttpServerTest<SERVER> extends AbstractHttpServerU
String parentId, String parentId,
String spanId, String spanId,
String method, String method,
ServerEndpoint endpoint, ServerEndpoint endpoint) {
AggregatedHttpResponse response) {
List<Consumer<TraceAssert>> assertions = new ArrayList<>(); List<Consumer<TraceAssert>> assertions = new ArrayList<>();
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
assertions.add( assertions.add(
@ -575,7 +625,7 @@ public abstract class AbstractHttpServerTest<SERVER> extends AbstractHttpServerU
List<Consumer<SpanDataAssert>> spanAssertions = new ArrayList<>(); List<Consumer<SpanDataAssert>> spanAssertions = new ArrayList<>();
spanAssertions.add( spanAssertions.add(
span -> { span -> {
assertServerSpan(span, method, endpoint); assertServerSpan(span, method, endpoint, endpoint.status);
if (traceId != null) { if (traceId != null) {
span.hasTraceId(traceId); span.hasTraceId(traceId);
} }
@ -674,14 +724,14 @@ public abstract class AbstractHttpServerTest<SERVER> extends AbstractHttpServerU
@CanIgnoreReturnValue @CanIgnoreReturnValue
@SuppressWarnings("deprecation") // until old http semconv are dropped in 2.0 @SuppressWarnings("deprecation") // until old http semconv are dropped in 2.0
protected SpanDataAssert assertServerSpan( protected SpanDataAssert assertServerSpan(
SpanDataAssert span, String method, ServerEndpoint endpoint) { SpanDataAssert span, String method, ServerEndpoint endpoint, int statusCode) {
Set<AttributeKey<?>> httpAttributes = options.httpAttributes.apply(endpoint); Set<AttributeKey<?>> httpAttributes = options.httpAttributes.apply(endpoint);
String expectedRoute = options.expectedHttpRoute.apply(endpoint); String expectedRoute = options.expectedHttpRoute.apply(endpoint, method);
String name = getString(method, endpoint, expectedRoute); String name = options.expectedServerSpanNameMapper.apply(endpoint, method, expectedRoute);
span.hasName(name).hasKind(SpanKind.SERVER); span.hasName(name).hasKind(SpanKind.SERVER);
if (endpoint.status >= 500) { if (statusCode >= 500) {
span.hasStatus(StatusData.error()); span.hasStatus(StatusData.error());
} }
@ -751,7 +801,7 @@ public abstract class AbstractHttpServerTest<SERVER> extends AbstractHttpServerU
} }
assertThat(attrs).containsEntry(getAttributeKey(SemanticAttributes.HTTP_METHOD), method); assertThat(attrs).containsEntry(getAttributeKey(SemanticAttributes.HTTP_METHOD), method);
assertThat(attrs) assertThat(attrs)
.containsEntry(getAttributeKey(SemanticAttributes.HTTP_STATUS_CODE), endpoint.status); .containsEntry(getAttributeKey(SemanticAttributes.HTTP_STATUS_CODE), statusCode);
AttributeKey<String> netProtocolKey = AttributeKey<String> netProtocolKey =
getAttributeKey(SemanticAttributes.NET_PROTOCOL_NAME); getAttributeKey(SemanticAttributes.NET_PROTOCOL_NAME);
@ -823,17 +873,12 @@ public abstract class AbstractHttpServerTest<SERVER> extends AbstractHttpServerU
return SemconvStabilityUtil.getAttributeKey(oldKey); return SemconvStabilityUtil.getAttributeKey(oldKey);
} }
private String getString(String method, ServerEndpoint endpoint, String expectedRoute) {
String name = options.expectedServerSpanNameMapper.apply(endpoint, method, expectedRoute);
return name;
}
@CanIgnoreReturnValue @CanIgnoreReturnValue
@SuppressWarnings("deprecation") // until old http semconv are dropped in 2.0 @SuppressWarnings("deprecation") // until old http semconv are dropped in 2.0
protected SpanDataAssert assertIndexedServerSpan(SpanDataAssert span, int requestId) { protected SpanDataAssert assertIndexedServerSpan(SpanDataAssert span, int requestId) {
ServerEndpoint endpoint = INDEXED_CHILD; ServerEndpoint endpoint = INDEXED_CHILD;
String method = "GET"; String method = "GET";
assertServerSpan(span, method, endpoint); assertServerSpan(span, method, endpoint, endpoint.status);
if (SemconvStability.emitOldHttpSemconv()) { if (SemconvStability.emitOldHttpSemconv()) {
span.hasAttributesSatisfying( span.hasAttributesSatisfying(
@ -865,12 +910,16 @@ public abstract class AbstractHttpServerTest<SERVER> extends AbstractHttpServerU
endpoint, method, route); endpoint, method, route);
} }
public String expectedHttpRoute(ServerEndpoint endpoint) { public String expectedHttpRoute(ServerEndpoint endpoint, String method) {
// no need to compute route if we're not expecting it // no need to compute route if we're not expecting it
if (!options.httpAttributes.apply(endpoint).contains(SemanticAttributes.HTTP_ROUTE)) { if (!options.httpAttributes.apply(endpoint).contains(SemanticAttributes.HTTP_ROUTE)) {
return null; return null;
} }
if (HttpConstants._OTHER.equals(method)) {
return null;
}
if (NOT_FOUND.equals(endpoint)) { if (NOT_FOUND.equals(endpoint)) {
return null; return null;
} else if (PATH_PARAM.equals(endpoint)) { } else if (PATH_PARAM.equals(endpoint)) {

View File

@ -9,11 +9,13 @@ import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint
import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.CanIgnoreReturnValue;
import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.instrumentation.api.internal.HttpConstants;
import io.opentelemetry.semconv.SemanticAttributes; import io.opentelemetry.semconv.SemanticAttributes;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -30,15 +32,22 @@ public final class HttpServerTestOptions {
SemconvStabilityUtil.getAttributeKey(SemanticAttributes.NET_PEER_PORT)))); SemconvStabilityUtil.getAttributeKey(SemanticAttributes.NET_PEER_PORT))));
public static final SpanNameMapper DEFAULT_EXPECTED_SERVER_SPAN_NAME_MAPPER = public static final SpanNameMapper DEFAULT_EXPECTED_SERVER_SPAN_NAME_MAPPER =
(uri, method, route) -> route == null ? method : method + " " + route; (uri, method, route) -> {
if (HttpConstants._OTHER.equals(method)) {
method = "HTTP";
}
return route == null ? method : method + " " + route;
};
Function<ServerEndpoint, Set<AttributeKey<?>>> httpAttributes = unused -> DEFAULT_HTTP_ATTRIBUTES; Function<ServerEndpoint, Set<AttributeKey<?>>> httpAttributes = unused -> DEFAULT_HTTP_ATTRIBUTES;
SpanNameMapper expectedServerSpanNameMapper = DEFAULT_EXPECTED_SERVER_SPAN_NAME_MAPPER; SpanNameMapper expectedServerSpanNameMapper = DEFAULT_EXPECTED_SERVER_SPAN_NAME_MAPPER;
Function<ServerEndpoint, String> expectedHttpRoute = unused -> null; BiFunction<ServerEndpoint, String, String> expectedHttpRoute = (endpoint, method) -> null;
Function<ServerEndpoint, String> sockPeerAddr = unused -> "127.0.0.1"; Function<ServerEndpoint, String> sockPeerAddr = unused -> "127.0.0.1";
String contextPath = ""; String contextPath = "";
Throwable expectedException = new Exception(EXCEPTION.body); Throwable expectedException = new Exception(EXCEPTION.body);
Supplier<String> metricsInstrumentationName = () -> null; Supplier<String> metricsInstrumentationName = () -> null;
// we're calling /success in the test, and most servers respond with 200 anyway
int responseCodeOnNonStandardHttpMethod = ServerEndpoint.SUCCESS.status;
Predicate<ServerEndpoint> hasHandlerSpan = unused -> false; Predicate<ServerEndpoint> hasHandlerSpan = unused -> false;
Predicate<ServerEndpoint> hasResponseSpan = unused -> false; Predicate<ServerEndpoint> hasResponseSpan = unused -> false;
@ -57,6 +66,7 @@ public final class HttpServerTestOptions {
boolean testCaptureHttpHeaders = true; boolean testCaptureHttpHeaders = true;
boolean testCaptureRequestParameters = false; boolean testCaptureRequestParameters = false;
boolean testHttpPipelining = true; boolean testHttpPipelining = true;
boolean testNonStandardHttpMethod = true;
boolean verifyServerSpanEndTime = true; boolean verifyServerSpanEndTime = true;
HttpServerTestOptions() {} HttpServerTestOptions() {}
@ -77,7 +87,7 @@ public final class HttpServerTestOptions {
@CanIgnoreReturnValue @CanIgnoreReturnValue
public HttpServerTestOptions setExpectedHttpRoute( public HttpServerTestOptions setExpectedHttpRoute(
Function<ServerEndpoint, String> expectedHttpRoute) { BiFunction<ServerEndpoint, String, String> expectedHttpRoute) {
this.expectedHttpRoute = expectedHttpRoute; this.expectedHttpRoute = expectedHttpRoute;
return this; return this;
} }
@ -107,6 +117,13 @@ public final class HttpServerTestOptions {
return this; return this;
} }
@CanIgnoreReturnValue
public HttpServerTestOptions setResponseCodeOnNonStandardHttpMethod(
int responseCodeOnNonStandardHttpMethod) {
this.responseCodeOnNonStandardHttpMethod = responseCodeOnNonStandardHttpMethod;
return this;
}
@CanIgnoreReturnValue @CanIgnoreReturnValue
public HttpServerTestOptions setHasHandlerSpan(Predicate<ServerEndpoint> hasHandlerSpan) { public HttpServerTestOptions setHasHandlerSpan(Predicate<ServerEndpoint> hasHandlerSpan) {
this.hasHandlerSpan = hasHandlerSpan; this.hasHandlerSpan = hasHandlerSpan;
@ -201,6 +218,13 @@ public final class HttpServerTestOptions {
return this; return this;
} }
// TODO: convert make this class follow the same pattern as HttpClientTestOptions
@CanIgnoreReturnValue
public HttpServerTestOptions disableTestNonStandardHttpMethod() {
this.testNonStandardHttpMethod = false;
return this;
}
@CanIgnoreReturnValue @CanIgnoreReturnValue
public HttpServerTestOptions setVerifyServerSpanEndTime(boolean verifyServerSpanEndTime) { public HttpServerTestOptions setVerifyServerSpanEndTime(boolean verifyServerSpanEndTime) {
this.verifyServerSpanEndTime = verifyServerSpanEndTime; this.verifyServerSpanEndTime = verifyServerSpanEndTime;