Skip to content

Commit 97d24da

Browse files
committed
use route pattern string as server side operation name
1 parent e8ef32c commit 97d24da

File tree

6 files changed

+48
-57
lines changed

6 files changed

+48
-57
lines changed

instrumentation/kamon-armeria/src/main/java/kamon/instrumentation/armeria/server/ArmeriaHttpServerDecorator.java

+16-8
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131

3232
public class ArmeriaHttpServerDecorator extends SimpleDecoratingHttpService {
3333
public static final AttributeKey<HttpServerInstrumentation.RequestHandler> REQUEST_HANDLER_TRACE_KEY =
34-
AttributeKey.valueOf(HttpServerInstrumentation.RequestHandler.class, "REQUEST_HANDLER_TRACE");
34+
AttributeKey.valueOf(HttpServerInstrumentation.RequestHandler.class, "REQUEST_HANDLER_TRACE");
3535

3636
private final Map<Integer, HttpServerInstrumentation> serverInstrumentationMap;
3737

@@ -47,15 +47,23 @@ public HttpResponse serve(ServiceRequestContext ctx, HttpRequest req) throws Exc
4747
if (httpServerInstrumentation != null) {
4848

4949
final HttpServerInstrumentation.RequestHandler requestHandler =
50-
httpServerInstrumentation.createHandler(KamonArmeriaMessageConverter.toRequest(req));
50+
httpServerInstrumentation.createHandler(KamonArmeriaMessageConverter.toRequest(req));
5151

5252
ctx.log()
53-
.whenComplete()
54-
.thenAccept(log -> {
55-
final Context context = ctx.attr(REQUEST_HANDLER_TRACE_KEY).context();
56-
requestHandler.buildResponse(KamonArmeriaMessageConverter.toResponse(log),context);
57-
requestHandler.responseSent();
58-
});
53+
.whenComplete()
54+
.thenAccept(log -> {
55+
final Context context = ctx.attr(REQUEST_HANDLER_TRACE_KEY).context();
56+
/**
57+
* This is true only when no configured Route was matched.
58+
* Cases where the route is fallback should be managed here {@link kamon.instrumentation.armeria.server.HandleNotFoundMethodAdvisor#around}
59+
*/
60+
if (!ctx.config().route().isFallback()) {
61+
requestHandler.span().name(ctx.config().route().patternString());
62+
}
63+
64+
requestHandler.buildResponse(KamonArmeriaMessageConverter.toResponse(log), context);
65+
requestHandler.responseSent();
66+
});
5967

6068
try (Storage.Scope ignored = Kamon.storeContext(requestHandler.context())) {
6169
ctx.setAttr(REQUEST_HANDLER_TRACE_KEY, requestHandler);

instrumentation/kamon-armeria/src/main/resources/reference.conf

-26
Original file line numberDiff line numberDiff line change
@@ -129,32 +129,6 @@ kamon.instrumentation.armeria {
129129
# a given request. Depending on the instrumented framework, it might be possible to apply this operation
130130
# name automatically or not, check the frameworks' instrumentation docs for more details.
131131
unhandled = "unhandled"
132-
133-
# FQCN for a HttpOperationNameGenerator implementation, or ony of the following shorthand forms:
134-
# - default: Uses the set default operation name
135-
# - method: Uses the request HTTP method as the operation name.
136-
#
137-
name-generator = "kamon.instrumentation.armeria.KamonArmeriaHttpOperationNameGenerator"
138-
139-
# Provides custom mappings from HTTP paths into operation names. Meant to be used in cases where the bytecode
140-
# instrumentation is not able to provide a sensible operation name that is free of high cardinality values.
141-
# For example, with the following configuration:
142-
# mappings {
143-
# "/organization/*/user/*/profile" = "/organization/:orgID/user/:userID/profile"
144-
# "/events/*/rsvps" = "EventRSVPs"
145-
# }
146-
#
147-
# Requests to "/organization/3651/user/39652/profile" and "/organization/22234/user/54543/profile" will have
148-
# the same operation name "/organization/:orgID/user/:userID/profile".
149-
#
150-
# Similarly, requests to "/events/aaa-bb-ccc/rsvps" and "/events/1234/rsvps" will have the same operation
151-
# name "EventRSVPs".
152-
#
153-
# The patterns are expressed as globs and the operation names are free form.
154-
#
155-
mappings {
156-
157-
}
158132
}
159133
}
160134
}

instrumentation/kamon-armeria/src/main/scala/kamon/instrumentation/armeria/server/ArmeriaHttpServerInstrumentation.scala

+16-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
package kamon.instrumentation.armeria.server
1717

1818
import java.util
19-
2019
import com.linecorp.armeria.common.HttpStatus
2120
import com.linecorp.armeria.server._
2221
import com.typesafe.config.Config
@@ -78,13 +77,27 @@ object HandleNotFoundMethodAdvisor {
7877
*/
7978
@Advice.OnMethodExit(onThrowable = classOf[Throwable])
8079
def around(@Advice.Argument(0) ctx: ServiceRequestContext,
80+
@Advice.Argument(1) routingCtx: RoutingContext,
8181
@Advice.Argument(2) statusException: HttpStatusException,
8282
@Advice.Thrown throwable: Throwable): Unit = {
8383
lazy val unhandledOperationName: String = Kamon.config().getConfig("kamon.instrumentation.armeria.server").getString("tracing.operations.unhandled")
8484

85+
val requestHandler = ctx.attr(REQUEST_HANDLER_TRACE_KEY)
86+
8587
if (throwable != null && statusException.httpStatus.code() == HttpStatus.NOT_FOUND.code()) {
86-
val requestHandler = ctx.attr(REQUEST_HANDLER_TRACE_KEY)
87-
requestHandler.span.name(unhandledOperationName)
88+
requestHandler.span.name(unhandledOperationName)
89+
} else {
90+
/**
91+
* If no exception was thrown then probably the request will be redirected because it doesn't ends with '/'.
92+
* So here we are trying to find the Service config that will handle the request if we add a '/' to the end because Armeria will do that.
93+
* This is the same strategy as the one Armeria uses here {@link com.linecorp.armeria.server.FallbackService# handleNotFound}
94+
*/
95+
val oldPath = routingCtx.path
96+
val newPath = oldPath + '/'
97+
val serviceConfig = ctx.config.virtualHost.findServiceConfig(routingCtx.overridePath(newPath))
98+
if (serviceConfig.isPresent) {
99+
requestHandler.span.name(serviceConfig.value().route().patternString())
100+
}
88101
}
89102
}
90103
}

instrumentation/kamon-armeria/src/test/resources/application.conf

-4
This file was deleted.

instrumentation/kamon-armeria/src/test/scala/kamon/instrumentation/armeria/server/ArmeriaGrpcServerTracingSpec.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class ArmeriaGrpcServerTracingSpec extends AnyWordSpec
4747

4848
eventually(timeout(3 seconds)) {
4949
val span = testSpanReporter().nextSpan().value
50-
span.operationName shouldBe "ArmeriaHelloService/Hello"
50+
span.operationName shouldBe "/kamon.armeria.instrumentation.grpc.ArmeriaHelloService/Hello"
5151
span.hasError shouldBe false
5252
span.metricTags.get(plain("component")) shouldBe "armeria.http.server"
5353
span.metricTags.get(plain("http.method")) shouldBe "POST"

instrumentation/kamon-armeria/src/test/scala/kamon/instrumentation/armeria/server/ArmeriaHttpServerTracingSpec.scala

+15-15
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class ArmeriaHttpServerTracingSpec extends AnyWordSpec
4747

4848
private def testSuite(protocol: String, interface: String, port: Int): Unit = {
4949

50-
val webClient = newWebClient(protocol,port)
50+
val webClient = newWebClient(protocol, port)
5151

5252
s"The Armeria $protocol server" when {
5353

@@ -75,8 +75,8 @@ class ArmeriaHttpServerTracingSpec extends AnyWordSpec
7575
val target = s"$protocol://$interface:$port/$pathNotFoundEndpoint"
7676
val expected = "unhandled"
7777

78-
val request = HttpRequest.of(RequestHeaders.of(HttpMethod.GET, pathNotFoundEndpoint))
79-
webClient.execute(request)
78+
val request = HttpRequest.of(RequestHeaders.of(HttpMethod.GET, pathNotFoundEndpoint))
79+
webClient.execute(request)
8080

8181
eventually(timeout(3 seconds)) {
8282
val span = testSpanReporter().nextSpan().value
@@ -91,12 +91,12 @@ class ArmeriaHttpServerTracingSpec extends AnyWordSpec
9191
}
9292

9393
"set operation name with path + http method" when {
94-
"resource doesn't exist" in {
95-
val target = s"$protocol://$interface:$port/$usersEndpoint/not-found"
96-
val expected = "/users/{}"
94+
"resource doesn't exist" in {
95+
val target = s"$protocol://$interface:$port/$usersEndpoint/not-found"
96+
val expected = "/users/:userId"
9797

98-
val request = HttpRequest.of(RequestHeaders.of(HttpMethod.GET, s"$usersEndpoint/not-found"))
99-
webClient.execute(request)
98+
val request = HttpRequest.of(RequestHeaders.of(HttpMethod.GET, s"$usersEndpoint/not-found"))
99+
webClient.execute(request)
100100

101101
eventually(timeout(3 seconds)) {
102102
val span = testSpanReporter().nextSpan().value
@@ -111,7 +111,7 @@ class ArmeriaHttpServerTracingSpec extends AnyWordSpec
111111
}
112112

113113
"not include path variables names" in {
114-
val expected = "/users/{}/accounts/{}"
114+
val expected = "/users/:userId/accounts/:accountId"
115115

116116
val request = HttpRequest.of(RequestHeaders.of(HttpMethod.GET, userAccountEndpoint))
117117
webClient.execute(request)
@@ -123,7 +123,7 @@ class ArmeriaHttpServerTracingSpec extends AnyWordSpec
123123
}
124124

125125
"not fail when request url contains special regexp chars" in {
126-
val expected = "/users/{}/accounts/{}"
126+
val expected = "/users/:userId/accounts/:accountId"
127127

128128
val request = HttpRequest.of(RequestHeaders.of(HttpMethod.GET, s"$userAccountEndpoint**"))
129129
val response = webClient.execute(request).aggregate().get()
@@ -137,7 +137,7 @@ class ArmeriaHttpServerTracingSpec extends AnyWordSpec
137137

138138
"mark spans as failed when request fails" in {
139139
val target = s"$protocol://$interface:$port/$usersEndpoint/error"
140-
val expected = "/users/{}"
140+
val expected = "/users/:userId"
141141

142142
val request = HttpRequest.of(RequestHeaders.of(HttpMethod.GET, s"$usersEndpoint/error"))
143143
webClient.execute(request)
@@ -156,9 +156,9 @@ class ArmeriaHttpServerTracingSpec extends AnyWordSpec
156156
"return a redirect status code" when {
157157
"a request to /docs is redirected to /docs/" in {
158158
val target = s"$protocol://$interface:$port/$docsEndpoint"
159-
val expected = s"/docs"
159+
val expected = s"/docs/*"
160160

161-
val request = HttpRequest.of(RequestHeaders.of(HttpMethod.GET, docsEndpoint))
161+
val request = HttpRequest.of(RequestHeaders.of(HttpMethod.GET, s"$docsEndpoint"))
162162
webClient.execute(request)
163163

164164
eventually(timeout(3 seconds)) {
@@ -172,10 +172,10 @@ class ArmeriaHttpServerTracingSpec extends AnyWordSpec
172172
}
173173
}
174174

175-
"return a ok status code " when {
175+
"return a ok status code" when {
176176
"a request to /docs/ is done" in {
177177
val target = s"$protocol://$interface:$port/$docsEndpoint/"
178-
val expected = s"/docs"
178+
val expected = s"/docs/*"
179179

180180
val request = HttpRequest.of(RequestHeaders.of(HttpMethod.GET, s"$docsEndpoint/"))
181181
webClient.execute(request)

0 commit comments

Comments
 (0)