packages feed

opentracing-jaeger-0.2.0: src/OpenTracing/Jaeger/Thrift.hs

{-# LANGUAGE TupleSections #-}

module OpenTracing.Jaeger.Thrift
    ( toThriftSpan
    , toThriftTags
    , toThriftProcess
    , toThriftBatch
    )
where

import           Data.ByteString.Lazy       (toStrict)
import           Control.Lens
import           Data.Bool                  (bool)
import           Data.Foldable
import           Data.Int                   (Int64)
import           Data.Text                  (Text)
import           Data.Text.Lazy.Builder     (toLazyText)
import           Data.Text.Lazy.Builder.Int (decimal)
import           Data.Text.Lens
import           Data.Vector                (Vector)
import qualified Data.Vector                as Vector
import           Data.Vector.Lens           (vector)
import           GHC.Stack                  (prettyCallStack)
import           Jaeger.Types
    ( Batch (..)
    , Log (..)
    , Process (..)
    , Span (..)
    , SpanRef (..)
    , Tag (..)
    )
import qualified Jaeger.Types               as Thrift
import           OpenTracing.Log
import           OpenTracing.Span
import           OpenTracing.Tags
import           OpenTracing.Time
import           OpenTracing.Types          (TraceID (..))


toThriftSpan :: FinishedSpan -> Thrift.Span
toThriftSpan s = Thrift.Span
    { span_traceIdLow    = view (spanContext . to traceIdLo') s
    , span_traceIdHigh   = view (spanContext . to traceIdHi') s
    , span_spanId        = view (spanContext . to ctxSpanID') s
    , span_parentSpanId  = maybe 0 (ctxSpanID' . refCtx) . findParent
                         $ view spanRefs s
    , span_operationName = view spanOperation s
    , span_references    = view ( spanRefs
                                . to (map toThriftSpanRef . toList)
                                . vector
                                . re _Just
                                )
                                s
    , span_flags         = view ( spanContext
                                . ctxSampled
                                . re _IsSampled
                                . to (bool 0 1)
                                )
                                s
    , span_startTime     = view (spanStart . to micros) s
    , span_duration      = view (spanDuration . to micros) s
    , span_tags          = view (spanTags . to toThriftTags . re _Just) s
    , span_logs          = Just
                         . Vector.fromList
                         . foldr' (\r acc -> toThriftLog r : acc) []
                         $ view spanLogs s
    }

toThriftSpanRef :: Reference -> Thrift.SpanRef
toThriftSpanRef ref = Thrift.SpanRef
    { spanRef_refType     = toThriftRefType ref
    , spanRef_traceIdLow  = traceIdLo' (refCtx ref)
    , spanRef_traceIdHigh = traceIdHi' (refCtx ref)
    , spanRef_spanId      = ctxSpanID' (refCtx ref)
    }

toThriftRefType :: Reference -> Thrift.SpanRefType
toThriftRefType (ChildOf     _) = Thrift.CHILD_OF
toThriftRefType (FollowsFrom _) = Thrift.FOLLOWS_FROM

toThriftTags :: Tags -> Vector Thrift.Tag
toThriftTags = ifoldMap (\k v -> Vector.singleton (toThriftTag k v)) . fromTags

toThriftTag :: Text -> TagVal -> Thrift.Tag
-- acc. to https://github.com/opentracing/specification/blob/8d634bc7e3e73050f6ac1006858cddac8d9e0abe/semantic_conventions.yaml
-- "http.status_code" is supposed to be integer-valued. Jaeger, however, drops
-- the value (nb. _not_ the tag key) unless it is a string.
toThriftTag HttpStatusCodeKey (IntT v) = Thrift.Tag
    { tag_key     = HttpStatusCodeKey
    , tag_vType   = Thrift.STRING
    , tag_vStr    = Just . view strict . toLazyText . decimal $ v
    , tag_vDouble = Nothing
    , tag_vBool   = Nothing
    , tag_vLong   = Nothing
    , tag_vBinary = Nothing
    }
toThriftTag k v =
  Thrift.Tag
  {
    tag_key = k
  , tag_vType = case v of
      BoolT   _ -> Thrift.BOOL
      StringT _ -> Thrift.STRING
      IntT    _ -> Thrift.LONG
      DoubleT _ -> Thrift.DOUBLE
      BinaryT _ -> Thrift.BINARY
  , tag_vStr = case v of
      StringT x -> Just x
      _ -> Nothing
  , tag_vDouble = case v of
      DoubleT x -> Just x
      _ -> Nothing
  , tag_vBool = case v of
      BoolT x -> Just x
      _ -> Nothing
  , tag_vLong = case v of
      IntT x -> Just x
      _ -> Nothing
  , tag_vBinary = case v of
      BinaryT x -> Just (toStrict x)
      _ -> Nothing
  }

toThriftLog :: LogRecord -> Thrift.Log
toThriftLog r = Thrift.Log
    { log_timestamp = view (logTime . to micros) r
    , log_fields    = foldMap ( Vector.singleton
                              . uncurry toThriftTag
                              . asTag
                              )
                    $ view logFields r
    }
  where
    asTag f = (logFieldLabel f,) . StringT $ case f of
        LogField _ v -> view packed (show v)
        Event      v -> v
        Message    v -> v
        Stack      v -> view packed (prettyCallStack v)
        ErrKind    v -> v
        ErrObj     v -> view packed (show v)

toThriftProcess :: Text -> Tags -> Thrift.Process
toThriftProcess srv tags = Thrift.Process
    { process_serviceName = srv
    , process_tags        = Just $ toThriftTags tags
    }

toThriftBatch :: Thrift.Process -> Vector FinishedSpan -> Thrift.Batch
toThriftBatch tproc spans = Thrift.Batch
    { batch_process = tproc
    , batch_spans   = toThriftSpan <$> spans
    , batch_seqNo   = Nothing
    , batch_stats   = Nothing
    }

traceIdLo' :: SpanContext -> Int64
traceIdLo' = fromIntegral . traceIdLo . ctxTraceID

traceIdHi' :: SpanContext -> Int64
traceIdHi' = maybe 0 fromIntegral . traceIdHi . ctxTraceID

ctxSpanID' :: SpanContext -> Int64
ctxSpanID' = fromIntegral . ctxSpanID