{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
module OpenTelemetry.Propagation where
import Control.Applicative
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BS8
import Data.Char (ord)
import Data.List (find)
import Data.String
import Data.Word
import OpenTelemetry.SpanContext
import Text.Printf
data PropagationFormat = PropagationFormat
{ propagateFromHeaders :: forall key. (Semigroup key, IsString key, Eq key) => [(key, BS.ByteString)] -> Maybe SpanContext,
propagateToHeaders :: forall key. (Semigroup key, IsString key, Eq key) => SpanContext -> [(key, BS.ByteString)]
}
instance Semigroup PropagationFormat where
PropagationFormat from1 to1 <> PropagationFormat from2 _to2 =
let from headers = from1 headers <|> from2 headers
to context = to1 context
in PropagationFormat from to
w3cTraceContext :: PropagationFormat
w3cTraceContext = PropagationFormat from to
where
from headers =
case find ((== "traceparent") . fst) headers of
Just (_, (parseSpanContext -> mctx)) -> mctx
_ -> Nothing
to (SpanContext (SId sid) (TId tid)) =
[("traceparent", BS8.pack $ printf "00-%x-%x-00" tid sid)]
b3 :: PropagationFormat
b3 = typical_opentracing_format_with_prefix "x-b3-"
otTracer :: PropagationFormat
otTracer = typical_opentracing_format_with_prefix "ot-tracer-"
typical_opentracing_format_with_prefix :: String -> PropagationFormat
typical_opentracing_format_with_prefix prefix = PropagationFormat from to
where
to (SpanContext (SId sid) (TId tid)) =
[ (fromString prefix <> "traceid", encode_u64 tid),
(fromString prefix <> "spanid", encode_u64 sid),
(fromString prefix <> "sampled", "true")
]
from headers =
let traceidKey = fromString prefix <> "traceid"
spanidKey = fromString 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)
in (\(t, s) -> SpanContext (SId s) (TId t)) <$> go headers (Nothing, Nothing)
parseSpanContext :: BS.ByteString -> Maybe SpanContext
parseSpanContext input =
case BS8.split '-' input of
["00", (fromHex -> Just tid), (fromHex -> Just sid), _] ->
Just $ SpanContext (SId sid) (TId tid)
_ -> Nothing
isLowerHexDigit :: Char -> Bool
isLowerHexDigit (ord -> w) = (w >= 48 && w <= 57) || (w >= 97 && w <= 102)
fromHex :: BS.ByteString -> Maybe Word64
fromHex bytes = BS8.foldl' go (Just 0) bytes
where
go Nothing _ = Nothing
go (Just !result) (ord -> d) | d >= 48 && d < 58 = Just $ result * 16 + fromIntegral d - 48
go (Just result) (ord -> d) | d >= 97 && d < 124 = Just $ result * 16 + fromIntegral d - 87
go _ _ = Nothing
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