Skip to content

Commit

Permalink
Add support for custom HTTP trace operation names to telemetry spans
Browse files Browse the repository at this point in the history
Vertx `WebClient` & `HttpClient` allow specifying a custom trace operation name. This uses a custom span name extractor to pass this trace operation name on as the span name.
  • Loading branch information
kdubb committed Jul 15, 2022
1 parent b48ccb6 commit be753b7
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -125,7 +126,7 @@ private static Instrumenter<HttpRequest, HttpResponse> getClientInstrumenter(fin
InstrumenterBuilder<HttpRequest, HttpResponse> clientBuilder = Instrumenter.builder(
openTelemetry,
INSTRUMENTATION_NAME,
HttpSpanNameExtractor.create(clientAttributesExtractor));
new ClientSpanNameExtractor(clientAttributesExtractor));

return clientBuilder
.setSpanStatusExtractor(HttpSpanStatusExtractor.create(serverAttributesExtractor))
Expand Down Expand Up @@ -357,6 +358,33 @@ public List<String> responseHeader(final HttpRequest request, final HttpResponse
}
}

private static class ClientSpanNameExtractor implements SpanNameExtractor<HttpRequest> {

private final SpanNameExtractor<HttpRequest> 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<HttpRequest> {
@Override
public void set(final HttpRequest carrier, final String key, final String value) {
Expand Down Expand Up @@ -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<String, String> headers) {
return new WriteHeadersHttpRequest(httpRequest, headers);
}
Expand Down
6 changes: 6 additions & 0 deletions integration-tests/opentelemetry/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@
<artifactId>quarkus-rest-client</artifactId>
</dependency>

<!-- Vertx WebClient -->
<dependency>
<groupId>io.smallrye.reactive</groupId>
<artifactId>smallrye-mutiny-vertx-web-client</artifactId>
</dependency>

<!-- Needed for InMemorySpanExporter to verify captured traces -->
<dependency>
<groupId>io.opentelemetry</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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) {
Expand All @@ -49,4 +64,21 @@ public String ping(@PathParam("message") String message) {
public Uni<String> asyncPing(@PathParam("message") String message) {
return pingRestClient.asyncPingpong(message);
}

@GET
@Path("async-ping-named/{message}")
public Uni<String> 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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, Object> 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
Expand Down

0 comments on commit be753b7

Please sign in to comment.