Skip to content

Instantly share code, notes, and snippets.

@magro
Last active August 29, 2015 14:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save magro/a330717d615f21820e3c to your computer and use it in GitHub Desktop.
Save magro/a330717d615f21820e3c to your computer and use it in GitHub Desktop.
object CassandraTracing {
def traceSegment(res: Future[ResultSet]): Future[ResultSet] = {
TraceRecorder.withTraceContextAndSystem { (ctx, system) =>
// Because we don't know the actual table that was queried for now we
// use a general name for the segment, it's renamed once we have the result
val segment = ctx.startSegment("db", "cassandra-client", "datastax")
res.onComplete {
case Success(rs) =>
// Get the table name from the result so that we can give the segment
// a more specific name
val table = rs.getColumnDefinitions.getTable(0)
segment.rename("db_" + table)
segment.finish()
case Failure(e) => segment.finish()
}(Execution.tracingContext)
res
}.getOrElse(res)
}
}
import io.ino.play.tracing.{SlowRequestLogger, StoreRequestInfoFilter}
import play.api.Application
import play.api.libs.concurrent.Akka
import play.api.mvc.WithFilters
object Global extends WithFilters(StoreRequestInfoFilter) {
override def onStart(app: Application): Unit = {
SlowRequestLogger.start(Akka.system(app))
}
}
package io.ino.play.tracing
import akka.actor.{Actor, ActorLogging, ActorSystem, Props}
import kamon.Kamon
import kamon.trace.{SegmentInfo, Trace, TraceInfo}
import play.api.Logger
import scala.concurrent.duration._
/**
* Kamon trace subscriber that logs slow requests. Relies on request method + uri stored
* under the keys TraceMetadata.{RequestMethod, RequestUri} in the trace context's metadata.
*/
class SlowRequestLogger extends Actor with ActorLogging {
private val logger = Logger("SlowRequestLog")
import TraceMetadata._
override def receive: Receive = {
case trace: TraceInfo =>
val requestTime = NANOSECONDS.toMillis(trace.elapsedTime.nanos)
val segmentsMsg = formatSegmentsInfo(trace.segments)
val msg = s""""${trace.metadata(RequestMethod)} ${trace.metadata(RequestUri)}" ($requestTime ms)$segmentsMsg"""
logger.info(msg)
}
/**
* Ugly stuff to build segment info, produces s.th. like
* "segments: segment1 (23 ms), segment2 (42 ms, someMeta: someValue)
*/
private def formatSegmentsInfo(segments: Seq[SegmentInfo]): String = {
if (segments.isEmpty) {
""
} else {
", segments: " + segments.map { segment =>
val detailsMsg = if (segment.metadata.isEmpty) {
""
} else {
", " + segment.metadata.map { case (key, value) => s"$key: $value"}.mkString(", ")
}
segment.name + " (" + NANOSECONDS.toMillis(segment.elapsedTime.nanos) + " ms" + detailsMsg + ")"
}.mkString(", ")
}
}
}
object SlowRequestLogger {
/**
* Starts the SlowRequestLogger - subscribe to traces recorded by kamon.
*/
def start(implicit system: ActorSystem): Unit = {
val slowRequestLogger = system.actorOf(Props(new SlowRequestLogger))
Kamon(Trace).subscribe(slowRequestLogger)
}
}
object TraceMetadata {
val RequestMethod = "requestMethod"
val RequestUri = "requestUri"
}
package io.ino.play.tracing
import kamon.trace.TraceRecorder
import play.api.mvc.{Filter, RequestHeader, Result}
import scala.concurrent.Future
/**
* Stores request info in kamon's trace local storage, so that it's available for logging (SlowRequestLogger).
*/
object StoreRequestInfoFilter extends Filter {
import TraceMetadata._
override def apply(next: (RequestHeader) => Future[Result])(rh: RequestHeader): Future[Result] = {
val ctxt = TraceRecorder.currentContext
ctxt.addMetadata(RequestMethod, rh.method)
ctxt.addMetadata(RequestUri, rh.uri)
next(rh)
}
}
val TracingRequestInterceptor = new RequestInterceptor {
override def interceptQuery(f: (SolrServer, SolrQuery) => Future[QueryResponse])
(solrServer: SolrServer, q: SolrQuery): Future[QueryResponse] = {
TraceRecorder.withTraceContextAndSystem { (ctx, system) =>
val segment = ctx.startSegment("solrQuery", "solr-client", "solrs")
val response = f(solrServer, q)
response.onComplete {
case Success(qr) =>
// Add the query time measured by solr as segment metadata
segment.addMetadata("solrQTime", qr.getQTime.toString)
segment.finish()
case Failure(e) => segment.finish()
}(ExecutionContext.global)
response
}.getOrElse(f(solrServer, q))
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment