aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/TracingDirectives.scala
blob: d0cbfd25a05164462ca892332b21a49817d9f043 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
package xyz.driver.tracing

import akka.http.scaladsl.model._
import akka.http.scaladsl.model.headers._
import akka.http.scaladsl.server._
import akka.http.scaladsl.server.Directives._
import java.util.UUID
import scala.util.Random
import java.time._
import scala.concurrent._
import scala.collection.immutable.Seq

trait TracingDirectives {
  import TracingDirectives._

  def optionalTraceContext: Directive1[Option[TraceContext]] =
    extractRequest.map { req =>
      TraceContext.fromHeaders(req.headers)
    }

  def withTraceContext(ctx: TraceContext): Directive0 =
    mapRequest(req => req.withHeaders(ctx.headers))

  def trace(tracer: Tracer, name: String, extraLabels: Map[String, String] = Map.empty): Directive0 =
    extractRequest.flatMap { request =>
      val labels = Map(
        "/http/user_agent" -> "driver-tracer",
        "/http/method" -> request.method.name,
        "/http/url" -> request.uri.path.toString,
        "/http/host" -> request.uri.authority.host.toString
      ) ++ extraLabels

      val span: Span = TraceContext.fromHeaders(request.headers) match {
        case None => // no parent span, create new trace
          Span(
            name = name,
            labels = labels
          )
        case Some(TraceContext(traceId, parentSpanId)) =>
          Span(
            name = name,
            traceId = traceId,
            parentSpanId = parentSpanId,
            labels = labels
          )
      }

      withTraceContext(TraceContext.fromSpan(span)) & mapRouteResult { res =>
        tracer.submit(span.end())
        res
      }

    }

  /*
  def span2(name2: String, tracer: Tracer): Directive0 = {
    val f: RouteResult ⇒ RouteResult = ???
    Directive { inner ⇒ ctx ⇒
      inner(())(ctx).map(f)(ctx.executionContext)
    }
  }
 */

}

object TracingDirectives extends TracingDirectives {

  case class TraceContext(traceId: UUID, parentSpanId: Option[UUID]) {
    import TraceContext._

    def headers: Seq[HttpHeader] =
      Seq(RawHeader(TraceHeaderName, traceId.toString)) ++
        parentSpanId.toSeq.map(id => RawHeader(SpanHeaderName, id.toString))
  }
  object TraceContext {
    val TraceHeaderName = "Tracing-Trace-Id"
    val SpanHeaderName = "Tracing-Span-Id"

    def fromHeaders(headers: Seq[HttpHeader]): Option[TraceContext] = {
      val traceId = headers
        .find(_.name == TraceHeaderName)
        .map(_.value)
        .map(UUID.fromString)
      val parentSpanId =
        headers.find(_.name == SpanHeaderName).map(_.value).map(UUID.fromString)
      traceId.map { tid =>
        TraceContext(tid, parentSpanId)
      }
    }
    def fromSpan(span: Span) = TraceContext(span.traceId, Some(span.spanId))
  }

}