lightstep-haskell-0.6.0: src/LightStep/Propagation.hs
{-# LANGUAGE OverloadedStrings #-}
module LightStep.Propagation
( module P,
module LightStep.Propagation,
)
where
import Control.Lens
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BS8
import Data.ProtoLens.Message (defMessage)
import Data.String
import GHC.Word
import Network.HTTP.Types.Header (HeaderName)
import Network.Wai
import Proto.Collector as P
import Proto.Collector_Fields as P
import Text.Printf
type TextMap = [(BS.ByteString, BS.ByteString)]
type HttpHeaders = [(HeaderName, BS.ByteString)]
-- TODO: Binary format. Both go and python basictracers use TracerState
-- protobuf https://github.com/opentracing/basictracer-go/blob/master/wire/wire.proto
data Propagator a
= Propagator
{ inject :: SpanContext -> a,
extract :: a -> Maybe SpanContext
}
textPropagator :: Propagator TextMap
textPropagator =
let prefix = "Ot-Tracer-"
in Propagator
{ inject = injectSpanContext prefix,
extract = extractSpanContext prefix
}
httpHeadersPropagator :: Propagator HttpHeaders
httpHeadersPropagator =
let prefix = "Ot-Tracer-"
in Propagator
{ inject = injectSpanContext prefix,
extract = extractSpanContext prefix
}
b3Propagator :: Propagator HttpHeaders
b3Propagator =
let prefix = "X-B3-"
in Propagator
{ inject = injectSpanContext prefix,
extract = extractSpanContext prefix
}
injectSpanContext ::
(IsString key, Semigroup key) =>
key ->
SpanContext ->
[(key, BS.ByteString)]
injectSpanContext prefix ctx =
[ (prefix <> "Traceid", encode_u64 $ ctx ^. traceId),
(prefix <> "Spanid", encode_u64 $ ctx ^. spanId),
(prefix <> "Sampled", "true")
]
extractSpanContext ::
(IsString key, Eq key, Semigroup key) =>
key ->
[(key, BS.ByteString)] ->
Maybe SpanContext
extractSpanContext prefix format =
(\(tid, sid) -> defMessage & traceId .~ tid & spanId .~ sid)
<$> go format (Nothing, Nothing)
where
traceidKey = prefix <> "Traceid"
spanidKey = prefix <> "Spanid"
go _ (Just tid, Just sid) = Just (tid, sid)
go [] _ = Nothing
go ((k, v) : xs) (tid, sid)
| k == traceidKey = go xs (decode_u64 v, sid)
| k == spanidKey = go xs (tid, decode_u64 v)
| otherwise = go xs (tid, sid)
extractSpanContextFromRequest :: Request -> Maybe SpanContext
extractSpanContextFromRequest =
extract httpHeadersPropagator . requestHeaders
-- parse128 :: BS.ByteString -> Maybe (Word64, Word64)
-- parse128 _ = Nothing -- TODO
encode_u64 :: Word64 -> BS.ByteString
encode_u64 x = BS8.pack (printf "%016x" x)
decode_u64 :: BS.ByteString -> Maybe Word64
decode_u64 bytes | BS.length bytes > 16 = Nothing
decode_u64 bytes = BS.foldl' go (Just 0) bytes
where
go Nothing _ = Nothing
go (Just !result) d | d >= 48 && d < 58 = Just $ result * 16 + fromIntegral d - 48
go (Just result) d | d >= 97 && d < 124 = Just $ result * 16 + fromIntegral d - 87
go _ _ = Nothing