{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE OverloadedStrings #-}
{-|
Module : Instana.SDK.Internal.Context
Description : An internal representation of a span with all values set
-}
module Instana.SDK.Internal.FullSpan
( FullSpan(..)
, FullSpanWithPid(..)
, SpanKind(..)
) where
import Data.Aeson (FromJSON, ToJSON, Value, (.:), (.=))
import qualified Data.Aeson as Aeson
import Data.Aeson.Types (Parser)
import Data.Text (Text)
import GHC.Generics
import Instana.SDK.Internal.Id (Id)
-- |Direction of the call.
data SpanKind =
-- |The monitored componenent receives a call.
Entry
-- |The monitored componenent calls something else.
| Exit
-- |An additional annotation that is added to the trace while a traced call
-- is being processed.
| Intermediate
deriving (Eq, Generic, Show)
instance FromJSON SpanKind where
parseJSON :: Value -> Parser SpanKind
parseJSON = Aeson.withScientific "span kind string" $
\k ->
case k of
-- (1=entry, 2=exit, 3=local/intermediate)
1 -> return Entry
2 -> return Exit
3 -> return Intermediate
_ ->
fail "expected numeric span kind (1, 2, or 3)."
instance ToJSON SpanKind where
toJSON :: SpanKind -> Value
toJSON k =
case k of
Entry -> Aeson.Number 1
Exit -> Aeson.Number 2
Intermediate -> Aeson.Number 3
-- |The `from` part of the span.
data From = From
{ entityId :: String
} deriving (Eq, Generic, Show)
instance FromJSON From where
parseJSON = Aeson.withObject "from" $
\f ->
From
<$> f .: "e" -- entityId
instance ToJSON From where
toJSON :: From -> Value
toJSON f = Aeson.object
[ "e" .= entityId f ]
-- |A representation of the span with all its data. This will be send to the
-- agent later.
data FullSpan = FullSpan
{ traceId :: Id
, spanId :: Id
, parentId :: Maybe Id
, spanName :: Text
, timestamp :: Int
, duration :: Int
, kind :: SpanKind
, errorCount :: Int
, spanData :: Value
} deriving (Eq, Generic, Show)
-- |Combines the actual span data with static per-process data (PID).
data FullSpanWithPid = FullSpanWithPid
{ fullSpan :: FullSpan
, pid :: String
} deriving (Eq, Generic, Show)
-- Compare
-- https://github.com/instana/technical-documentation/blob/master/tracing/format.md
-- Registered and SDK spans use the same format, exact same attributes. The only
-- difference is the data, SDK spans should provide data.sdk (see below).
instance ToJSON FullSpanWithPid where
toJSON :: FullSpanWithPid -> Value
toJSON fullSpanWithPid =
let
s = fullSpan fullSpanWithPid
p = pid fullSpanWithPid
in
Aeson.object
[ "t" .= traceId s
, "s" .= spanId s
, "p" .= parentId s
, "n" .= spanName s
, "ts" .= timestamp s
, "ta" .= ("haskell" :: String)
, "d" .= duration s
, "k" .= kind s
, "ec" .= errorCount s
, "data" .= spanData s
, "f" .= From p
-- TODO - missing attributes:
-- - data.service - should have dedicated functionality to be set (for SDK
-- spans)
-- - For SDK spans: Everything should be in data.sdk, structure is described in
-- https://github.com/instana/technical-documentation/blob/master/tracing/format.md#json-format-for-sdk-spans
-- - e: [{ # events that happened during this span, seems to be used mostly by EUM??
-- t: <long> # timestamp of this event relative to start (0 .. d)
-- v: <String> # type of this annotation (ttfb, dom-ready, etc)
-- }],
-- - f.h: AgentId/HostID (optional), specified in the language announce response body.
-- },
-- - b: { # batching data
-- s: <long> # size; amount of batched spans
-- d: <long> # duration in ms; more realistic time of time consumed by individual batched spans. (optional, regular duration taken if absent)
-- },
-- - stack: [{ # stack trace
-- c: <String> # Class name
-- m: <String> # Method name
-- n: <String> # Line number
-- f: <String> # File name (optional in Java)
-- }],
-- - deferred: <boolean> # whether the span is deferred (optional), ??
]