Skip to content

Commit 5f0f3e1

Browse files
committed
+ kamon-play-25: add play 2.5.x support
1 parent 7d103e7 commit 5f0f3e1

20 files changed

+995
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
2+
3+
<aspectj>
4+
<aspects>
5+
<aspect name="kamon.play.instrumentation.RequestInstrumentation"/>
6+
<aspect name="kamon.play.instrumentation.WSInstrumentation"/>
7+
<aspect name="kamon.play.instrumentation.LoggerLikeInstrumentation"/>
8+
</aspects>
9+
10+
<weaver>
11+
<include within="play.api..*"/>
12+
</weaver>
13+
</aspectj>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# ================================== #
2+
# Kamon-Play Reference Configuration #
3+
# ================================== #
4+
5+
kamon {
6+
play {
7+
8+
# Header name used when propagating the `TraceContext.token` value across applications.
9+
trace-token-header-name = "X-Trace-Token"
10+
11+
# When set to true, Kamon will automatically set and propogate the `TraceContext.token` value under the following
12+
# conditions:
13+
# - When a server side request is received containing the trace token header, the new `TraceContext` will have that
14+
# some token, and once the response to that request is ready, the trace token header is also included in the
15+
# response.
16+
# - When a WS client request is issued and a `TraceContext` is available, the trace token header will be included
17+
# in the request headers.
18+
automatic-trace-token-propagation = true
19+
20+
# Fully qualified name of the implementation of kamon.play.NameGenerator that will be used for assigning names
21+
# to traces and client http segments.
22+
name-generator = kamon.play.DefaultNameGenerator
23+
24+
}
25+
26+
modules {
27+
kamon-play {
28+
requires-aspectj = yes
29+
}
30+
}
31+
}
32+
33+
#register the module with Play
34+
play.modules.enabled += "kamon.play.di.KamonModule"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* =========================================================================================
3+
* Copyright © 2013-2015 the kamon project <http://kamon.io/>
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
6+
* except in compliance with the License. You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software distributed under the
11+
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
12+
* either express or implied. See the License for the specific language governing permissions
13+
* and limitations under the License.
14+
* =========================================================================================
15+
*/
16+
17+
package kamon.play
18+
19+
import akka.util.ByteString
20+
import kamon.trace.Tracer
21+
import kamon.util.SameThreadExecutionContext
22+
import play.api.libs.streams.Accumulator
23+
import play.api.mvc.{ EssentialAction, EssentialFilter, RequestHeader, Result }
24+
25+
class KamonFilter extends EssentialFilter {
26+
27+
override def apply(next: EssentialAction): EssentialAction = new EssentialAction {
28+
override def apply(requestHeader: RequestHeader): Accumulator[ByteString, Result] = {
29+
30+
def onResult(result: Result): Result = {
31+
Tracer.currentContext.collect { ctx
32+
ctx.finish()
33+
PlayExtension.httpServerMetrics.recordResponse(ctx.name, result.header.status.toString)
34+
if (PlayExtension.includeTraceToken) result.withHeaders(PlayExtension.traceTokenHeaderName -> ctx.token)
35+
else result
36+
37+
} getOrElse result
38+
}
39+
40+
//override the current trace name
41+
Tracer.currentContext.rename(PlayExtension.generateTraceName(requestHeader))
42+
val nextAccumulator = next.apply(requestHeader)
43+
nextAccumulator.map(onResult)(SameThreadExecutionContext)
44+
}
45+
}
46+
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* =========================================================================================
3+
* Copyright © 2013-2015 the kamon project <http://kamon.io/>
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
6+
* except in compliance with the License. You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software distributed under the
11+
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
12+
* either express or implied. See the License for the specific language governing permissions
13+
* and limitations under the License.
14+
* =========================================================================================
15+
*/
16+
17+
package kamon.play
18+
19+
import akka.actor._
20+
import kamon.Kamon
21+
import kamon.util.http.HttpServerMetrics
22+
import kamon.util.logger.LazyLogger
23+
import play.api.libs.ws.WSRequest
24+
import play.api.mvc.RequestHeader
25+
26+
object PlayExtension {
27+
val SegmentLibraryName = "WS-client"
28+
29+
val log = LazyLogger("kamon.play.PlayExtension")
30+
31+
private val config = Kamon.config.getConfig("kamon.play")
32+
private val dynamic = new ReflectiveDynamicAccess(getClass.getClassLoader)
33+
val httpServerMetrics = Kamon.metrics.entity(HttpServerMetrics, "play-server")
34+
35+
val includeTraceToken: Boolean = config.getBoolean("automatic-trace-token-propagation")
36+
val traceTokenHeaderName: String = config.getString("trace-token-header-name")
37+
38+
private val nameGeneratorFQN = config.getString("name-generator")
39+
private val nameGenerator: NameGenerator = dynamic.createInstanceFor[NameGenerator](nameGeneratorFQN, Nil).get
40+
41+
def generateTraceName(requestHeader: RequestHeader): String = nameGenerator.generateTraceName(requestHeader)
42+
def generateHttpClientSegmentName(request: WSRequest): String = nameGenerator.generateHttpClientSegmentName(request)
43+
}
44+
45+
trait NameGenerator {
46+
def generateTraceName(requestHeader: RequestHeader): String
47+
def generateHttpClientSegmentName(request: WSRequest): String
48+
}
49+
50+
class DefaultNameGenerator extends NameGenerator {
51+
import scala.collection.concurrent.TrieMap
52+
import play.api.routing.Router
53+
import java.util.Locale
54+
import kamon.util.TriemapAtomicGetOrElseUpdate.Syntax
55+
56+
private val cache = TrieMap.empty[String, String]
57+
private val normalizePattern = """\$([^<]+)<[^>]+>""".r
58+
59+
def generateTraceName(requestHeader: RequestHeader): String = requestHeader.tags.get(Router.Tags.RouteVerb).map { verb
60+
val path = requestHeader.tags(Router.Tags.RoutePattern)
61+
cache.atomicGetOrElseUpdate(s"$verb$path", {
62+
val traceName = {
63+
// Convert paths of form GET /foo/bar/$paramname<regexp>/blah to foo.bar.paramname.blah.get
64+
val p = normalizePattern.replaceAllIn(path, "$1").replace('/', '.').dropWhile(_ == '.')
65+
val normalisedPath = {
66+
if (p.lastOption.exists(_ != '.')) s"$p."
67+
else p
68+
}
69+
s"$normalisedPath${verb.toLowerCase(Locale.ENGLISH)}"
70+
}
71+
traceName
72+
})
73+
} getOrElse "UntaggedTrace"
74+
75+
def generateHttpClientSegmentName(request: WSRequest): String = request.uri.getAuthority
76+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/* ===================================================
2+
* Copyright © 2013-2015 the kamon project <http://kamon.io/>
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
* ========================================================== */
16+
17+
package kamon.play.action
18+
19+
import kamon.trace.Tracer
20+
import play.api.mvc._
21+
import scala.concurrent.Future
22+
23+
case class TraceName[A](name: String)(action: Action[A]) extends Action[A] {
24+
def apply(request: Request[A]): Future[Result] = {
25+
Tracer.currentContext.rename(name)
26+
action(request)
27+
}
28+
lazy val parser = action.parser
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/* =========================================================================================
2+
* Copyright © 2013-2015 the kamon project <http://kamon.io/>
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License") you may not use this file
5+
* except in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the
10+
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
11+
* either express or implied. See the License for the specific language governing permissions
12+
* and limitations under the License.
13+
* =========================================================================================
14+
*/
15+
16+
package kamon.play.di
17+
18+
import javax.inject._
19+
import kamon.metric.MetricsModule
20+
import kamon.trace.TracerModule
21+
import play.api.inject.{ ApplicationLifecycle, Module }
22+
import play.api.{ Logger, Configuration, Environment }
23+
import scala.concurrent.Future
24+
25+
trait Kamon {
26+
def start(): Unit
27+
def shutdown(): Unit
28+
def metrics(): MetricsModule
29+
def tracer(): TracerModule
30+
}
31+
32+
class KamonModule extends Module {
33+
def bindings(environment: Environment, configuration: Configuration) = {
34+
Seq(bind[Kamon].to[KamonAPI].eagerly())
35+
}
36+
}
37+
38+
@Singleton
39+
class KamonAPI @Inject() (lifecycle: ApplicationLifecycle, environment: Environment) extends Kamon {
40+
private val log = Logger(classOf[KamonAPI])
41+
42+
log.info("Registering the Kamon Play Module.")
43+
44+
start() //force to start kamon eagerly on application startup
45+
46+
def start(): Unit = kamon.Kamon.start()
47+
def shutdown(): Unit = kamon.Kamon.shutdown()
48+
def metrics(): MetricsModule = kamon.Kamon.metrics
49+
def tracer(): TracerModule = kamon.Kamon.tracer
50+
51+
lifecycle.addStopHook { ()
52+
Future.successful(shutdown())
53+
}
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/* =========================================================================================
2+
* Copyright © 2013-2015 the kamon project <http://kamon.io/>
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5+
* except in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the
10+
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
11+
* either express or implied. See the License for the specific language governing permissions
12+
* and limitations under the License.
13+
* =========================================================================================
14+
*/
15+
16+
package kamon.play.instrumentation
17+
18+
import kamon.trace.logging.MdcKeysSupport
19+
import org.aspectj.lang.ProceedingJoinPoint
20+
import org.aspectj.lang.annotation._
21+
22+
@Aspect
23+
class LoggerLikeInstrumentation extends MdcKeysSupport {
24+
25+
@Pointcut("execution(* play.api.LoggerLike+.info(..))")
26+
def infoPointcut(): Unit = {}
27+
28+
@Pointcut("execution(* play.api.LoggerLike+.debug(..))")
29+
def debugPointcut(): Unit = {}
30+
31+
@Pointcut("execution(* play.api.LoggerLike+.warn(..))")
32+
def warnPointcut(): Unit = {}
33+
34+
@Pointcut("execution(* play.api.LoggerLike+.error(..))")
35+
def errorPointcut(): Unit = {}
36+
37+
@Pointcut("execution(* play.api.LoggerLike+.trace(..))")
38+
def tracePointcut(): Unit = {}
39+
40+
@Around("(infoPointcut() || debugPointcut() || warnPointcut() || errorPointcut() || tracePointcut())")
41+
def aroundLog(pjp: ProceedingJoinPoint): Any = withMdc {
42+
pjp.proceed()
43+
}
44+
}
45+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/* =========================================================================================
2+
* Copyright © 2013-2015 the kamon project <http://kamon.io/>
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5+
* except in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the
10+
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
11+
* either express or implied. See the License for the specific language governing permissions
12+
* and limitations under the License.
13+
* =========================================================================================
14+
*/
15+
16+
package kamon.play.instrumentation
17+
18+
import akka.util.ByteString
19+
import kamon.Kamon.tracer
20+
import kamon.play.{ KamonFilter, PlayExtension }
21+
import kamon.trace._
22+
import kamon.util.SameThreadExecutionContext
23+
import org.aspectj.lang.ProceedingJoinPoint
24+
import org.aspectj.lang.annotation._
25+
import play.api.libs.streams.Accumulator
26+
import play.api.mvc.Results._
27+
import play.api.mvc.{ EssentialFilter, _ }
28+
29+
@Aspect
30+
class RequestInstrumentation {
31+
32+
private lazy val filter: EssentialFilter = new KamonFilter()
33+
34+
@DeclareMixin("play.api.mvc.RequestHeader+")
35+
def mixinContextAwareToRequestHeader: TraceContextAware = TraceContextAware.default
36+
37+
@Before("call(* play.api.http.DefaultHttpRequestHandler.routeRequest(..)) && args(requestHeader)")
38+
def routeRequest(requestHeader: RequestHeader): Unit = {
39+
val token = if (PlayExtension.includeTraceToken) {
40+
requestHeader.headers.get(PlayExtension.traceTokenHeaderName)
41+
} else None
42+
43+
Tracer.setCurrentContext(tracer.newContext("UnnamedTrace", token))
44+
}
45+
46+
@Around("call(* play.api.http.HttpFilters.filters(..))")
47+
def filters(pjp: ProceedingJoinPoint): Any = {
48+
pjp.proceed().asInstanceOf[Seq[EssentialFilter]] :+ filter
49+
}
50+
51+
@Before("call(* play.api.http.HttpErrorHandler.onClientServerError(..)) && args(requestContextAware, statusCode, *)")
52+
def onClientError(requestContextAware: TraceContextAware, statusCode: Int): Unit = {
53+
requestContextAware.traceContext.collect { ctx
54+
PlayExtension.httpServerMetrics.recordResponse(ctx.name, statusCode.toString)
55+
}
56+
}
57+
58+
@Before("call(* play.api.http.HttpErrorHandler.onServerError(..)) && args(requestContextAware, ex)")
59+
def onServerError(requestContextAware: TraceContextAware, ex: Throwable): Unit = {
60+
requestContextAware.traceContext.collect { ctx
61+
PlayExtension.httpServerMetrics.recordResponse(ctx.name, InternalServerError.header.status.toString)
62+
}
63+
}
64+
}

0 commit comments

Comments
 (0)