Skip to content

Commit

Permalink
Set http.route attribute as path template (#149)
Browse files Browse the repository at this point in the history
* Set http.route attribute as path template

We have seen issues with the current spans exported from the http
library in observability backends, due to too granular span names when
the routes contain path variables.

As per the opentelemetry specification, this PR extracts the path
template from the UriRouteMatch object, and sets it as the
`http.route` attribute.

https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/http/#http-server-semantic-conventions

* Update tracing-opentelemetry-http/src/main/java/io/micronaut/tracing/opentelemetry/instrument/http/server/MicronautHttpServerAttributesGetter.java

Co-authored-by: Sergio del Amo <sergio.delamo@softamo.com>
Co-authored-by: Nemanja Mikic <nemanja.mikic@oracle.com>
  • Loading branch information
3 people authored Oct 14, 2022
1 parent 62515d7 commit eac6ff7
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 0 deletions.
1 change: 1 addition & 0 deletions tracing-opentelemetry-http/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ dependencies {
api platform (libs.boms.opentelemetry.instrumentation.alpha)
api mn.micronaut.runtime
api mn.micronaut.http.client
api mn.micronaut.router
api projects.tracingAnnotation
api projects.tracingOpentelemetry
api libs.opentelemetry.api
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,14 @@
import io.micronaut.http.HttpAttributes;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.uri.UriMatchTemplate;
import io.micronaut.web.router.UriRoute;
import io.micronaut.web.router.UriRouteMatch;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpServerAttributesGetter;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;

import java.util.List;
import java.util.Optional;

@Internal
enum MicronautHttpServerAttributesGetter implements HttpServerAttributesGetter<HttpRequest<Object>, HttpResponse<Object>> {
Expand Down Expand Up @@ -79,6 +83,15 @@ public String target(HttpRequest<Object> request) {
@Override
@Nullable
public String route(HttpRequest<Object> request) {
Optional<Object> routeInfo = request.getAttribute(HttpAttributes.ROUTE_INFO)
.filter(UriRouteMatch.class::isInstance)
.map(ri -> (UriRouteMatch) ri)
.map(UriRouteMatch::getRoute)
.map(UriRoute::getUriMatchTemplate)
.map(UriMatchTemplate::toPathString);
if (routeInfo.isPresent()) {
return routeInfo.get().toString();
}
String route = request.getAttribute(HttpAttributes.URI_TEMPLATE).map(Object::toString)
.orElse(request.getUri().toString());
return request.getMethodName() + " - " + route;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.micronaut.tracing.instrument.http
import groovy.util.logging.Slf4j
import io.micronaut.context.ApplicationContext
import io.micronaut.core.annotation.Introspected
import io.micronaut.core.annotation.Nullable
import io.micronaut.http.HttpRequest
import io.micronaut.http.HttpResponse
import io.micronaut.http.annotation.*
Expand Down Expand Up @@ -251,6 +252,36 @@ class OpenTelemetryHttpSpec extends Specification {
exporter.reset()
}

void 'route match template is added as route attribute'() {
def testExporter = embeddedServer.applicationContext.getBean(InMemorySpanExporter)
def warehouseClient = embeddedServer.applicationContext.getBean(WarehouseClient)

expect:

warehouseClient.order(UUID.randomUUID())
conditions.eventually {
testExporter.finishedSpanItems.attributes.stream().anyMatch(x -> x.get(AttributeKey.stringKey("http.route")) == "/client/order/{orderId}")
}

cleanup:
testExporter.reset()
}

void 'query variables are not included in route template attribute'() {
def testExporter = embeddedServer.applicationContext.getBean(InMemorySpanExporter)
def warehouseClient = embeddedServer.applicationContext.getBean(WarehouseClient)

expect:

warehouseClient.order(UUID.randomUUID(), UUID.randomUUID())
conditions.eventually {
testExporter.finishedSpanItems.attributes.stream().anyMatch(x -> x.get(AttributeKey.stringKey("http.route")) == "/client/order/{orderId}")
}

cleanup:
testExporter.reset()
}

@Introspected
static class SomeBody {
}
Expand Down Expand Up @@ -429,6 +460,12 @@ class OpenTelemetryHttpSpec extends Specification {

}

@Get("/order/{orderId}")
void order(@PathVariable("orderId") UUID orderId, @Nullable @QueryValue("customerId") UUID customerId) {

}


}

@Client("/client")
Expand All @@ -442,6 +479,12 @@ class OpenTelemetryHttpSpec extends Specification {
@NewSpan
void order(@SpanTag("warehouse.order") Map<String, ?> json);

@Get("/order/{orderId}")
void order(UUID orderId);

@Get("/order/{orderId}?customerId={customerId}")
void order(UUID orderId, UUID customerId);

}

@Client(id = "correctspanname")
Expand Down

0 comments on commit eac6ff7

Please sign in to comment.