{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE InstanceSigs #-} {-# LANGUAGE ScopedTypeVariables #-} {-| Module : Instana.SDK.Internal.Id Description : A module for working with trace IDs and span IDs -} module Instana.SDK.Internal.Id ( Id , generate , fromString , fromText , longOrShortTraceId , longTraceId , toByteString , toByteStringUnshortened , toString , toStringUnshortened , toText -- exposed for testing purposes , createFromIntsForTest ) where import Control.Monad (replicateM) import Data.Aeson (FromJSON, ToJSON, Value) import qualified Data.Aeson as Aeson import Data.Aeson.Types (Parser) import qualified Data.ByteString.Char8 as BSC8 import qualified Data.String (IsString (..)) import Data.Text (Text) import qualified Data.Text as T import GHC.Generics import Numeric (showHex) import qualified System.Random as Random import Instana.SDK.Internal.Util (leftPad) -- |Represents an ID (trace ID, span ID). data Id = -- |a representation of a 64 bit ID with just enough Int components to -- reach 64 bits (used when generating new random IDs) IntComponents [Int] -- |a representation of a 64 bit ID as a plain string (used when -- deserializing IDs, for example when reading HTTP headers) | IdString String -- |a 16 character/64 bit ID that has been shortened from a longer original. -- The first component is the shortened ID, the second the is original ID. | ShortenedId String String deriving (Eq, Generic, Show) instance FromJSON Id where parseJSON :: Value -> Parser Id parseJSON = Aeson.withText "Id string" $ \string -> return $ fromString $ (T.unpack string) instance ToJSON Id where toJSON :: Id -> Value toJSON = Aeson.String . toText instance Data.String.IsString Id where fromString = fromString appendAsHex :: Int -> String -> Int -> String appendAsHex noOfComponents accumulator intValue = appendPaddedHex accumulator intValue where toHex = (flip showHex) "" . abs padding = 64 `div` noOfComponents `div` 4 toPaddedHex = leftPad padding . toHex appendPaddedHex = flip ((++) . toPaddedHex) -- |Generates a new random ID. generate :: IO Id generate = do -- The number of bits used for an Haskell Int depends on the GHC -- implementation. It is guaranteed to cover the range from -2^29 to 2^29 - 1. -- On modern systems it is often -2^63 to 2^63 - 1. -- -- We need 64 bits, so we actually need to generate multiple Ints (usually -- two) and stitch them together during JSON decoding. let requiredNumberOfIntComponents = 64 `div` bitsPerInt (randomInts :: [Int]) <- replicateM requiredNumberOfIntComponents Random.randomIO return $ IntComponents $ randomInts bitsPerInt :: Int bitsPerInt = floor $ logBase (2 :: Double) $ fromIntegral (maxBound :: Int) -- |Converts an ID into a String toString :: Id -> String toString theId = case theId of IntComponents intComponents -> let noOfComponents = length intComponents in foldl (appendAsHex noOfComponents) "" (reverse intComponents) IdString string -> string ShortenedId string _ -> string -- |Converts an ID into a String, using the unshortened value (in case this is a -- ShortenedId). toStringUnshortened :: Id -> String toStringUnshortened theId = case theId of ShortenedId _ unshortened -> unshortened _ -> toString theId -- |Retrieves the original long trace ID as a string in case this ID has been -- created by shortening a 128 bit trace ID, or Nothing otherwise. longTraceId :: Id -> Maybe String longTraceId theId = case theId of ShortenedId _ original -> Just original _ -> Nothing -- |Retrieves the original long trace ID as a string in case this ID has been -- created by shortening a 128 bit trace ID, or the full ID as a string -- otherwise. longOrShortTraceId :: Id -> String longOrShortTraceId theId = case theId of ShortenedId _ original -> original _ -> toString theId --- |Converts a string into an ID. fromString :: String -> Id fromString = fromText . T.pack --- |Converts a Text into an ID. fromText :: Text -> Id fromText t = if T.length t > 16 then ShortenedId (T.unpack $ T.takeEnd 16 t) (T.unpack t) else IdString $ T.unpack t -- |Converts an ID into a Text toText :: Id -> Text toText = T.pack . toString -- |Converts an ID into a ByteString toByteString :: Id -> BSC8.ByteString toByteString = BSC8.pack . toString -- |Converts an ID into a ByteString, providing the unshortened value (this -- only makes a difference when this is a ShortenedId). toByteStringUnshortened :: Id -> BSC8.ByteString toByteStringUnshortened = BSC8.pack . toStringUnshortened -- |Only exposed for testing, do not use this. createFromIntsForTest :: [Int] -> Id createFromIntsForTest = IntComponents