packages feed

lightstep-haskell-0.4.4: src/LightStep/Propagation.hs

{-# LANGUAGE OverloadedStrings #-}

module LightStep.Propagation where

import Control.Lens
import Data.Bits
import qualified Data.ByteString as BS
import Data.List (foldl')
import Data.ProtoLens.Message (defMessage)
import GHC.Word
import Network.HTTP.Types
import Network.Wai
import Proto.Collector
import Proto.Collector_Fields

extractSpanContextFromRequest :: Request -> Maybe SpanContext
extractSpanContextFromRequest = extractSpanContextFromRequestHeaders . requestHeaders

extractSpanContextFromRequestHeaders :: RequestHeaders -> Maybe SpanContext
extractSpanContextFromRequestHeaders hdrs =
  case foldl' go (Nothing, Nothing) hdrs of
    (Just tid, Just sid) ->
      defMessage
        & traceId .~ tid
        & spanId .~ sid
        & Just
    _ -> Nothing
  where
    -- TODO: 128-bit x-b3-traceid
    go (_, sid) ("x-b3-traceid", decode_u64 -> Just tid) = (Just tid, sid)
    go (tid, _) ("x-b3-spanid", decode_u64 -> Just sid) = (tid, Just sid)
    go (_, sid) ("ot-tracer-traceid", decode_u64 -> Just tid) = (Just tid, sid)
    go (tid, _) ("ot-tracer-spanid", decode_u64 -> Just sid) = (tid, Just sid)
    -- TODO: Propagation of the whole span context in a single header
    -- go _ ("x-ot-span-context", parse128 -> Just (tid, sid)) = (Just tid, Just sid)

    go acc _ = acc

-- parse128 :: BS.ByteString -> Maybe (Word64, Word64)
-- parse128 _ = Nothing -- TODO

headersForSpanContext :: SpanContext -> [(BS.ByteString, BS.ByteString)]
headersForSpanContext ctx =
  [ ("ot-tracer-traceid", encode_u64 $ ctx ^. traceId),
    ("ot-tracer-spanid", encode_u64 $ ctx ^. spanId)
  ]

b3headersForSpanContext :: SpanContext -> [(BS.ByteString, BS.ByteString)]
b3headersForSpanContext ctx =
  [ ("x-b3-traceid", encode_u64 $ ctx ^. traceId),
    ("x-b3-spanid", encode_u64 $ ctx ^. spanId),
    ("x-b3-sampled", "true")
  ]

encode_u64 :: Word64 -> BS.ByteString
-- TODO: a better way to encode base16
encode_u64 x =
  let encodeChar :: Word8 -> Word8
      encodeChar c | c < 10 = 48 + c
      encodeChar c = 87 + c
      hexDigits = go 16 [] x
      go :: Int -> [Word8] -> Word64 -> [Word8]
      go 0 acc _ = acc
      go n acc v = go (n - 1) (encodeChar (fromIntegral v .&. 0x0f) : acc) (v `div` 16)
   in BS.pack hexDigits

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