{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE OverloadedStrings #-} {-| Module : Instana.SDK.TracingHeaders Description : A set of tracing headers -} module Instana.SDK.TracingHeaders ( TracingHeaders(..) , TracingLevel(..) , levelHeaderName , parseXInstanaL , readHttpTracingHeaders , spanIdHeaderName , stringToTracingLevel , syntheticHeaderName , traceIdHeaderName , traceparentHeaderName , tracestateHeaderName , tracingLevelToString ) where import qualified Data.ByteString.Char8 as BSC8 import qualified Data.List as List import GHC.Generics import qualified Network.HTTP.Types.Header as HTTPHeader import qualified Network.Wai as Wai import Text.Regex.PCRE ((=~)) import Instana.SDK.Internal.Util ((|>)) -- |X-INSTANA-T traceIdHeaderName :: HTTPHeader.HeaderName traceIdHeaderName = "X-INSTANA-T" -- |X-INSTANA-S spanIdHeaderName :: HTTPHeader.HeaderName spanIdHeaderName = "X-INSTANA-S" -- |X-INSTANA-L levelHeaderName :: HTTPHeader.HeaderName levelHeaderName = "X-INSTANA-L" -- |X-INSTANA-SYNTHETIC syntheticHeaderName :: HTTPHeader.HeaderName syntheticHeaderName = "X-INSTANA-SYNTHETIC" -- |traceparent traceparentHeaderName :: HTTPHeader.HeaderName traceparentHeaderName = "traceparent" -- |tracestate tracestateHeaderName :: HTTPHeader.HeaderName tracestateHeaderName = "tracestate" -- |Tracing level. data TracingLevel = -- |Record calls. Trace -- |Don't record calls. | Suppress deriving (Eq, Generic, Show) -- |Converts a string into the tracing level. stringToTracingLevel :: String -> TracingLevel stringToTracingLevel s = if s == "0" then Suppress else Trace -- |Parses the X-INSTANA-L value to determine the tracing level, and optionally -- the correlation type and correlation ID. parseXInstanaL :: Maybe String -> (TracingLevel, Maybe String, Maybe String) parseXInstanaL xInstanaLValueMaybe = case xInstanaLValueMaybe of Nothing -> (Trace, Nothing, Nothing) Just xInstanaLValue -> let (_, _, _, groups) = xInstanaLValue =~ xInstanaLRegex :: (String, String, String, [String]) in case groups of [] -> (Trace, Nothing, Nothing) ["", "", ""] -> (Trace, Nothing, Nothing) ["0", _, _] -> (Suppress, Nothing, Nothing) ["1", "", ""] -> (Trace, Nothing, Nothing) ["1", _correlationType, _correlationId] -> (Trace, Just _correlationType, Just _correlationId) _ -> (Trace, Nothing, Nothing) xInstanaLRegex :: String xInstanaLRegex = -- example "1,correlationType=web;correlationId=1234567890abcdef" "^\\s*([01])\\s*(?:,\\s*correlationType\\s*=\\s*([^ ;]*)\\s*;\\s*correlationId\\s*=\\s*([^ ;]*)\\s*)?$" -- |Converts tracing level into a string. tracingLevelToString :: TracingLevel -> String tracingLevelToString l = case l of Trace -> "1" Suppress -> "0" -- |A set of tracing headers. data TracingHeaders = TracingHeaders { -- |the trace ID traceId :: Maybe String -- |the span ID , spanId :: Maybe String -- |the tracing level (on/off) , level :: TracingLevel -- |eum correlation type , correlationType :: Maybe String -- |eum correlation ID , correlationId :: Maybe String -- |synthetic flag , synthetic :: Bool -- |W3C Trace Context traceparent , traceparent :: Maybe String -- |W3C Trace Context tracestate , tracestate :: Maybe String } deriving (Eq, Generic, Show) -- |Reads the Instana tracing headers -- (https://docs.instana.io/core_concepts/tracing/#http-tracing-headers) from -- the given request. readHttpTracingHeaders :: Wai.Request -> TracingHeaders readHttpTracingHeaders request = let headers = Wai.requestHeaders request -- lookup is automatically case insensitive because -- HeaderName = CI ByteString (CI -> Case Insensitive String) tId = headers |> List.lookup traceIdHeaderName |> (<$>) BSC8.unpack sId = headers |> List.lookup spanIdHeaderName |> (<$>) BSC8.unpack xInstanaLValue = headers |> List.lookup levelHeaderName |> (<$>) BSC8.unpack (lvl, crtp, crid) = parseXInstanaL xInstanaLValue sy = headers |> List.lookup syntheticHeaderName |> (<$>) BSC8.unpack tp = headers |> List.lookup traceparentHeaderName |> (<$>) BSC8.unpack ts = headers |> List.lookup tracestateHeaderName |> (<$>) BSC8.unpack in TracingHeaders { traceId = tId , spanId = sId , level = lvl , correlationType = crtp , correlationId = crid , synthetic = sy == (Just "1") , traceparent = tp , tracestate = ts }