Skip to content

Commit 4b397cb

Browse files
authored
Merge pull request #926 from yarosman/master-play-route-name-generator
Allow to define custom router name generator for play server
2 parents ffcdcc6 + 1fada3d commit 4b397cb

File tree

5 files changed

+118
-20
lines changed

5 files changed

+118
-20
lines changed

instrumentation/kamon-play/build.sbt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import sbt.Tests._
22
import play.grpc.gen.scaladsl.{PlayScalaServerCodeGenerator, PlayScalaClientCodeGenerator}
33

4-
val `Play-2.6-version` = "2.6.23"
5-
val `Play-2.7-version` = "2.7.3"
6-
val `Play-2.8-version` = "2.8.2"
4+
val `Play-2.6-version` = "2.6.25"
5+
val `Play-2.7-version` = "2.7.9"
6+
val `Play-2.8-version` = "2.8.7"
77

88
/**
99
* Test Configurations
@@ -45,8 +45,8 @@ libraryDependencies ++= { if(scalaBinaryVersion.value == "2.13") Seq.empty else
4545
)}
4646

4747
libraryDependencies ++= { if(scalaBinaryVersion.value == "2.11") Seq.empty else Seq(
48-
"com.lightbend.play" %% "play-grpc-runtime" % "0.9.0" % "test-play-2.8",
49-
"com.lightbend.play" %% "play-grpc-scalatest" % "0.9.0" % "test-play-2.8",
48+
"com.lightbend.play" %% "play-grpc-runtime" % "0.9.1" % "test-play-2.8",
49+
"com.lightbend.play" %% "play-grpc-scalatest" % "0.9.1" % "test-play-2.8",
5050
"com.typesafe.play" %% "play-akka-http2-support" % `Play-2.8-version` % "test-play-2.8",
5151
"com.typesafe.play" %% "play" % `Play-2.8-version` % "test-play-2.8",
5252
"com.typesafe.play" %% "play-netty-server" % `Play-2.8-version` % "test-play-2.8",
@@ -73,12 +73,12 @@ lazy val baseTestSettings = Seq(
7373
)
7474

7575
inConfig(TestCommon)(Defaults.testSettings ++ instrumentationSettings ++ baseTestSettings ++ Seq(
76-
crossScalaVersions := Seq("2.11.12", "2.12.11")
76+
crossScalaVersions := Seq("2.11.12", "2.12.13")
7777
))
7878

7979
inConfig(`Test-Play-2.6`)(Defaults.testSettings ++ instrumentationSettings ++ baseTestSettings ++ Seq(
8080
sources := joinSources(TestCommon, `Test-Play-2.6`).value,
81-
crossScalaVersions := Seq("2.11.12", "2.12.11"),
81+
crossScalaVersions := Seq("2.11.12", "2.12.13"),
8282
testGrouping := singleTestPerJvm(definedTests.value, javaOptions.value),
8383
unmanagedResourceDirectories ++= (unmanagedResourceDirectories in Compile).value,
8484
unmanagedResourceDirectories ++= (unmanagedResourceDirectories in TestCommon).value,
@@ -93,7 +93,7 @@ inConfig(`Test-Play-2.7`)(Defaults.testSettings ++ instrumentationSettings ++ ba
9393

9494
inConfig(`Test-Play-2.8`)(Defaults.testSettings ++ instrumentationSettings ++ baseTestSettings ++ Seq(
9595
sources := joinSources(TestCommon, `Test-Play-2.8`).value,
96-
crossScalaVersions := Seq("2.12.11", "2.13.1"),
96+
crossScalaVersions := Seq("2.12.13", "2.13.3"),
9797
akkaGrpcGeneratedSources := Seq(AkkaGrpc.Server, AkkaGrpc.Client),
9898
akkaGrpcGeneratedLanguages := Seq(AkkaGrpc.Scala),
9999
akkaGrpcExtraGenerators += PlayScalaServerCodeGenerator,

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,12 @@ kamon.instrumentation.play.http {
107107
# Custom mappings between routes and operation names.
108108
operations {
109109

110+
# Default configuration for HttpOperationNameGenerator implementation, but it has never been used
111+
# - default: Uses the set default operation name
112+
# - method: Uses the request HTTP method as the operation name.
113+
#
114+
name-generator = "default"
115+
110116
# The default operation name to be used when creating Spans to handle the HTTP server requests. In most
111117
# cases it is not possible to define an operation name right at the moment of starting the HTTP server Span
112118
# and in those cases, this operation name will be initially assigned to the Span. Instrumentation authors
@@ -139,6 +145,29 @@ kamon.instrumentation.play.http {
139145
}
140146
}
141147
}
148+
149+
#
150+
# Configure specefic tracing/monitoring elements which depends on `play-framework` internal APIs that might change in the future
151+
#
152+
extra {
153+
# Framework-specific way to generate operations names instead of using `kamon.instrumentation.play.http.server.tracing.operations.name-generator`
154+
# Used by default with implementations that converts paths of form `/foo/bar/$paramname<regexp>/blah` to `/foo/bar/paramname/blah`
155+
#
156+
# FQCN for a kamon.instrumentation.play.RouterOperationNameGenerator implementation, or ony of the following shorthand forms:
157+
# - default: Uses the set default operation name
158+
# - method: Uses the request HTTP method as the operation name.
159+
#
160+
name-generator = "kamon.instrumentation.play.DefaultRouterOperationNameGenerator"
161+
162+
# Framework-specific way to generate operations names instead of using `kamon.instrumentation.play.http.server.tracing.operations.name-generator`
163+
# Used by default with setting operation name to `request.uri.toRelative`
164+
#
165+
# FQCN for a kamon.instrumentation.play.GrpcRouterNameGenerator implementation, or ony of the following shorthand forms:
166+
# - default: Uses the set default operation name
167+
# - method: Uses the request HTTP method as the operation name.
168+
#
169+
grpc-name-generator = "kamon.instrumentation.play.DefaultGrpcRouterNameGenerator"
170+
}
142171
}
143172

144173
client {
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package kamon.instrumentation.play
2+
3+
import akka.http.scaladsl.model.HttpRequest
4+
5+
trait GrpcRouterNameGenerator {
6+
7+
def generateOperationName(request: akka.http.scaladsl.model.HttpRequest): String
8+
9+
}
10+
11+
class DefaultGrpcRouterNameGenerator extends GrpcRouterNameGenerator {
12+
13+
override def generateOperationName(request: HttpRequest): String = request.uri.toRelative.toString
14+
15+
}

instrumentation/kamon-play/src/main/scala/kamon/instrumentation/play/PlayServerInstrumentation.scala

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@ package kamon.instrumentation.play
1919
import java.time.Duration
2020
import java.util.concurrent.atomic.AtomicLong
2121

22+
import com.typesafe.config.Config
2223
import io.netty.channel.Channel
2324
import io.netty.handler.codec.http.{HttpRequest, HttpResponse}
2425
import io.netty.util.concurrent.GenericFutureListener
25-
import kamon.Kamon
26+
import kamon.{ClassLoading, Kamon}
2627
import kamon.context.Storage
2728
import kamon.instrumentation.akka.http.ServerFlowWrapper
2829
import kamon.instrumentation.context.{CaptureCurrentTimestampOnExit, HasTimestamp}
@@ -33,12 +34,13 @@ import kanela.agent.api.instrumentation.InstrumentationBuilder
3334
import kanela.agent.api.instrumentation.classloader.ClassRefiner
3435
import kanela.agent.api.instrumentation.mixin.Initializer
3536
import kanela.agent.libs.net.bytebuddy.asm.Advice
37+
import org.slf4j.LoggerFactory
3638
import play.api.mvc.RequestHeader
3739
import play.api.routing.{HandlerDef, Router}
3840
import play.core.server.NettyServer
3941

4042
import scala.collection.JavaConverters.asScalaBufferConverter
41-
import scala.collection.concurrent.TrieMap
43+
import scala.util.Try
4244
import scala.util.{Failure, Success}
4345

4446
class PlayServerInstrumentation extends InstrumentationBuilder {
@@ -243,32 +245,61 @@ object HasServerInstrumentation {
243245

244246
object GenerateOperationNameOnFilterHandler {
245247

246-
private val _operationNameCache = TrieMap.empty[String, String]
247-
private val _normalizePattern = """\$([^<]+)<[^>]+>""".r
248+
private val defaultRouterNameGenerator = new DefaultRouterOperationNameGenerator()
249+
private val _logger = LoggerFactory.getLogger(GenerateOperationNameOnFilterHandler.getClass)
250+
251+
@volatile private var _routerNameGenerator: RouterOperationNameGenerator = rebuildRouterNameGenerator(Kamon.config())
252+
253+
Kamon.onReconfigure(newConfig => _routerNameGenerator = rebuildRouterNameGenerator(newConfig))
254+
255+
private def rebuildRouterNameGenerator(config: Config): RouterOperationNameGenerator = {
256+
val nameGeneratorClazz = config.getString("kamon.instrumentation.play.http.server.extra.name-generator")
257+
Try(ClassLoading.createInstance[RouterOperationNameGenerator](nameGeneratorClazz)) match {
258+
case Failure(exception) =>
259+
_logger.error(s"Exception occurred on $nameGeneratorClazz instance creation, used default", exception)
260+
defaultRouterNameGenerator
261+
case Success(value) =>
262+
value
263+
}
264+
}
248265

249266
@Advice.OnMethodEnter
250267
def enter(@Advice.Argument(0) request: RequestHeader): Unit = {
251268
request.attrs.get(Router.Attrs.HandlerDef).map(handler => {
252269
val span = Kamon.currentSpan()
253-
span.name(generateOperationName(handler))
270+
span.name(_routerNameGenerator.generateOperationName(handler))
254271
span.takeSamplingDecision()
255272
})
256273
}
257274

258-
private def generateOperationName(handlerDef: HandlerDef): String =
259-
_operationNameCache.getOrElseUpdate(handlerDef.path, {
260-
// Convert paths of form /foo/bar/$paramname<regexp>/blah to /foo/bar/paramname/blah
261-
_normalizePattern.replaceAllIn(handlerDef.path, "$1")
262-
})
263-
264275
}
265276

266277
object GenerateGRPCOperationName {
278+
279+
private val defaultGrpcRouterNameGenerator = new DefaultGrpcRouterNameGenerator()
280+
private val _logger = LoggerFactory.getLogger(GenerateOperationNameOnFilterHandler.getClass)
281+
282+
@volatile private var _grpcRouterNameGenerator: GrpcRouterNameGenerator = rebuildRouterNameGenerator(Kamon.config())
283+
284+
Kamon.onReconfigure(newConfig => _grpcRouterNameGenerator = rebuildRouterNameGenerator(newConfig))
285+
286+
private def rebuildRouterNameGenerator(config: Config): GrpcRouterNameGenerator = {
287+
val nameGeneratorClazz = config.getString("kamon.instrumentation.play.http.server.extra.grpc-name-generator")
288+
Try(ClassLoading.createInstance[GrpcRouterNameGenerator](nameGeneratorClazz)) match {
289+
case Failure(exception) =>
290+
_logger.error(s"Exception occurred on $nameGeneratorClazz instance creation, used default", exception)
291+
defaultGrpcRouterNameGenerator
292+
case Success(value) =>
293+
value
294+
}
295+
}
296+
267297
@Advice.OnMethodEnter
268298
def enter(@Advice.Argument(0) request: akka.http.scaladsl.model.HttpRequest): Unit = {
269299
val span = Kamon.currentSpan()
270-
span.name(request.uri.toRelative.toString)
300+
span.name(_grpcRouterNameGenerator.generateOperationName(request))
271301
span.tag("http.protocol", request.protocol.value)
272302
span.takeSamplingDecision()
273303
}
304+
274305
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package kamon.instrumentation.play
2+
3+
import play.api.routing.HandlerDef
4+
5+
import scala.collection.concurrent.TrieMap
6+
7+
trait RouterOperationNameGenerator {
8+
def generateOperationName(handlerDef: HandlerDef): String
9+
}
10+
11+
class DefaultRouterOperationNameGenerator extends RouterOperationNameGenerator {
12+
13+
private val _operationNameCache = TrieMap.empty[String, String]
14+
private val _normalizePattern = """\$([^<]+)<[^>]+>""".r
15+
16+
def generateOperationName(handlerDef: HandlerDef): String = {
17+
_operationNameCache.getOrElseUpdate(handlerDef.path, {
18+
// Convert paths of form /foo/bar/$paramname<regexp>/blah to /foo/bar/paramname/blah
19+
_normalizePattern.replaceAllIn(handlerDef.path, "$1")
20+
})
21+
}
22+
23+
}

0 commit comments

Comments
 (0)