{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE Strict #-}
{- | Conversion of the hs-opentelemetry internal representation of the trace ID and the span ID and the B3 header representation of them each other.
|----------------+---------------------------------------+------------------------------|
| | Trace ID | Span ID |
|----------------+---------------------------------------+------------------------------|
| Internal | 128-bit integer | 64-bit integer |
| B3 Header | Hex text of 64-bit or 128-bit integer | Hex text of 64-bit integer |
|----------------+---------------------------------------+------------------------------|
-}
module OpenTelemetry.Propagator.B3.Internal (
-- * Encoders
encodeTraceId,
encodeSpanId,
-- * Decoders
decodeXb3TraceIdHeader,
decodeXb3SpanIdHeader,
decodeXb3SampledHeader,
decodeXb3FlagsHeader,
decodeB3SampleHeader,
decodeB3SingleHeader,
-- * B3SingleHeader
B3SingleHeader (..),
-- * SampleState
SamplingState (..),
-- ** Conversions
samplingStateToValue,
samplingStateFromValue,
printSamplingStateSingle,
printSamplingStateMulti,
-- * Header Keys
b3Header,
xb3TraceIdHeader,
xb3SpanIdHeader,
xb3SampledHeader,
xb3FlagsHeader,
) where
--------------------------------------------------------------------------------
import Control.Applicative ((<|>))
import Control.Monad (void)
import qualified Data.Attoparsec.ByteString.Char8 as Atto
import Data.ByteString (ByteString)
import qualified Data.ByteString.Builder as BB
import qualified Data.ByteString.Lazy as BL
import qualified Data.Char as C
import Data.Functor (($>))
import Data.Text (Text)
import Network.HTTP.Types (HeaderName)
import OpenTelemetry.Trace.Id (Base (..), SpanId, TraceId, baseEncodedToSpanId, baseEncodedToTraceId, spanIdBaseEncodedBuilder, traceIdBaseEncodedBuilder)
import OpenTelemetry.Trace.TraceState (Value (..))
--------------------------------------------------------------------------------
encodeTraceId
:: TraceId
-> ByteString
-- ^ ASCII text of 64-bit integer
encodeTraceId = BL.toStrict . BB.toLazyByteString . traceIdBaseEncodedBuilder Base16
encodeSpanId
:: SpanId
-> ByteString
-- ^ ASCII text of 64-bit integer
encodeSpanId = BL.toStrict . BB.toLazyByteString . spanIdBaseEncodedBuilder Base16
--------------------------------------------------------------------------------
decodeXb3TraceIdHeader :: ByteString -> Maybe TraceId
decodeXb3TraceIdHeader tp = case Atto.parseOnly parserTraceId tp of
Left _ -> Nothing
Right traceId -> Just traceId
decodeXb3SpanIdHeader :: ByteString -> Maybe SpanId
decodeXb3SpanIdHeader tp = case Atto.parseOnly parserSpanId tp of
Left _ -> Nothing
Right spanId -> Just spanId
decodeXb3SampledHeader :: ByteString -> Maybe SamplingState
decodeXb3SampledHeader tp = case Atto.parseOnly parserXb3Sampled tp of
Left _ -> Nothing
Right sampled -> Just sampled
decodeXb3FlagsHeader :: ByteString -> Maybe SamplingState
decodeXb3FlagsHeader tp = case Atto.parseOnly parserXb3Flags tp of
Left _ -> Nothing
Right flags -> Just flags
decodeB3SingleHeader :: ByteString -> Maybe B3SingleHeader
decodeB3SingleHeader tp = case Atto.parseOnly parserB3Single tp of
Left _ -> Nothing
Right b3 -> Just b3
decodeB3SampleHeader :: ByteString -> Maybe SamplingState
decodeB3SampleHeader tp = case Atto.parseOnly parserSamplingState tp of
Left _ -> Nothing
Right b3 -> Just b3
--------------------------------------------------------------------------------
parserTraceId :: Atto.Parser TraceId
parserTraceId = do
traceIdBs <- Atto.takeWhile C.isHexDigit
case baseEncodedToTraceId Base16 traceIdBs of
Left err -> fail err
Right traceId -> pure traceId
parserSpanId :: Atto.Parser SpanId
parserSpanId = do
parentIdBs <- Atto.takeWhile C.isHexDigit
case baseEncodedToSpanId Base16 parentIdBs of
Left err -> fail err
Right ok -> pure ok
data SamplingState = Accept | Deny | Debug | Defer
deriving (Eq)
-- | Parser for the @x-b3-sampled@ header value.
parserXb3Sampled :: Atto.Parser SamplingState
parserXb3Sampled = accept <|> deny
where
accept = "1" $> Accept
deny = "0" $> Deny
parserXb3Flags :: Atto.Parser SamplingState
parserXb3Flags = "1" $> Debug
{- | Note that this parser is only correct for the B3 single header
format. In B3 Multi you can only pass a @0@ or @1@ for the sample
state for 'Accept' and 'Deny' respectively.
-}
parserSamplingState :: Atto.Parser SamplingState
parserSamplingState = accept <|> deny <|> debug
where
accept = "1" $> Accept
deny = "0" $> Deny
debug = "d" $> Debug
{- | Encode a 'SamplingState' as the Sampling State component of the
@b3@ header value.
-}
printSamplingStateSingle :: SamplingState -> Maybe Text
printSamplingStateSingle = \case
Accept -> Just "1"
Deny -> Just "0"
Debug -> Just "d"
Defer -> Nothing
printSamplingStateMulti :: SamplingState -> Maybe (HeaderName, Text)
printSamplingStateMulti = \case
Accept -> Just (xb3SampledHeader, "1")
Deny -> Just (xb3SampledHeader, "0")
Debug -> Just (xb3FlagsHeader, "1")
Defer -> Nothing
-- | Encode a 'SamplingState' as a 'Value'.
samplingStateToValue :: SamplingState -> Value
samplingStateToValue = \case
Accept -> Value "accept"
Deny -> Value "deny"
Debug -> Value "debug"
Defer -> Value "defer"
-- | Used to decode the 'SamplingState' from a 'TraceState' 'Value'.
samplingStateFromValue :: Value -> Maybe SamplingState
samplingStateFromValue = \case
Value "accept" -> Just Accept
Value "deny" -> Just Deny
Value "debug" -> Just Debug
Value "defer" -> Just Defer
_ -> Nothing
data B3SingleHeader = B3SingleHeader
{ traceId :: TraceId
, spanId :: SpanId
, samplingState :: SamplingState
, parentSpanId :: Maybe SpanId
}
parserB3Single :: Atto.Parser B3SingleHeader
parserB3Single = do
traceId <- parserTraceId
spanId <- void "-" *> parserSpanId
samplingState <- Atto.option Defer (void "-" *> parserSamplingState)
parentSpanId <- Atto.option Nothing (void "-" *> fmap Just parserSpanId)
pure B3SingleHeader {..}
--------------------------------------------------------------------------------
b3Header :: HeaderName
b3Header = "b3"
xb3TraceIdHeader :: HeaderName
xb3TraceIdHeader = "X-B3-TraceId"
xb3SpanIdHeader :: HeaderName
xb3SpanIdHeader = "X-B3-SpanId"
xb3SampledHeader :: HeaderName
xb3SampledHeader = "X-B3-Sampled"
xb3FlagsHeader :: HeaderName
xb3FlagsHeader = "X-B3-Flags"