From b8a26e0f173a528c700a3d0cfa4ed2f8079a7ae1 Mon Sep 17 00:00:00 2001 From: Diego Date: Sat, 8 Mar 2014 20:17:15 -0300 Subject: WIP:Play integration WebExternal --- kamon-play/src/main/resources/META-INF/aop.xml | 1 + .../instrumentation/RequestInstrumentation.scala | 2 +- .../play/instrumentation/WSInstrumentation.scala | 70 ++++++++++++++++++++++ .../kamon/play/RequestInstrumentationSpec.scala | 7 ++- .../scala/kamon/play/WSInstrumentationSpec.scala | 59 ++++++++++++++++++ 5 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 kamon-play/src/main/scala/kamon/play/instrumentation/WSInstrumentation.scala create mode 100644 kamon-play/src/test/scala/kamon/play/WSInstrumentationSpec.scala (limited to 'kamon-play/src') diff --git a/kamon-play/src/main/resources/META-INF/aop.xml b/kamon-play/src/main/resources/META-INF/aop.xml index ca119c2b..3c3cceed 100644 --- a/kamon-play/src/main/resources/META-INF/aop.xml +++ b/kamon-play/src/main/resources/META-INF/aop.xml @@ -3,6 +3,7 @@ + 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 76edb90c..c55f89e0 100644 --- a/kamon-play/src/main/scala/kamon/play/instrumentation/RequestInstrumentation.scala +++ b/kamon-play/src/main/scala/kamon/play/instrumentation/RequestInstrumentation.scala @@ -30,7 +30,7 @@ import scala.concurrent.ExecutionContext.Implicits.global @Aspect class RequestInstrumentation { - @DeclareMixin("play.api.mvc.Request || play.api.mvc.WrappedRequest || play.api.test.FakeRequest") + @DeclareMixin("play.api.mvc.RequestHeader || play.api.test.FakeRequest") def mixinContextAwareNewRequest: TraceContextAware = TraceContextAware.default @Pointcut("execution(* play.api.GlobalSettings+.onStart(*)) && args(application)") diff --git a/kamon-play/src/main/scala/kamon/play/instrumentation/WSInstrumentation.scala b/kamon-play/src/main/scala/kamon/play/instrumentation/WSInstrumentation.scala new file mode 100644 index 00000000..2fedcd70 --- /dev/null +++ b/kamon-play/src/main/scala/kamon/play/instrumentation/WSInstrumentation.scala @@ -0,0 +1,70 @@ +package kamon.play.instrumentation + +import javax.net.ssl.SSLContext +import org.aspectj.lang.annotation.{ Around, Pointcut, Aspect } +import org.aspectj.lang.ProceedingJoinPoint +import com.ning.http.client._ +import com.ning.http.client.filter.{ RequestFilter, FilterContext } +import kamon.trace.{ SegmentCompletionHandle, TraceRecorder } +import kamon.metrics.TraceMetrics.HttpClientRequest + +@Aspect +class WSInstrumentation { + + @Pointcut("call(* play.api.libs.ws.WS$.newClient(..))") + def onNewAsyncHttpClient(): Unit = {} + + @Around("onNewAsyncHttpClient()") + def aroundNewAsyncHttpClient(pjp: ProceedingJoinPoint): Any = { + val playConfig = play.api.Play.maybeApplication.map(_.configuration) + val wsTimeout = playConfig.flatMap(_.getMilliseconds("ws.timeout")) + val asyncHttpConfig = new AsyncHttpClientConfig.Builder() + .setConnectionTimeoutInMs(playConfig.flatMap(_.getMilliseconds("ws.timeout.connection")).orElse(wsTimeout).getOrElse(120000L).toInt) + .setIdleConnectionTimeoutInMs(playConfig.flatMap(_.getMilliseconds("ws.timeout.idle")).orElse(wsTimeout).getOrElse(120000L).toInt) + .setRequestTimeoutInMs(playConfig.flatMap(_.getMilliseconds("ws.timeout.request")).getOrElse(120000L).toInt) + .setFollowRedirects(playConfig.flatMap(_.getBoolean("ws.followRedirects")).getOrElse(true)) + .setUseProxyProperties(playConfig.flatMap(_.getBoolean("ws.useProxyProperties")).getOrElse(true)) + + playConfig.flatMap(_.getString("ws.useragent")).map { useragent ⇒ + asyncHttpConfig.setUserAgent(useragent) + } + if (!playConfig.flatMap(_.getBoolean("ws.acceptAnyCertificate")).getOrElse(false)) { + asyncHttpConfig.setSSLContext(SSLContext.getDefault) + } + + asyncHttpConfig.addRequestFilter(new KamonRequestFilter()) + + new AsyncHttpClient(asyncHttpConfig.build()) + } +} + +class KamonRequestFilter extends RequestFilter { + import KamonRequestFilter._ + + override def filter(ctx: FilterContext[_]): FilterContext[_] = { + val completionHandle = TraceRecorder.startSegment(HttpClientRequest(ctx.getRequest.getRawURI.toString(), UserTime), basicRequestAttributes(ctx.getRequest())) + new FilterContext.FilterContextBuilder(ctx).asyncHandler(new AsyncHandlerWrapper[Response](ctx.getAsyncHandler(), completionHandle)).build() + } + + class AsyncHandlerWrapper[T](asyncHandler: AsyncHandler[_], completionHandle: Option[SegmentCompletionHandle]) extends AsyncCompletionHandler[T] { + override def onCompleted(response: Response): T = { + completionHandle.map(_.finish(Map.empty)) + asyncHandler.onCompleted().asInstanceOf[T] + } + override def onThrowable(t: Throwable) = { + asyncHandler.onThrowable(t) + } + } +} + +object KamonRequestFilter { + val UserTime = "UserTime" + + def basicRequestAttributes(request: Request): Map[String, String] = { + Map[String, String]( + "host" -> request.getHeaders().getFirstValue("host"), + "path" -> request.getURI.getPath, + "method" -> request.getMethod) + } +} + diff --git a/kamon-play/src/test/scala/kamon/play/RequestInstrumentationSpec.scala b/kamon-play/src/test/scala/kamon/play/RequestInstrumentationSpec.scala index 5b75359f..2dce39f8 100644 --- a/kamon-play/src/test/scala/kamon/play/RequestInstrumentationSpec.scala +++ b/kamon-play/src/test/scala/kamon/play/RequestInstrumentationSpec.scala @@ -75,7 +75,12 @@ class RequestInstrumentationSpec extends PlaySpecification { private val expectedToken = Some(traceTokenValue) private val traceTokenHeader = (traceTokenHeaderName -> traceTokenValue) - "the request instrumentation" should { + "the Request instrumentation" should { + "respond to the asyncResult action with X-Trace-Token" in new WithServer(appWithRoutes) { + val Some(result) = route(FakeRequest(GET, "/asyncResult").withHeaders(traceTokenHeader)) + header(traceTokenHeaderName, result) must equalTo(expectedToken) + } + "respond to the async action with X-Trace-Token" in new WithServer(appWithRoutes) { val Some(result) = route(FakeRequest(GET, "/async").withHeaders(traceTokenHeader)) header(traceTokenHeaderName, result) must equalTo(expectedToken) diff --git a/kamon-play/src/test/scala/kamon/play/WSInstrumentationSpec.scala b/kamon-play/src/test/scala/kamon/play/WSInstrumentationSpec.scala new file mode 100644 index 00000000..2cebfe42 --- /dev/null +++ b/kamon-play/src/test/scala/kamon/play/WSInstrumentationSpec.scala @@ -0,0 +1,59 @@ +/* =================================================== + * 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 play.api.test._ +import play.api.mvc.{ Results, Action } +import play.api.mvc.Results.Ok +import scala.Some +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.Future +import org.junit.runner.RunWith +import org.specs2.runner.JUnitRunner +import play.api.mvc.AsyncResult +import play.api.test.FakeApplication +import kamon.play.action.TraceName +import play.api.libs.ws.WS + +@RunWith(classOf[JUnitRunner]) +class WSInstrumentationSpec extends PlaySpecification { + + System.setProperty("config.file", "./kamon-play/src/test/resources/conf/application.conf") + + val appWithRoutes = FakeApplication(withRoutes = { + + case ("GET", "/async") ⇒ + Action.async { + WS.url("http://www.google.com").get().map { + response ⇒ + Ok(response.toString()) + } + } + }) + + "the WS instrumentation" should { + "respond to the async action" in new WithServer(appWithRoutes) { + val Some(result) = route(FakeRequest(GET, "/async")) + println("-902425309-53095-5 " + result) + result.onComplete { + case scala.util.Success(r) ⇒ println(r) + case scala.util.Failure(t) ⇒ println(t) + } + Thread.sleep(3000) + } + } +} \ No newline at end of file -- cgit v1.2.3