Skip to content
This repository was archived by the owner on Mar 29, 2020. It is now read-only.

Commit 39997d7

Browse files
authored
Merge pull request #73 from ivantopo/keep-assigned-http-operation-name
keep any operation name assigned by the HTTP Server instrumentation
2 parents 6337eec + f7329ad commit 39997d7

File tree

8 files changed

+261
-32
lines changed

8 files changed

+261
-32
lines changed

build.sbt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ resolvers += Resolver.mavenLocal
1818
resolvers += Resolver.bintrayRepo("hseeberger", "maven")
1919

2020

21-
val kamonCore = "io.kamon" %% "kamon-core" % "2.0.0"
22-
val kamonTestKit = "io.kamon" %% "kamon-testkit" % "2.0.0"
23-
val kamonCommon = "io.kamon" %% "kamon-instrumentation-common" % "2.0.0"
24-
val kamonAkka25 = "io.kamon" %% "kamon-akka" % "2.0.0"
25-
val kanelaAgent = "io.kamon" % "kanela-agent" % "1.0.1"
21+
val kamonCore = "io.kamon" %% "kamon-core" % "2.0.3"
22+
val kamonTestKit = "io.kamon" %% "kamon-testkit" % "2.0.3"
23+
val kamonCommon = "io.kamon" %% "kamon-instrumentation-common" % "2.0.1"
24+
val kamonAkka25 = "io.kamon" %% "kamon-akka" % "2.0.1"
25+
val kanelaAgent = "io.kamon" % "kanela-agent" % "1.0.4"
2626

2727
val akkaHttpJson = "de.heikoseeberger" %% "akka-http-json4s" % "1.27.0"
2828
val json4sNative = "org.json4s" %% "json4s-native" % "3.6.7"

