All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.opentracing.play.TracingAction.scala Maven / Gradle / Ivy

The newest version!
package io.opentracing.play

import io.opentracing.{Span, Tracer}
import io.opentracing.util.GlobalTracer
import io.opentracing.propagation.Format
import io.opentracing.tag.Tags
import io.opentracing.threadcontext.ContextSpan
import java.util.concurrent.Callable

import akka.stream.Materializer
import play.api.mvc._

import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Success, Try}

private object RequestSpan {
  def apply(tracer: Tracer, request: RequestHeader): Span =
    tracer
      .buildSpan(Routes.endpointName(request).getOrElse(s"HTTP ${request.method}"))
      .asChildOf(tracer.extract(Format.Builtin.HTTP_HEADERS, new HeadersTextMap(request.headers)))
      .withTag(Tags.SPAN_KIND.getKey, Tags.SPAN_KIND_SERVER)
      .start()
}
class TracingRequest[+A](val span: Span, request: Request[A]) extends WrappedRequest(request)

/**
 * A class to make it easy to create Actions that take a TracingRequest and
 * run with a Span stored in thread local storage.
 *
 * Normally you would create an object that extends this class, and use that to create Actions.
 *
 * {{{
 * object TracingAction extends TracingActionBuilder(ContextSpan.DEFAULT, Nil)
 * }}}
 */
class TracingActionBuilder(
  protected[this] val tracer: Tracer,
  protected[this] val contextSpan: ContextSpan,
  taggers: Iterable[SpanTagger]
)(val parser: BodyParser[AnyContent])(implicit ec: ExecutionContext)
    extends ActionBuilder[TracingRequest, AnyContent] {

  /**
   * Finish tagging the span and finish it. A subclass may wish to
   */
  protected def finishSpan[A](request: TracingRequest[A], result: Option[Result]) = {
    taggers.foreach(_.tag(request.span, request, result))
    request.span.finish()
  }

  final def invokeBlock[A](request: Request[A], block: TracingRequest[A] => Future[Result]): Future[Result] = {
    val span = RequestSpan(tracer, request)
    val tracingRequest = new TracingRequest(span, request)
    contextSpan
      .set(span)
      .call(new Callable[Future[Result]] {

        private def failureHandler(exception: Throwable): Future[Result] = {
          Tags.ERROR.set(tracingRequest.span, true)
          finishSpan(tracingRequest, None)
          Future.failed(exception)
        }

        def call() =
          Try(block(tracingRequest)) match {
            case Failure(exception) =>
              failureHandler(exception)
            case Success(resultFuture) =>
              resultFuture
                .map { result =>
                  finishSpan(tracingRequest, Some(result))
                  result
                }
                .recoverWith {
                  case exception =>
                    failureHandler(exception)
                }
          }

      })
  }

  override protected def executionContext: ExecutionContext = ec
}

/**
 * Like TracingRequest but uses ContextSpan.DEFAULT and GlobalTracer for the context and tracer.
 */
class DefaultTracingActionBuilder(taggers: Iterable[SpanTagger])(
  implicit ec: ExecutionContext,
  mat: Materializer
) extends TracingActionBuilder(GlobalTracer.get, ContextSpan.DEFAULT, taggers)(new BodyParsers.Default)




© 2015 - 2025 Weber Informatics LLC | Privacy Policy