From 29068fc70a3e5a17a630c2c7fff951572bb5fa21 Mon Sep 17 00:00:00 2001 From: Ivan Topolnjak Date: Thu, 3 Jul 2014 14:36:42 -0300 Subject: ! all: refactor the core metric recording instruments and accomodate UserMetrics This PR is including several changes to the kamon-core, most notably: - Formalize the interface for Histograms, Counters and MinMaxCounters. Making sure that the interfaces are as clean as possible. - Move away from the all Vector[Measurement] based Histogram snapshot to a new approach in which we use a single long to store both the index in the counts array and the frequency on that bucket. The leftmost 2 bytes of each long are used for storing the counts array index and the remaining 6 bytes are used for the actual count, and everything is put into a simple long array. This way only the buckets that actually have values will be included in the snapshot with the smallest possible memory footprint. - Introduce Gauges. - Reorganize the instrumentation for Akka and Scala and rewrite most of the tests of this components to avoid going through the subscription protocol to test. - Introduce trace tests and fixes on various tests. - Necessary changes on new relic, datadog and statsd modules to compile with the new codebase. Pending: - Finish the upgrade of the new relic to the current model. - Introduce proper limit checks for histograms to ensure that we never pass the 2/6 bytes limits. - More testing, more testing, more testing. - Create the KamonStandalone module. --- kamon-play/src/test/scala/kamon/play/WSInstrumentationSpec.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'kamon-play/src/test') diff --git a/kamon-play/src/test/scala/kamon/play/WSInstrumentationSpec.scala b/kamon-play/src/test/scala/kamon/play/WSInstrumentationSpec.scala index 0c3783bb..775d3e26 100644 --- a/kamon-play/src/test/scala/kamon/play/WSInstrumentationSpec.scala +++ b/kamon-play/src/test/scala/kamon/play/WSInstrumentationSpec.scala @@ -28,9 +28,9 @@ import akka.testkit.{ TestKitBase, TestProbe } import com.typesafe.config.ConfigFactory import org.scalatest.{ Matchers, WordSpecLike } import kamon.Kamon -import kamon.metrics.{ TraceMetrics, Metrics } -import kamon.metrics.Subscriptions.TickMetricSnapshot -import kamon.metrics.TraceMetrics.ElapsedTime +import kamon.metric.{ TraceMetrics, Metrics } +import kamon.metric.Subscriptions.TickMetricSnapshot +import kamon.metric.TraceMetrics.ElapsedTime class WSInstrumentationSpec extends TestKitBase with WordSpecLike with Matchers with OneServerPerSuite { -- cgit v1.2.3 From a80de719ca8b7ef2d6d3c4a10ed1e212a5a1ea83 Mon Sep 17 00:00:00 2001 From: Diego Date: Sun, 6 Jul 2014 19:00:03 -0300 Subject: + play: introducing LoggerLikeInstrumentation in order to copy the properties of TraceLocalStorage to MDC --- kamon-play/src/main/resources/META-INF/aop.xml | 1 + .../LoggerLikeInstrumentation.scala | 70 ++++++++++++ .../instrumentation/RequestInstrumentation.scala | 23 ++-- .../kamon/play/LoggerLikeInstrumentationSpec.scala | 125 +++++++++++++++++++++ .../kamon/play/RequestInstrumentationSpec.scala | 21 ++-- 5 files changed, 217 insertions(+), 23 deletions(-) create mode 100644 kamon-play/src/main/scala/kamon/play/instrumentation/LoggerLikeInstrumentation.scala create mode 100644 kamon-play/src/test/scala/kamon/play/LoggerLikeInstrumentationSpec.scala (limited to 'kamon-play/src/test') diff --git a/kamon-play/src/main/resources/META-INF/aop.xml b/kamon-play/src/main/resources/META-INF/aop.xml index ca499a33..e24d48d5 100644 --- a/kamon-play/src/main/resources/META-INF/aop.xml +++ b/kamon-play/src/main/resources/META-INF/aop.xml @@ -4,6 +4,7 @@ + diff --git a/kamon-play/src/main/scala/kamon/play/instrumentation/LoggerLikeInstrumentation.scala b/kamon-play/src/main/scala/kamon/play/instrumentation/LoggerLikeInstrumentation.scala new file mode 100644 index 00000000..b7afeb76 --- /dev/null +++ b/kamon-play/src/main/scala/kamon/play/instrumentation/LoggerLikeInstrumentation.scala @@ -0,0 +1,70 @@ +/* ========================================================================================= + * Copyright © 2013-2014 the kamon project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + * ========================================================================================= + */ + +package kamon.play.instrumentation + +import kamon.trace.{ TraceContext, TraceContextAware } +import org.aspectj.lang.ProceedingJoinPoint +import org.aspectj.lang.annotation._ +import org.slf4j.MDC + +@Aspect +class LoggerLikeInstrumentation { + + import LoggerLikeInstrumentation._ + + @DeclareMixin("play.api.LoggerLike+") + def mixinContextAwareToLoggerLike: TraceContextAware = TraceContextAware.default + + @Pointcut("execution(* play.api.LoggerLike+.info(..))") + def infoPointcut(): Unit = {} + + @Pointcut("execution(* play.api.LoggerLike+.warn(..))") + def warnPointcut(): Unit = {} + + @Pointcut("execution(* play.api.LoggerLike+.error(..))") + def errorPointcut(): Unit = {} + + @Pointcut("execution(* play.api.LoggerLike+.trace(..))") + def tracePointcut(): Unit = {} + + @Around("(infoPointcut() || warnPointcut() || errorPointcut() || tracePointcut()) && this(logger)") + def aroundLog(pjp: ProceedingJoinPoint, logger: TraceContextAware): Any = { + withMDC(logger.traceContext) { + pjp.proceed() + } + } +} + +object LoggerLikeInstrumentation { + def withMDC[A](currentContext: Option[TraceContext])(block: ⇒ A): A = { + val keys = currentContext.map(extractProperties).map(putAndExtractKeys) + + try block finally keys.map(k ⇒ k.foreach(MDC.remove(_))) + } + + def putAndExtractKeys(values: Iterable[Map[String, Any]]): Iterable[String] = values.map { + value ⇒ value.map { case (key, value) ⇒ MDC.put(key, value.toString); key } + }.flatten + + def extractProperties(ctx: TraceContext): Iterable[Map[String, Any]] = ctx.traceLocalStorage.underlyingStorage.values.map { + case traceLocalValue @ (p: Product) ⇒ { + val properties = p.productIterator + traceLocalValue.getClass.getDeclaredFields.filter(field ⇒ field.getName != "$outer").map(_.getName -> properties.next).toMap + } + case anything ⇒ Map.empty[String, Any] + } +} + diff --git a/kamon-play/src/main/scala/kamon/play/instrumentation/RequestInstrumentation.scala b/kamon-play/src/main/scala/kamon/play/instrumentation/RequestInstrumentation.scala index 00170b1b..db864fb6 100644 --- a/kamon-play/src/main/scala/kamon/play/instrumentation/RequestInstrumentation.scala +++ b/kamon-play/src/main/scala/kamon/play/instrumentation/RequestInstrumentation.scala @@ -1,18 +1,17 @@ -/* =================================================== +/* ========================================================================================= * Copyright © 2013-2014 the kamon project * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ========================================================== */ + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + * ========================================================================================= + */ package kamon.play.instrumentation @@ -49,7 +48,7 @@ class RequestInstrumentation { } @Around("execution(* play.api.GlobalSettings+.doFilter(*)) && args(next)") - def afterDoFilter(pjp: ProceedingJoinPoint, next: EssentialAction): Any = { + def aroundDoFilter(pjp: ProceedingJoinPoint, next: EssentialAction): Any = { val essentialAction = (requestHeader: RequestHeader) ⇒ { val incomingContext = TraceRecorder.currentContext diff --git a/kamon-play/src/test/scala/kamon/play/LoggerLikeInstrumentationSpec.scala b/kamon-play/src/test/scala/kamon/play/LoggerLikeInstrumentationSpec.scala new file mode 100644 index 00000000..6c90c29f --- /dev/null +++ b/kamon-play/src/test/scala/kamon/play/LoggerLikeInstrumentationSpec.scala @@ -0,0 +1,125 @@ +/* ========================================================================================= + * Copyright © 2013-2014 the kamon project + * + * Licensed under the Apache License, Version 2.0 (the "License") you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + * ========================================================================================= + */ + +package kamon.play + +import ch.qos.logback.classic.AsyncAppender +import ch.qos.logback.classic.spi.ILoggingEvent +import ch.qos.logback.core.read.ListAppender +import ch.qos.logback.core.status.NopStatusListener +import kamon.trace.TraceLocal +import org.scalatest.BeforeAndAfter +import org.scalatestplus.play._ +import play.api.Logger +import play.api.mvc.Results.Ok +import play.api.mvc._ +import play.api.test.Helpers._ +import play.api.test._ + +import scala.concurrent.Future + +class LoggerLikeInstrumentationSpec extends PlaySpec with OneServerPerSuite with BeforeAndAfter with Logging { + + System.setProperty("config.file", "./kamon-play/src/test/resources/conf/application.conf") + + val executor = scala.concurrent.ExecutionContext.Implicits.global + + val infoMessage = "Info Message" + + val headerValue = "My header value" + val otherValue = "My other value" + + case class Test(header: String, other: String) + + object TraceLocalKey extends TraceLocal.TraceLocalKey { + type ValueType = Test + } + + before { + LoggingHandler.startLogging() + } + + after { + LoggingHandler.stopLogging() + } + + implicit override lazy val app = FakeApplication(withRoutes = { + + case ("GET", "/logging") ⇒ + Action.async { + Future { + TraceLocal.store(TraceLocalKey)(Test(headerValue, otherValue)) + logger.info(infoMessage) + Ok("OK") + }(executor) + } + }) + + "the LoggerLike instrumentation" should { + "be put the properties of TraceLocal into the MDC as key -> value in a request" in { + LoggingHandler.appenderStart() + + val Some(result) = route(FakeRequest(GET, "/logging")) + Thread.sleep(500) // wait to complete the future + TraceLocal.retrieve(TraceLocalKey) must be(Some(Test(headerValue, otherValue))) + + LoggingHandler.appenderStop() + + headerValue must be(LoggingHandler.getValueFromMDC("header")) + otherValue must be(LoggingHandler.getValueFromMDC("other")) + } + } +} + +trait Logging { + val logger = Logger(this.getClass) +} + +object LoggingHandler { + + val root = play.Logger.of(org.slf4j.Logger.ROOT_LOGGER_NAME) + val underlyingLogger = root.underlying().asInstanceOf[ch.qos.logback.classic.Logger] + val context = underlyingLogger.getLoggerContext + val asyncAppender = new AsyncAppender() + val listAppender = new ListAppender[ILoggingEvent]() + val onConsoleStatusListener = new NopStatusListener() + + def startLogging(): Unit = { + context.getStatusManager().add(onConsoleStatusListener) + asyncAppender.setContext(context) + listAppender.setContext(context) + listAppender.setName("list") + listAppender.start() + } + + def stopLogging(): Unit = { + listAppender.stop() + } + + def appenderStart(): Unit = { + asyncAppender.addAppender(listAppender) + asyncAppender.start() + underlyingLogger.addAppender(asyncAppender) + } + + def appenderStop(): Unit = { + asyncAppender.stop() + } + + def getValueFromMDC(key: String): String = { + listAppender.list.get(0).getMDCPropertyMap.get(key) + } +} + diff --git a/kamon-play/src/test/scala/kamon/play/RequestInstrumentationSpec.scala b/kamon-play/src/test/scala/kamon/play/RequestInstrumentationSpec.scala index 710c6ed5..3090e60e 100644 --- a/kamon-play/src/test/scala/kamon/play/RequestInstrumentationSpec.scala +++ b/kamon-play/src/test/scala/kamon/play/RequestInstrumentationSpec.scala @@ -1,18 +1,17 @@ -/* =================================================== +/* ========================================================================================= * Copyright © 2013-2014 the kamon project * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ========================================================== */ + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + * ========================================================================================= + */ package kamon.play -- cgit v1.2.3 From cf445701ce25e2713838148b037c5a9921a0e22a Mon Sep 17 00:00:00 2001 From: Diego Date: Sun, 6 Jul 2014 21:33:14 -0300 Subject: = play: comment test because not run in travis --- .../src/test/scala/kamon/play/LoggerLikeInstrumentationSpec.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'kamon-play/src/test') diff --git a/kamon-play/src/test/scala/kamon/play/LoggerLikeInstrumentationSpec.scala b/kamon-play/src/test/scala/kamon/play/LoggerLikeInstrumentationSpec.scala index 6c90c29f..6e2ab412 100644 --- a/kamon-play/src/test/scala/kamon/play/LoggerLikeInstrumentationSpec.scala +++ b/kamon-play/src/test/scala/kamon/play/LoggerLikeInstrumentationSpec.scala @@ -77,8 +77,8 @@ class LoggerLikeInstrumentationSpec extends PlaySpec with OneServerPerSuite with LoggingHandler.appenderStop() - headerValue must be(LoggingHandler.getValueFromMDC("header")) - otherValue must be(LoggingHandler.getValueFromMDC("other")) + //headerValue must be(LoggingHandler.getValueFromMDC("header")) + //otherValue must be(LoggingHandler.getValueFromMDC("other")) } } } @@ -119,6 +119,7 @@ object LoggingHandler { } def getValueFromMDC(key: String): String = { + println("------------------------->>>>>>>>>>>>>>>>>>>>>>>>>>>" + listAppender.list.get(0).getMDCPropertyMap) listAppender.list.get(0).getMDCPropertyMap.get(key) } } -- cgit v1.2.3 From 3bb4fd1b1c74eaaf9b5f8ea0a447c88c11b3d82c Mon Sep 17 00:00:00 2001 From: Diego Date: Sun, 6 Jul 2014 22:46:24 -0300 Subject: = play: fixes LoggerLikeInstrumentationSpec --- .../kamon/play/LoggerLikeInstrumentationSpec.scala | 46 ++++++++++------------ 1 file changed, 21 insertions(+), 25 deletions(-) (limited to 'kamon-play/src/test') diff --git a/kamon-play/src/test/scala/kamon/play/LoggerLikeInstrumentationSpec.scala b/kamon-play/src/test/scala/kamon/play/LoggerLikeInstrumentationSpec.scala index 6e2ab412..c41f7004 100644 --- a/kamon-play/src/test/scala/kamon/play/LoggerLikeInstrumentationSpec.scala +++ b/kamon-play/src/test/scala/kamon/play/LoggerLikeInstrumentationSpec.scala @@ -15,14 +15,15 @@ package kamon.play -import ch.qos.logback.classic.AsyncAppender import ch.qos.logback.classic.spi.ILoggingEvent +import ch.qos.logback.classic.{ AsyncAppender, LoggerContext } import ch.qos.logback.core.read.ListAppender import ch.qos.logback.core.status.NopStatusListener import kamon.trace.TraceLocal import org.scalatest.BeforeAndAfter import org.scalatestplus.play._ -import play.api.Logger +import org.slf4j +import play.api.LoggerLike import play.api.mvc.Results.Ok import play.api.mvc._ import play.api.test.Helpers._ @@ -30,21 +31,20 @@ import play.api.test._ import scala.concurrent.Future -class LoggerLikeInstrumentationSpec extends PlaySpec with OneServerPerSuite with BeforeAndAfter with Logging { +class LoggerLikeInstrumentationSpec extends PlaySpec with OneServerPerSuite with BeforeAndAfter { System.setProperty("config.file", "./kamon-play/src/test/resources/conf/application.conf") val executor = scala.concurrent.ExecutionContext.Implicits.global val infoMessage = "Info Message" - val headerValue = "My header value" val otherValue = "My other value" - case class Test(header: String, other: String) + case class LocalStorageValue(header: String, other: String) object TraceLocalKey extends TraceLocal.TraceLocalKey { - type ValueType = Test + type ValueType = LocalStorageValue } before { @@ -60,8 +60,8 @@ class LoggerLikeInstrumentationSpec extends PlaySpec with OneServerPerSuite with case ("GET", "/logging") ⇒ Action.async { Future { - TraceLocal.store(TraceLocalKey)(Test(headerValue, otherValue)) - logger.info(infoMessage) + TraceLocal.store(TraceLocalKey)(LocalStorageValue(headerValue, otherValue)) + LoggingHandler.info(infoMessage) Ok("OK") }(executor) } @@ -73,33 +73,30 @@ class LoggerLikeInstrumentationSpec extends PlaySpec with OneServerPerSuite with val Some(result) = route(FakeRequest(GET, "/logging")) Thread.sleep(500) // wait to complete the future - TraceLocal.retrieve(TraceLocalKey) must be(Some(Test(headerValue, otherValue))) + TraceLocal.retrieve(TraceLocalKey) must be(Some(LocalStorageValue(headerValue, otherValue))) LoggingHandler.appenderStop() - //headerValue must be(LoggingHandler.getValueFromMDC("header")) - //otherValue must be(LoggingHandler.getValueFromMDC("other")) + headerValue must be(LoggingHandler.getValueFromMDC("header")) + otherValue must be(LoggingHandler.getValueFromMDC("other")) } } } -trait Logging { - val logger = Logger(this.getClass) -} - -object LoggingHandler { +object LoggingHandler extends LoggerLike { - val root = play.Logger.of(org.slf4j.Logger.ROOT_LOGGER_NAME) - val underlyingLogger = root.underlying().asInstanceOf[ch.qos.logback.classic.Logger] - val context = underlyingLogger.getLoggerContext + val loggerContext = new LoggerContext() + val rootLogger = loggerContext.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME) val asyncAppender = new AsyncAppender() val listAppender = new ListAppender[ILoggingEvent]() - val onConsoleStatusListener = new NopStatusListener() + val nopStatusListener = new NopStatusListener() + + override val logger: slf4j.Logger = rootLogger def startLogging(): Unit = { - context.getStatusManager().add(onConsoleStatusListener) - asyncAppender.setContext(context) - listAppender.setContext(context) + loggerContext.getStatusManager().add(nopStatusListener) + asyncAppender.setContext(loggerContext) + listAppender.setContext(loggerContext) listAppender.setName("list") listAppender.start() } @@ -111,7 +108,7 @@ object LoggingHandler { def appenderStart(): Unit = { asyncAppender.addAppender(listAppender) asyncAppender.start() - underlyingLogger.addAppender(asyncAppender) + rootLogger.addAppender(asyncAppender) } def appenderStop(): Unit = { @@ -119,7 +116,6 @@ object LoggingHandler { } def getValueFromMDC(key: String): String = { - println("------------------------->>>>>>>>>>>>>>>>>>>>>>>>>>>" + listAppender.list.get(0).getMDCPropertyMap) listAppender.list.get(0).getMDCPropertyMap.get(key) } } -- cgit v1.2.3 From 023cc7a93e92524d1829256677cfbff1056fbe6f Mon Sep 17 00:00:00 2001 From: Ivan Topolnjak Date: Sun, 27 Jul 2014 17:03:10 -0300 Subject: + play: record http server metrics, closes #56 --- kamon-play/src/main/resources/reference.conf | 4 ++++ kamon-play/src/main/scala/kamon/play/Play.scala | 3 +++ .../instrumentation/RequestInstrumentation.scala | 13 +++++++++-- .../kamon/play/RequestInstrumentationSpec.scala | 26 +++++++++++++++++++++- 4 files changed, 43 insertions(+), 3 deletions(-) (limited to 'kamon-play/src/test') diff --git a/kamon-play/src/main/resources/reference.conf b/kamon-play/src/main/resources/reference.conf index 47a31ef4..72266a0c 100644 --- a/kamon-play/src/main/resources/reference.conf +++ b/kamon-play/src/main/resources/reference.conf @@ -3,6 +3,10 @@ # ================================== # kamon { + metrics { + tick-interval = 1 hour + } + play { include-trace-token-header = true trace-token-header-name = "X-Trace-Token" diff --git a/kamon-play/src/main/scala/kamon/play/Play.scala b/kamon-play/src/main/scala/kamon/play/Play.scala index ca9c10e5..03436458 100644 --- a/kamon-play/src/main/scala/kamon/play/Play.scala +++ b/kamon-play/src/main/scala/kamon/play/Play.scala @@ -18,6 +18,8 @@ package kamon.play import akka.actor.{ ExtendedActorSystem, Extension, ExtensionIdProvider, ExtensionId } import kamon.Kamon +import kamon.http.HttpServerMetrics +import kamon.metric.Metrics object Play extends ExtensionId[PlayExtension] with ExtensionIdProvider { override def lookup(): ExtensionId[_ <: Extension] = Play @@ -29,6 +31,7 @@ class PlayExtension(private val system: ExtendedActorSystem) extends Kamon.Exten private val config = system.settings.config.getConfig("kamon.play") + val httpServerMetrics = Kamon(Metrics)(system).register(HttpServerMetrics, HttpServerMetrics.Factory).get val defaultDispatcher = system.dispatchers.lookup(config.getString("dispatcher")) val includeTraceToken: Boolean = config.getBoolean("include-trace-token-header") val traceTokenHeaderName: String = config.getString("trace-token-header-name") diff --git a/kamon-play/src/main/scala/kamon/play/instrumentation/RequestInstrumentation.scala b/kamon-play/src/main/scala/kamon/play/instrumentation/RequestInstrumentation.scala index db864fb6..975510e9 100644 --- a/kamon-play/src/main/scala/kamon/play/instrumentation/RequestInstrumentation.scala +++ b/kamon-play/src/main/scala/kamon/play/instrumentation/RequestInstrumentation.scala @@ -16,7 +16,7 @@ package kamon.play.instrumentation import kamon.Kamon -import kamon.play.Play +import kamon.play.{ PlayExtension, Play } import kamon.trace.{ TraceContextAware, TraceRecorder } import org.aspectj.lang.ProceedingJoinPoint import org.aspectj.lang.annotation._ @@ -52,11 +52,17 @@ class RequestInstrumentation { val essentialAction = (requestHeader: RequestHeader) ⇒ { val incomingContext = TraceRecorder.currentContext - val executor = Kamon(Play)(Akka.system()).defaultDispatcher + val playExtension = Kamon(Play)(Akka.system()) + val executor = playExtension.defaultDispatcher next(requestHeader).map { result ⇒ + TraceRecorder.currentContext.map { ctx ⇒ + recordHttpServerMetrics(result, ctx.name, playExtension) + } + TraceRecorder.finish() + incomingContext match { case None ⇒ result case Some(traceContext) ⇒ @@ -70,6 +76,9 @@ class RequestInstrumentation { pjp.proceed(Array(EssentialAction(essentialAction))) } + def recordHttpServerMetrics(result: Result, traceName: String, playExtension: PlayExtension): Unit = + playExtension.httpServerMetrics.recordResponse(traceName, result.header.status.toString, 1L) + @Around("execution(* play.api.GlobalSettings+.onError(..)) && args(request, ex)") def aroundOnError(pjp: ProceedingJoinPoint, request: TraceContextAware, ex: Throwable): Any = request.traceContext match { case None ⇒ pjp.proceed() diff --git a/kamon-play/src/test/scala/kamon/play/RequestInstrumentationSpec.scala b/kamon-play/src/test/scala/kamon/play/RequestInstrumentationSpec.scala index 3090e60e..fc195580 100644 --- a/kamon-play/src/test/scala/kamon/play/RequestInstrumentationSpec.scala +++ b/kamon-play/src/test/scala/kamon/play/RequestInstrumentationSpec.scala @@ -15,6 +15,10 @@ package kamon.play +import scala.concurrent.duration._ +import kamon.Kamon +import kamon.http.HttpServerMetrics +import kamon.metric.{ CollectionContext, Metrics } import kamon.play.action.TraceName import kamon.trace.{ TraceLocal, TraceRecorder } import org.scalatestplus.play._ @@ -23,8 +27,9 @@ import play.api.mvc.Results.Ok import play.api.mvc._ import play.api.test.Helpers._ import play.api.test._ +import play.libs.Akka -import scala.concurrent.Future +import scala.concurrent.{ Await, Future } class RequestInstrumentationSpec extends PlaySpec with OneServerPerSuite { @@ -107,6 +112,25 @@ class RequestInstrumentationSpec extends PlaySpec with OneServerPerSuite { val Some(result) = route(FakeRequest(GET, "/retrieve").withHeaders(traceTokenHeader, traceLocalStorageHeader)) TraceLocal.retrieve(TraceLocalKey).get must be(traceLocalStorageValue) } + + "record http server metrics for all processed requests" in { + val collectionContext = CollectionContext(100) + Kamon(Metrics)(Akka.system()).register(HttpServerMetrics, HttpServerMetrics.Factory).get.collect(collectionContext) + + for (repetition ← 1 to 10) { + Await.result(route(FakeRequest(GET, "/default").withHeaders(traceTokenHeader)).get, 10 seconds) + } + + for (repetition ← 1 to 5) { + Await.result(route(FakeRequest(GET, "/notFound").withHeaders(traceTokenHeader)).get, 10 seconds) + } + + val snapshot = Kamon(Metrics)(Akka.system()).register(HttpServerMetrics, HttpServerMetrics.Factory).get.collect(collectionContext) + snapshot.countsPerTraceAndStatusCode("GET: /default")("200").count must be(10) + snapshot.countsPerTraceAndStatusCode("GET: /notFound")("404").count must be(5) + snapshot.countsPerStatusCode("200").count must be(10) + snapshot.countsPerStatusCode("404").count must be(5) + } } object MockGlobalTest extends WithFilters(TraceLocalFilter) -- cgit v1.2.3