diff --git a/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/vertx/HttpInstrumenterVertxTracer.java b/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/vertx/HttpInstrumenterVertxTracer.java index 162af7b40b289..0e21f0d2bdbc3 100644 --- a/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/vertx/HttpInstrumenterVertxTracer.java +++ b/extensions/opentelemetry/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/vertx/HttpInstrumenterVertxTracer.java @@ -1,6 +1,5 @@ package io.quarkus.opentelemetry.runtime.tracing.vertx; -import static io.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND; import static io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteSource.FILTER; import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_CLIENT_IP; import static io.quarkus.opentelemetry.runtime.OpenTelemetryConfig.INSTRUMENTATION_NAME; @@ -17,6 +16,7 @@ import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; +import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesGetter; import io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteBiGetter; @@ -31,6 +31,7 @@ import io.vertx.core.http.HttpMethod; import io.vertx.core.http.HttpServerRequest; import io.vertx.core.http.HttpVersion; +import io.vertx.core.http.impl.HttpRequestHead; import io.vertx.core.http.impl.headers.HeadersAdaptor; import io.vertx.core.http.impl.headers.HeadersMultiMap; import io.vertx.core.net.SocketAddress; @@ -125,7 +126,7 @@ private static Instrumenter getClientInstrumenter(fin InstrumenterBuilder clientBuilder = Instrumenter.builder( openTelemetry, INSTRUMENTATION_NAME, - HttpSpanNameExtractor.create(clientAttributesExtractor)); + new ClientSpanNameExtractor(clientAttributesExtractor)); return clientBuilder .setSpanStatusExtractor(HttpSpanStatusExtractor.create(serverAttributesExtractor)) @@ -357,6 +358,33 @@ public List responseHeader(final HttpRequest request, final HttpResponse } } + private static class ClientSpanNameExtractor implements SpanNameExtractor { + + private final SpanNameExtractor http; + + ClientSpanNameExtractor(ClientAttributesExtractor clientAttributesExtractor) { + this.http = HttpSpanNameExtractor.create(clientAttributesExtractor); + } + + @Override + public String extract(HttpRequest httpRequest) { + if (httpRequest instanceof HttpRequestHead) { + HttpRequestHead head = (HttpRequestHead) httpRequest; + if (head.traceOperation != null) { + return head.traceOperation; + } + } + if (httpRequest instanceof WriteHeadersHttpRequest) { + WriteHeadersHttpRequest writeHeaders = (WriteHeadersHttpRequest) httpRequest; + String traceOperation = writeHeaders.traceOperation(); + if (traceOperation != null) { + return traceOperation; + } + } + return http.extract(httpRequest); + } + } + private static class HttpRequestTextMapSetter implements TextMapSetter { @Override public void set(final HttpRequest carrier, final String key, final String value) { @@ -481,6 +509,13 @@ public SocketAddress remoteAddress() { return httpRequest.remoteAddress(); } + public String traceOperation() { + if (httpRequest instanceof HttpRequestHead) { + return ((HttpRequestHead) httpRequest).traceOperation; + } + return null; + } + static WriteHeadersHttpRequest request(HttpRequest httpRequest, BiConsumer headers) { return new WriteHeadersHttpRequest(httpRequest, headers); } diff --git a/integration-tests/opentelemetry/pom.xml b/integration-tests/opentelemetry/pom.xml index 3642042ff1b89..59d954f3dfd52 100644 --- a/integration-tests/opentelemetry/pom.xml +++ b/integration-tests/opentelemetry/pom.xml @@ -35,6 +35,12 @@ quarkus-rest-client + + + io.smallrye.reactive + smallrye-mutiny-vertx-web-client + + io.opentelemetry diff --git a/integration-tests/opentelemetry/src/main/java/io/quarkus/it/opentelemetry/PingPongResource.java b/integration-tests/opentelemetry/src/main/java/io/quarkus/it/opentelemetry/PingPongResource.java index b875008a6b972..af49b27dae0da 100644 --- a/integration-tests/opentelemetry/src/main/java/io/quarkus/it/opentelemetry/PingPongResource.java +++ b/integration-tests/opentelemetry/src/main/java/io/quarkus/it/opentelemetry/PingPongResource.java @@ -6,11 +6,20 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; +import org.eclipse.microprofile.config.inject.ConfigProperty; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import org.eclipse.microprofile.rest.client.inject.RestClient; import io.smallrye.common.annotation.Blocking; import io.smallrye.mutiny.Uni; +import io.vertx.core.MultiMap; +import io.vertx.core.http.HttpMethod; +import io.vertx.core.http.RequestOptions; +import io.vertx.mutiny.core.Vertx; +import io.vertx.mutiny.core.buffer.Buffer; +import io.vertx.mutiny.core.http.HttpClient; +import io.vertx.mutiny.core.http.HttpClientRequest; +import io.vertx.mutiny.core.http.HttpClientResponse; @Singleton @Path("/client") @@ -31,6 +40,12 @@ public interface PingPongRestClient { @RestClient PingPongRestClient pingRestClient; + @Inject + Vertx vertx; + + @ConfigProperty(name = "test.url") + String testUrl; + @GET @Path("pong/{message}") public String pong(@PathParam("message") String message) { @@ -49,4 +64,21 @@ public String ping(@PathParam("message") String message) { public Uni asyncPing(@PathParam("message") String message) { return pingRestClient.asyncPingpong(message); } + + @GET + @Path("async-ping-named/{message}") + public Uni asyncPingNamed(@PathParam("message") String message) { + HttpClient httpClient = vertx.createHttpClient(); + RequestOptions options = new RequestOptions() + .setMethod(HttpMethod.GET) + .setAbsoluteURI(testUrl + "/client/pong/" + message) + .setHeaders(MultiMap.caseInsensitiveMultiMap()) + .setTraceOperation("Async Ping"); + return httpClient.request(options) + .flatMap(HttpClientRequest::send) + .flatMap(HttpClientResponse::body) + .map(Buffer::toString) + .onItemOrFailure().call(httpClient::close); + } + } diff --git a/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryTestCase.java b/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryTestCase.java index 2a73f49d5c0d7..39964be7272de 100644 --- a/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryTestCase.java +++ b/integration-tests/opentelemetry/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryTestCase.java @@ -753,6 +753,27 @@ void testTemplatedPathOnClass() { Assertions.assertNotNull(spanData.get("attr_http.user_agent")); } + @Test + void testCustomSpanNames() { + resetExporter(); + + given() + .contentType("application/json") + .when().get("/client/async-ping-named/one") + .then() + .statusCode(200) + .body(containsString("one")); + + Awaitility.await().atMost(Duration.ofMinutes(2)).until(() -> getSpans().size() > 2); + Map spanData = getSpans().get(1); + Assertions.assertNotNull(spanData); + Assertions.assertNotNull(spanData.get("spanId")); + + verifyResource(spanData); + + Assertions.assertEquals("Async Ping", spanData.get("name")); + } + /** * From bug #26149 * NPE was thrown when HTTP version was not supported with OpenTelemetry