kamon-akka-http/src/main/scala-2.11/kamon/instrumentation/akka/http/AkkaHttpServerInstrumentation.scala

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -179,17 +179,25 @@ object ResolveOperationNameOnRouteInterceptor {
179179
// by accessing the Span and changing it directly there, so we wouldn't want to overwrite that.
180180

181181
Kamon.currentContext().get(LastAutomaticOperationNameEdit.Key).foreach(lastEdit => {
182-
if(Kamon.currentSpan().operationName() == lastEdit.operationName) {
182+
val currentSpan = Kamon.currentSpan()
183+
184+
if(lastEdit.allowAutomaticChanges) {
185+
if(currentSpan.operationName() == lastEdit.operationName) {
183186
val allMatches = requestContext.asInstanceOf[HasMatchingContext].matchingContext.reverse.map(singleMatch)
184187
val operationName = allMatches.mkString("")
185188

186189
if(operationName.nonEmpty) {
187-
Kamon.currentSpan()
190+
currentSpan
188191
.name(operationName)
189192
.takeSamplingDecision()
190193

191194
lastEdit.operationName = operationName
192195
}
196+
} else {
197+
lastEdit.allowAutomaticChanges = false
198+
}
199+
} else {
200+
currentSpan.takeSamplingDecision()
193201
}
194202
})
195203

@@ -220,13 +228,27 @@ object ResolveOperationNameOnRouteInterceptor {
220228
}
221229
}
222230

223-
class LastAutomaticOperationNameEdit(@volatile var operationName: String)
231+
/**
232+
* Tracks the last operation name that was automatically assigned to an operation via instrumentation. The
233+
* instrumentation might assign a name to the operations via settings on the HTTP Server instrumentation instance or
234+
* via the Path directives instrumentation, but might never reassign a name if the user somehow assigned their own name
235+
* to the operation. Users chan change operation names by:
236+
* - Using operation mappings via configuration of the HTTP Server.
237+
* - Providing a custom HTTP Operation Name Generator for the server.
238+
* - Using the "operationName" directive.
239+
* - Directly accessing the Span for the current operation and changing the name on it.
240+
*
241+
*/
242+
class LastAutomaticOperationNameEdit(
243+
@volatile var operationName: String,
244+
@volatile var allowAutomaticChanges: Boolean
245+
)
224246

225247
object LastAutomaticOperationNameEdit {
226248
val Key = Context.key[Option[LastAutomaticOperationNameEdit]]("laone", None)
227249

228-
def apply(operationName: String): LastAutomaticOperationNameEdit =
229-
new LastAutomaticOperationNameEdit(operationName)
250+
def apply(operationName: String, allowAutomaticChanges: Boolean): LastAutomaticOperationNameEdit =
251+
new LastAutomaticOperationNameEdit(operationName, allowAutomaticChanges)
230252
}
231253

232254
object RequestContextCopyInterceptor {

kamon-akka-http/src/main/scala-2.12/kamon/instrumentation/akka/http/AkkaHttpServerInstrumentation.scala

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -179,17 +179,25 @@ object ResolveOperationNameOnRouteInterceptor {
179179
// by accessing the Span and changing it directly there, so we wouldn't want to overwrite that.
180180

181181
Kamon.currentContext().get(LastAutomaticOperationNameEdit.Key).foreach(lastEdit => {
182-
if(Kamon.currentSpan().operationName() == lastEdit.operationName) {
182+
val currentSpan = Kamon.currentSpan()
183+
184+
if(lastEdit.allowAutomaticChanges) {
185+
if(currentSpan.operationName() == lastEdit.operationName) {
183186
val allMatches = requestContext.asInstanceOf[HasMatchingContext].matchingContext.reverse.map(singleMatch)
184187
val operationName = allMatches.mkString("")
185188

186189
if(operationName.nonEmpty) {
187-
Kamon.currentSpan()
190+
currentSpan
188191
.name(operationName)
189192
.takeSamplingDecision()
190193

191194
lastEdit.operationName = operationName
192195
}
196+
} else {
197+
lastEdit.allowAutomaticChanges = false
198+
}
199+
} else {
200+
currentSpan.takeSamplingDecision()
193201
}
194202
})
195203

@@ -220,13 +228,27 @@ object ResolveOperationNameOnRouteInterceptor {
220228
}
221229
}
222230

223-
class LastAutomaticOperationNameEdit(@volatile var operationName: String)
231+
/**
232+
* Tracks the last operation name that was automatically assigned to an operation via instrumentation. The
233+
* instrumentation might assign a name to the operations via settings on the HTTP Server instrumentation instance or
234+
* via the Path directives instrumentation, but might never reassign a name if the user somehow assigned their own name
235+
* to the operation. Users chan change operation names by:
236+
* - Using operation mappings via configuration of the HTTP Server.
237+
* - Providing a custom HTTP Operation Name Generator for the server.
238+
* - Using the "operationName" directive.
239+
* - Directly accessing the Span for the current operation and changing the name on it.
240+
*
241+
*/
242+
class LastAutomaticOperationNameEdit(
243+
@volatile var operationName: String,
244+
@volatile var allowAutomaticChanges: Boolean
245+
)
224246

225247
object LastAutomaticOperationNameEdit {
226248
val Key = Context.key[Option[LastAutomaticOperationNameEdit]]("laone", None)
227249

228-
def apply(operationName: String): LastAutomaticOperationNameEdit =
229-
new LastAutomaticOperationNameEdit(operationName)
250+
def apply(operationName: String, allowAutomaticChanges: Boolean): LastAutomaticOperationNameEdit =
251+
new LastAutomaticOperationNameEdit(operationName, allowAutomaticChanges)
230252
}
231253

232254
object RequestContextCopyInterceptor {

kamon-akka-http/src/main/scala-2.13/kamon/instrumentation/akka/http/AkkaHttpServerInstrumentation.scala

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -179,17 +179,25 @@ object ResolveOperationNameOnRouteInterceptor {
179179
// by accessing the Span and changing it directly there, so we wouldn't want to overwrite that.
180180

181181
Kamon.currentContext().get(LastAutomaticOperationNameEdit.Key).foreach(lastEdit => {
182-
if(Kamon.currentSpan().operationName() == lastEdit.operationName) {
183-
val allMatches = requestContext.asInstanceOf[HasMatchingContext].matchingContext.reverse.map(singleMatch)
184-
val operationName = allMatches.mkString("")
182+
val currentSpan = Kamon.currentSpan()
185183

186-
if(operationName.nonEmpty) {
187-
Kamon.currentSpan()
188-
.name(operationName)
189-
.takeSamplingDecision()
184+
if(lastEdit.allowAutomaticChanges) {
185+
if(currentSpan.operationName() == lastEdit.operationName) {
186+
val allMatches = requestContext.asInstanceOf[HasMatchingContext].matchingContext.reverse.map(singleMatch)
187+
val operationName = allMatches.mkString("")
190188

191-
lastEdit.operationName = operationName
189+
if (operationName.nonEmpty) {
190+
currentSpan
191+
.name(operationName)
192+
.takeSamplingDecision()
193+
194+
lastEdit.operationName = operationName
195+
}
196+
} else {
197+
lastEdit.allowAutomaticChanges = false
192198
}
199+
} else {
200+
currentSpan.takeSamplingDecision()
193201
}
194202
})
195203

@@ -220,13 +228,27 @@ object ResolveOperationNameOnRouteInterceptor {
220228
}
221229
}
222230

223-
class LastAutomaticOperationNameEdit(@volatile var operationName: String)
231+
/**
232+
* Tracks the last operation name that was automatically assigned to an operation via instrumentation. The
233+
* instrumentation might assign a name to the operations via settings on the HTTP Server instrumentation instance or
234+
* via the Path directives instrumentation, but might never reassign a name if the user somehow assigned their own name
235+
* to the operation. Users chan change operation names by:
236+
* - Using operation mappings via configuration of the HTTP Server.
237+
* - Providing a custom HTTP Operation Name Generator for the server.
238+
* - Using the "operationName" directive.
239+
* - Directly accessing the Span for the current operation and changing the name on it.
240+
*
241+
*/
242+
class LastAutomaticOperationNameEdit(
243+
@volatile var operationName: String,
244+
@volatile var allowAutomaticChanges: Boolean
245+
)
224246

225247
object LastAutomaticOperationNameEdit {
226248
val Key = Context.key[Option[LastAutomaticOperationNameEdit]]("laone", None)
227249

228-
def apply(operationName: String): LastAutomaticOperationNameEdit =
229-
new LastAutomaticOperationNameEdit(operationName)
250+
def apply(operationName: String, allowAutomaticChanges: Boolean): LastAutomaticOperationNameEdit =
251+
new LastAutomaticOperationNameEdit(operationName, allowAutomaticChanges)
230252
}
231253

232254
object RequestContextCopyInterceptor {

kamon-akka-http/src/main/scala/kamon/instrumentation/akka/http/ServerFlowWrapper.scala

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,20 @@ object ServerFlowWrapper {
6565
val requestHandler = httpServerInstrumentation.createHandler(toRequest(request), deferSamplingDecision = true)
6666
.requestReceived()
6767

68+
val defaultOperationName = httpServerInstrumentation.settings.defaultOperationName
69+
val requestSpan = requestHandler.span
70+
71+
// Automatic Operation name changes will only be allowed if the initial name HTTP Server didn't assign a
72+
// user-defined name to the operation (that would be, either via an Operation Name Generator or a custom
73+
// operation mapping).
74+
val allowAutomaticChanges = requestSpan.operationName() == defaultOperationName
75+
6876
_pendingRequests.enqueue(requestHandler)
6977

7078
// The only reason why it's safe to leave the Thread dirty is because the Actor
7179
// instrumentation will cleanup afterwards.
7280
Kamon.storeContext(requestHandler.context.withEntry(
73-
LastAutomaticOperationNameEdit.Key, Option(LastAutomaticOperationNameEdit(requestHandler.span.operationName()))
81+
LastAutomaticOperationNameEdit.Key, Option(LastAutomaticOperationNameEdit(requestSpan.operationName(), allowAutomaticChanges))
7482
))
7583

7684
push(requestOut, request)

kamon-akka-http/src/test/resources/application.conf

Lines changed: 147 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,150 @@ kamon {
22
trace.sampler = "always"
33
}
44

5-
akka.http.server.preview.enable-http2 = on
5+
akka.http.server.preview.enable-http2 = on
6+
7+
8+
9+
kamon.instrumentation.akka.http {
10+
11+
# Settings to control the HTTP Server instrumentation.
12+
#
13+
# IMPORTANT: Besides the "initial-operation-name" and "unhandled-operation-name" settings, the entire configuration of
14+
# the HTTP Server Instrumentation is based on the constructs provided by the Kamon Instrumentation Common library
15+
# which will always fallback to the settings found under the "kamon.instrumentation.http-server.default" path. The
16+
# default settings have been included here to make them easy to find and understand in the context of this project and
17+
# commented out so that any changes to the default settings will actually have effect.
18+
#
19+
server {
20+
21+
#
22+
# Configuration for HTTP context propagation.
23+
#
24+
propagation {
25+
26+
# Enables or disables HTTP context propagation on this HTTP server instrumentation. Please note that if
27+
# propagation is disabled then some distributed tracing features will not be work as expected (e.g. Spans can
28+
# be created and reported but will not be linked across boundaries nor take trace identifiers from tags).
29+
#enabled = yes
30+
31+
# HTTP propagation channel to b used by this instrumentation. Take a look at the kamon.propagation.http.default
32+
# configuration for more details on how to configure the detault HTTP context propagation.
33+
#channel = "default"
34+
}
35+
36+
37+
#
38+
# Configuration for HTTP server metrics collection.
39+
#
40+
metrics {
41+
42+
# Enables collection of HTTP server metrics. When enabled the following metrics will be collected, assuming
43+
# that the instrumentation is fully compliant:
44+
#
45+
# - http.server.requets
46+
# - http.server.request.active
47+
# - http.server.request.size
48+
# - http.server.response.size
49+
# - http.server.connection.lifetime
50+
# - http.server.connection.usage
51+
# - http.server.connection.open
52+
#
53+
# All metrics have at least three tags: component, interface and port. Additionally, the http.server.requests
54+
# metric will also have a status_code tag with the status code group (1xx, 2xx and so on).
55+
#
56+
#enabled = yes
57+
}
58+
59+
60+
#
61+
# Configuration for HTTP request tracing.
62+
#
63+
tracing {
64+
65+
# Enables HTTP request tracing. When enabled the instrumentation will create Spans for incoming requests
66+
# and finish them when the response is sent back to the clients.
67+
#enabled = yes
68+
69+
# Select a context tag that provides a preferred trace identifier. The preferred trace identifier will be used
70+
# only if all these conditions are met:
71+
# - the context tag is present.
72+
# - there is no parent Span on the incoming context (i.e. this is the first service on the trace).
73+
# - the identifier is valid in accordance to the identity provider.
74+
#preferred-trace-id-tag = "none"
75+
76+
# Enables collection of span metrics using the `span.processing-time` metric.
77+
#span-metrics = on
78+
79+
# Select which tags should be included as span and span metric tags. The possible options are:
80+
# - span: the tag is added as a Span tag (i.e. using span.tag(...))
81+
# - metric: the tag is added a a Span metric tag (i.e. using span.tagMetric(...))
82+
# - off: the tag is not used.
83+
#
84+
tags {
85+
86+
# Use the http.url tag.
87+
#url = span
88+
89+
# Use the http.method tag.
90+
#method = metric
91+
92+
# Use the http.status_code tag.
93+
#status-code = metric
94+
95+
# Copy tags from the context into the Spans with the specified purpouse. For example, to copy a customer_type
96+
# tag from the context into the HTTP Server Span created by the instrumentation, the following configuration
97+
# should be added:
98+
#
99+
# from-context {
100+
# customer_type = span
101+
# }
102+
#
103+
from-context {
104+
105+
}
106+
}
107+
108+
# Controls writing trace and span identifiers to HTTP response headers sent by the instrumented servers. The
109+
# configuration can be set to either "none" to disable writing the identifiers on the response headers or to
110+
# the header name to be used when writing the identifiers.
111+
response-headers {
112+
113+
# HTTP response header name for the trace identifier, or "none" to disable it.
114+
#trace-id = "trace-id"
115+
116+
# HTTP response header name for the server span identifier, or "none" to disable it.
117+
#span-id = none
118+
}
119+
120+
# Custom mappings between routes and operation names.
121+
operations {
122+
123+
# The default operation name to be used when creating Spans to handle the HTTP server requests. In most
124+
# cases it is not possible to define an operation name right at the moment of starting the HTTP server Span
125+
# and in those cases, this operation name will be initially assigned to the Span. Instrumentation authors
126+
# should do their best effort to provide a suitable operation name or make use of the "mappings" facilities.
127+
#default = "http.server.request"
128+
129+
# Provides custom mappings from HTTP paths into operation names. Meant to be used in cases where the bytecode
130+
# instrumentation is not able to provide a sensible operation name that is free of high cardinality values.
131+
# For example, with the following configuration:
132+
# mappings {
133+
# "/organization/*/user/*/profile" = "/organization/:orgID/user/:userID/profile"
134+
# "/events/*/rsvps" = "EventRSVPs"
135+
# }
136+
#
137+
# Requests to "/organization/3651/user/39652/profile" and "/organization/22234/user/54543/profile" will have
138+
# the same operation name "/organization/:orgID/user/:userID/profile".
139+
#
140+
# Similarly, requests to "/events/aaa-bb-ccc/rsvps" and "/events/1234/rsvps" will have the same operation
141+
# name "EventRSVPs".
142+
#
143+
# The patterns are expressed as globs and the operation names are free form.
144+
#
145+
mappings {
146+
"/name-will-be-changed" = "named-via-config"
147+
}
148+
}
149+
}
150+
}
151+
}

0 commit comments

Comments
 (0)