{-# LANGUAGE DeriveGeneric     #-}
{-# LANGUAGE OverloadedStrings #-}
{-|
Module      : Instana.SDK.Span.Span
Description : A type for spans (entries, exits or intermediates).
-}
module Instana.SDK.Span.Span
  ( Span (Entry, Exit)
  , SpanKind (EntryKind, ExitKind, IntermediateKind)
  , addAnnotation
  , addAnnotationAt
  , addAnnotationValueAt
  , addJsonValueAt
  , addToErrorCount
  , correlationId
  , correlationType
  , errorCount
  , initialData
  , parentId
  , serviceName
  , setCorrelationId
  , setCorrelationType
  , setServiceName
  , setSynthetic
  , setTpFlag
  , setW3cTraceContext
  , spanData
  , spanId
  , spanKind
  , spanName
  , spanType
  , synthetic
  , timestamp
  , tpFlag
  , traceId
  , w3cTraceContext
  ) where


import           Data.Aeson                           (ToJSON)
import           Data.List                            as List
import           Data.Text                            as T
import           GHC.Generics

import           Instana.SDK.Internal.Id              (Id)
import           Instana.SDK.Internal.W3CTraceContext (W3CTraceContext)
import           Instana.SDK.Span.EntrySpan           (EntrySpan)
import qualified Instana.SDK.Span.EntrySpan           as EntrySpan
import           Instana.SDK.Span.ExitSpan            (ExitSpan)
import qualified Instana.SDK.Span.ExitSpan            as ExitSpan
import           Instana.SDK.Span.SpanData            (Annotation,
                                                       AnnotationValue,
                                                       SpanData (SpanData))
import qualified Instana.SDK.Span.SpanData            as SpanData
import           Instana.SDK.Span.SpanType            (SpanType (RegisteredSpan, SdkSpan))


-- |The span kind (entry, exit or intermediate).
data SpanKind =
    -- |The monitored componenent receives a call.
    EntryKind
    -- |The monitored componenent calls something else.
  | ExitKind
    -- |An additional annotation that is added to the trace while a traced call
    -- is being processed.
  | IntermediateKind
  deriving (SpanKind -> SpanKind -> Bool
(SpanKind -> SpanKind -> Bool)
-> (SpanKind -> SpanKind -> Bool) -> Eq SpanKind
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: SpanKind -> SpanKind -> Bool
$c/= :: SpanKind -> SpanKind -> Bool
== :: SpanKind -> SpanKind -> Bool
$c== :: SpanKind -> SpanKind -> Bool
Eq, (forall x. SpanKind -> Rep SpanKind x)
-> (forall x. Rep SpanKind x -> SpanKind) -> Generic SpanKind
forall x. Rep SpanKind x -> SpanKind
forall x. SpanKind -> Rep SpanKind x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cto :: forall x. Rep SpanKind x -> SpanKind
$cfrom :: forall x. SpanKind -> Rep SpanKind x
Generic, Int -> SpanKind -> ShowS
[SpanKind] -> ShowS
SpanKind -> String
(Int -> SpanKind -> ShowS)
-> (SpanKind -> String) -> ([SpanKind] -> ShowS) -> Show SpanKind
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [SpanKind] -> ShowS
$cshowList :: [SpanKind] -> ShowS
show :: SpanKind -> String
$cshow :: SpanKind -> String
showsPrec :: Int -> SpanKind -> ShowS
$cshowsPrec :: Int -> SpanKind -> ShowS
Show)


-- |A span.
data Span =
    Entry EntrySpan
  | Exit ExitSpan
  deriving (Span -> Span -> Bool
(Span -> Span -> Bool) -> (Span -> Span -> Bool) -> Eq Span
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Span -> Span -> Bool
$c/= :: Span -> Span -> Bool
== :: Span -> Span -> Bool
$c== :: Span -> Span -> Bool
Eq, (forall x. Span -> Rep Span x)
-> (forall x. Rep Span x -> Span) -> Generic Span
forall x. Rep Span x -> Span
forall x. Span -> Rep Span x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cto :: forall x. Rep Span x -> Span
$cfrom :: forall x. Span -> Rep Span x
Generic, Int -> Span -> ShowS
[Span] -> ShowS
Span -> String
(Int -> Span -> ShowS)
-> (Span -> String) -> ([Span] -> ShowS) -> Show Span
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Span] -> ShowS
$cshowList :: [Span] -> ShowS
show :: Span -> String
$cshow :: Span -> String
showsPrec :: Int -> Span -> ShowS
$cshowsPrec :: Int -> Span -> ShowS
Show)


-- |Accessor for the trace ID.
traceId :: Span -> Id
traceId :: Span -> Id
traceId span_ :: Span
span_ =
  case Span
span_ of
    Entry entry :: EntrySpan
entry -> EntrySpan -> Id
EntrySpan.traceId EntrySpan
entry
    Exit exit :: ExitSpan
exit   -> ExitSpan -> Id
ExitSpan.traceId ExitSpan
exit


-- |Accessor for the span ID.
spanId :: Span -> Id
spanId :: Span -> Id
spanId span_ :: Span
span_ =
  case Span
span_ of
    Entry entry :: EntrySpan
entry -> EntrySpan -> Id
EntrySpan.spanId EntrySpan
entry
    Exit exit :: ExitSpan
exit   -> ExitSpan -> Id
ExitSpan.spanId ExitSpan
exit


-- |Parent span ID.
parentId :: Span -> Maybe Id
parentId :: Span -> Maybe Id
parentId span_ :: Span
span_ =
  case Span
span_ of
    Entry entry :: EntrySpan
entry -> EntrySpan -> Maybe Id
EntrySpan.parentId EntrySpan
entry
    Exit exit :: ExitSpan
exit   -> Id -> Maybe Id
forall a. a -> Maybe a
Just (Id -> Maybe Id) -> Id -> Maybe Id
forall a b. (a -> b) -> a -> b
$ ExitSpan -> Id
ExitSpan.parentId ExitSpan
exit


-- |Type of span (registerd span vs. SDK span)
spanType :: Span -> SpanType
spanType :: Span -> SpanType
spanType span_ :: Span
span_ =
  case Span
span_ of
    Entry entry :: EntrySpan
entry -> EntrySpan -> SpanType
EntrySpan.spanType EntrySpan
entry
    Exit exit :: ExitSpan
exit   -> ExitSpan -> SpanType
ExitSpan.spanType ExitSpan
exit


-- |Name of span.
spanName :: Span -> Text
spanName :: Span -> Text
spanName span_ :: Span
span_ =
  case Span
span_ of
    Entry entry :: EntrySpan
entry -> EntrySpan -> Text
EntrySpan.spanName EntrySpan
entry
    Exit exit :: ExitSpan
exit   -> ExitSpan -> Text
ExitSpan.spanName ExitSpan
exit


-- |Kind of span.
spanKind :: Span -> SpanKind
spanKind :: Span -> SpanKind
spanKind span_ :: Span
span_ =
  case Span
span_ of
    Entry _ -> SpanKind
EntryKind
    Exit _  -> SpanKind
ExitKind


-- |Start time.
timestamp :: Span -> Int
timestamp :: Span -> Int
timestamp span_ :: Span
span_ =
  case Span
span_ of
    Entry entry :: EntrySpan
entry -> EntrySpan -> Int
EntrySpan.timestamp EntrySpan
entry
    Exit exit :: ExitSpan
exit   -> ExitSpan -> Int
ExitSpan.timestamp ExitSpan
exit


-- |Error count.
errorCount :: Span -> Int
errorCount :: Span -> Int
errorCount span_ :: Span
span_ =
  case Span
span_ of
    Entry entry :: EntrySpan
entry -> EntrySpan -> Int
EntrySpan.errorCount EntrySpan
entry
    Exit exit :: ExitSpan
exit   -> ExitSpan -> Int
ExitSpan.errorCount ExitSpan
exit


-- |Add to the error count.
addToErrorCount :: Int -> Span -> Span
addToErrorCount :: Int -> Span -> Span
addToErrorCount increment :: Int
increment span_ :: Span
span_ =
  case Span
span_ of
    Entry entry :: EntrySpan
entry ->
      EntrySpan -> Span
Entry (EntrySpan -> Span) -> EntrySpan -> Span
forall a b. (a -> b) -> a -> b
$ Int -> EntrySpan -> EntrySpan
EntrySpan.addToErrorCount Int
increment EntrySpan
entry
    Exit exit :: ExitSpan
exit ->
      ExitSpan -> Span
Exit (ExitSpan -> Span) -> ExitSpan -> Span
forall a b. (a -> b) -> a -> b
$ Int -> ExitSpan -> ExitSpan
ExitSpan.addToErrorCount Int
increment ExitSpan
exit


-- |An optional attribute for overriding the name of the service in Instana.
serviceName :: Span -> Maybe Text
serviceName :: Span -> Maybe Text
serviceName span_ :: Span
span_ =
  case Span
span_ of
    Entry entry :: EntrySpan
entry -> EntrySpan -> Maybe Text
EntrySpan.serviceName EntrySpan
entry
    Exit exit :: ExitSpan
exit   -> ExitSpan -> Maybe Text
ExitSpan.serviceName ExitSpan
exit


-- |Override the name of the service for the associated call in Instana.
setServiceName :: Text -> Span -> Span
setServiceName :: Text -> Span -> Span
setServiceName serviceName_ :: Text
serviceName_ span_ :: Span
span_ =
  case Span
span_ of
    Entry entry :: EntrySpan
entry ->
      EntrySpan -> Span
Entry (EntrySpan -> Span) -> EntrySpan -> Span
forall a b. (a -> b) -> a -> b
$ Text -> EntrySpan -> EntrySpan
EntrySpan.setServiceName Text
serviceName_ EntrySpan
entry
    Exit exit :: ExitSpan
exit ->
      ExitSpan -> Span
Exit (ExitSpan -> Span) -> ExitSpan -> Span
forall a b. (a -> b) -> a -> b
$ Text -> ExitSpan -> ExitSpan
ExitSpan.setServiceName Text
serviceName_ ExitSpan
exit


-- |The website monitoring correlation type.
correlationType :: Span -> Maybe Text
correlationType :: Span -> Maybe Text
correlationType span_ :: Span
span_ =
  case Span
span_ of
    Entry entry :: EntrySpan
entry -> EntrySpan -> Maybe Text
EntrySpan.correlationType EntrySpan
entry
    Exit _      -> Maybe Text
forall a. Maybe a
Nothing


-- |Set the website monitoring correlation type. This should only be set on
-- root entry spans. It will be silently ignored for other types of spans.
setCorrelationType :: Text -> Span -> Span
setCorrelationType :: Text -> Span -> Span
setCorrelationType correlationType_ :: Text
correlationType_ span_ :: Span
span_ =
  case Span
span_ of
    Entry entry :: EntrySpan
entry ->
      EntrySpan -> Span
Entry (EntrySpan -> Span) -> EntrySpan -> Span
forall a b. (a -> b) -> a -> b
$ Text -> EntrySpan -> EntrySpan
EntrySpan.setCorrelationType Text
correlationType_ EntrySpan
entry
    Exit _ ->
      Span
span_


-- |The website monitoring correlation ID.
correlationId :: Span -> Maybe Text
correlationId :: Span -> Maybe Text
correlationId span_ :: Span
span_ =
  case Span
span_ of
    Entry entry :: EntrySpan
entry -> EntrySpan -> Maybe Text
EntrySpan.correlationId EntrySpan
entry
    Exit _      -> Maybe Text
forall a. Maybe a
Nothing


-- |Set the website monitoring correlation ID. This should only be set on
-- root entry spans. It will be silently ignored for other types of spans.
setCorrelationId :: Text -> Span -> Span
setCorrelationId :: Text -> Span -> Span
setCorrelationId correlationId_ :: Text
correlationId_ span_ :: Span
span_ =
  case Span
span_ of
    Entry entry :: EntrySpan
entry ->
      EntrySpan -> Span
Entry (EntrySpan -> Span) -> EntrySpan -> Span
forall a b. (a -> b) -> a -> b
$ Text -> EntrySpan -> EntrySpan
EntrySpan.setCorrelationId Text
correlationId_ EntrySpan
entry
    Exit _ ->
      Span
span_


-- |The W3C Trace Context. An entry span only has an associated W3C trace
-- context, if W3C trace context headers have been received. In contrast,
-- exit spans always have an associated W3C trace context.
w3cTraceContext :: Span -> Maybe W3CTraceContext
w3cTraceContext :: Span -> Maybe W3CTraceContext
w3cTraceContext span_ :: Span
span_ =
  case Span
span_ of
    Entry entry :: EntrySpan
entry -> EntrySpan -> Maybe W3CTraceContext
EntrySpan.w3cTraceContext EntrySpan
entry
    Exit exit :: ExitSpan
exit   -> W3CTraceContext -> Maybe W3CTraceContext
forall a. a -> Maybe a
Just (W3CTraceContext -> Maybe W3CTraceContext)
-> W3CTraceContext -> Maybe W3CTraceContext
forall a b. (a -> b) -> a -> b
$ ExitSpan -> W3CTraceContext
ExitSpan.w3cTraceContext ExitSpan
exit


-- |Attaches a W3C trace context to the span.
setW3cTraceContext :: W3CTraceContext -> Span -> Span
setW3cTraceContext :: W3CTraceContext -> Span -> Span
setW3cTraceContext w3cTraceContext_ :: W3CTraceContext
w3cTraceContext_ span_ :: Span
span_ =
  case Span
span_ of
    Entry entry :: EntrySpan
entry ->
      EntrySpan -> Span
Entry (EntrySpan -> Span) -> EntrySpan -> Span
forall a b. (a -> b) -> a -> b
$ W3CTraceContext -> EntrySpan -> EntrySpan
EntrySpan.setW3cTraceContext W3CTraceContext
w3cTraceContext_ EntrySpan
entry
    Exit exit :: ExitSpan
exit ->
      ExitSpan -> Span
Exit (ExitSpan -> Span) -> ExitSpan -> Span
forall a b. (a -> b) -> a -> b
$ W3CTraceContext -> ExitSpan -> ExitSpan
ExitSpan.setW3cTraceContext W3CTraceContext
w3cTraceContext_ ExitSpan
exit


-- |The span.tp flag. A span with span.tp = True has inherited the trace ID/
-- parent ID from W3C trace context instead of Instana headers. Only valid for
-- non-root entry spans.
tpFlag :: Span -> Bool
tpFlag :: Span -> Bool
tpFlag span_ :: Span
span_ =
  case Span
span_ of
    Entry entry :: EntrySpan
entry -> EntrySpan -> Bool
EntrySpan.tpFlag EntrySpan
entry
    Exit _      -> Bool
False


-- |Set the span.tp flag. A span with span.tp = True has inherited the trace ID/
-- parent ID from W3C trace context instead of Instana headers. Only valid for
-- non-root entry spans, will be silently ignored for root entry spans and exit
-- spans.
setTpFlag :: Span -> Span
setTpFlag :: Span -> Span
setTpFlag span_ :: Span
span_ =
  case Span
span_ of
    Entry entry :: EntrySpan
entry ->
      EntrySpan -> Span
Entry (EntrySpan -> Span) -> EntrySpan -> Span
forall a b. (a -> b) -> a -> b
$ EntrySpan -> EntrySpan
EntrySpan.setTpFlag EntrySpan
entry
    Exit _ ->
      Span
span_


-- |The synthetic flag.
synthetic :: Span -> Bool
synthetic :: Span -> Bool
synthetic span_ :: Span
span_ =
  case Span
span_ of
    Entry entry :: EntrySpan
entry -> EntrySpan -> Bool
EntrySpan.synthetic EntrySpan
entry
    Exit _      -> Bool
False


-- |Set the synthetic flag. This should only be set on entry spans. It will be
-- silently ignored for other types of spans.
setSynthetic :: Bool -> Span -> Span
setSynthetic :: Bool -> Span -> Span
setSynthetic synthetic_ :: Bool
synthetic_ span_ :: Span
span_ =
  case Span
span_ of
    Entry entry :: EntrySpan
entry ->
      EntrySpan -> Span
Entry (EntrySpan -> Span) -> EntrySpan -> Span
forall a b. (a -> b) -> a -> b
$ Bool -> EntrySpan -> EntrySpan
EntrySpan.setSynthetic Bool
synthetic_ EntrySpan
entry
    Exit _ ->
      Span
span_


-- |Optional additional span data.
spanData :: Span -> SpanData
spanData :: Span -> SpanData
spanData span_ :: Span
span_ =
  case Span
span_ of
    Entry entry :: EntrySpan
entry -> EntrySpan -> SpanData
EntrySpan.spanData EntrySpan
entry
    Exit exit :: ExitSpan
exit   -> ExitSpan -> SpanData
ExitSpan.spanData ExitSpan
exit


-- |Add an annotation to the span's data section. For SDK spans, the annotation
-- is added to span.data.sdk.custom.tags, for registered spans it is added
-- directly to span.data.
addAnnotation :: Annotation -> Span -> Span
addAnnotation :: Annotation -> Span -> Span
addAnnotation annotation :: Annotation
annotation span_ :: Span
span_ =
  let
    wrappedAnnotation :: Annotation
wrappedAnnotation = Span -> Annotation -> Annotation
wrapAnnotationIfNecessary Span
span_ Annotation
annotation
  in
  case Span
span_ of
    Entry entry :: EntrySpan
entry -> EntrySpan -> Span
Entry (EntrySpan -> Span) -> EntrySpan -> Span
forall a b. (a -> b) -> a -> b
$ Annotation -> EntrySpan -> EntrySpan
EntrySpan.addAnnotation Annotation
wrappedAnnotation EntrySpan
entry
    Exit exit :: ExitSpan
exit   -> ExitSpan -> Span
Exit (ExitSpan -> Span) -> ExitSpan -> Span
forall a b. (a -> b) -> a -> b
$ Annotation -> ExitSpan -> ExitSpan
ExitSpan.addAnnotation Annotation
wrappedAnnotation ExitSpan
exit


-- |Add an annotation at the given path to the span's data section.
addAnnotationAt :: Text -> Annotation -> Span -> Span
addAnnotationAt :: Text -> Annotation -> Span -> Span
addAnnotationAt path :: Text
path annotation :: Annotation
annotation span_ :: Span
span_ =
  let
    pathPrefix :: [Text]
pathPrefix = Text -> Text -> [Text]
T.splitOn "." Text
path
    newData :: Annotation
newData = (Text -> Annotation -> Annotation)
-> Annotation -> [Text] -> Annotation
forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
List.foldr
      (\nextPathSegment :: Text
nextPathSegment accumulated :: Annotation
accumulated ->
        Text -> [Annotation] -> Annotation
SpanData.objectAnnotation Text
nextPathSegment [Annotation
accumulated]
      )
      Annotation
annotation
      [Text]
pathPrefix
  in
  Annotation -> Span -> Span
addAnnotation Annotation
newData Span
span_


-- |Add a list value at the given path to the span's data section. For SDK
-- spans, you should use addAnnotationValueToSdkSpan instead. For annotations with simple
-- values (string, number, boolean, etc.), you can also use the convenience
-- function addAnnotationAt.
addAnnotationValueAt :: Text -> AnnotationValue -> Span -> Span
addAnnotationValueAt :: Text -> AnnotationValue -> Span -> Span
addAnnotationValueAt path :: Text
path value :: AnnotationValue
value span_ :: Span
span_ =
  let
    pathSegments :: [Text]
pathSegments = Text -> Text -> [Text]
T.splitOn "." Text
path
    lastPathSegment :: Text
lastPathSegment = [Text] -> Text
forall a. [a] -> a
List.last [Text]
pathSegments
    pathPrefix :: [Text]
pathPrefix = Int -> [Text] -> [Text]
forall a. Int -> [a] -> [a]
List.take ([Text] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
List.length [Text]
pathSegments Int -> Int -> Int
forall a. Num a => a -> a -> a
- 1) [Text]
pathSegments
    newData :: Annotation
newData = (Text -> Annotation -> Annotation)
-> Annotation -> [Text] -> Annotation
forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
List.foldr
      (\nextPathSegment :: Text
nextPathSegment accumulated :: Annotation
accumulated ->
        Text -> [Annotation] -> Annotation
SpanData.objectAnnotation Text
nextPathSegment [Annotation
accumulated]
      )
      (Text -> AnnotationValue -> Annotation
SpanData.singleAnnotation Text
lastPathSegment AnnotationValue
value)
      [Text]
pathPrefix
  in
  Annotation -> Span -> Span
addAnnotation Annotation
newData Span
span_


-- |Add a simple value (string, boolean, number) at the given path to the span's
-- data section. Should not be used for objects or lists in case you intend to
-- merge them with additional values at the same path later.
addJsonValueAt :: ToJSON a => Text -> a -> Span -> Span
addJsonValueAt :: Text -> a -> Span -> Span
addJsonValueAt path :: Text
path value :: a
value span_ :: Span
span_ =
  Text -> AnnotationValue -> Span -> Span
addAnnotationValueAt Text
path (a -> AnnotationValue
forall a. ToJSON a => a -> AnnotationValue
SpanData.simpleValue a
value) Span
span_


wrapAnnotationIfNecessary :: Span -> Annotation -> Annotation
wrapAnnotationIfNecessary :: Span -> Annotation -> Annotation
wrapAnnotationIfNecessary span_ :: Span
span_ annotation :: Annotation
annotation =
  case Span -> SpanType
spanType Span
span_ of
    RegisteredSpan _ ->
      Annotation
annotation
    SdkSpan _ ->
      ( Text -> [Annotation] -> Annotation
SpanData.objectAnnotation "sdk" [
          Text -> [Annotation] -> Annotation
SpanData.objectAnnotation "custom" [
            Text -> [Annotation] -> Annotation
SpanData.objectAnnotation "tags" [
              Annotation
annotation
            ]
          ]
        ]
      )


-- |Returns the initial data (span.data) for a SpanType value.
initialData :: SpanKind -> SpanType -> SpanData
initialData :: SpanKind -> SpanType -> SpanData
initialData kind :: SpanKind
kind (SdkSpan s :: Text
s)     = SpanKind -> Text -> SpanData
initialSdkData SpanKind
kind Text
s
initialData _ (RegisteredSpan _) = SpanData
SpanData.empty


-- |Provides the initial data for an SDK span.
initialSdkData :: SpanKind -> Text -> SpanData
initialSdkData :: SpanKind -> Text -> SpanData
initialSdkData kind :: SpanKind
kind spanName_ :: Text
spanName_ =
  let
    sdkKind :: Text
    sdkKind :: Text
sdkKind =
      case SpanKind
kind of
        EntryKind        -> "entry"
        ExitKind         -> "exit"
        IntermediateKind -> "intermediate"
  in
  [Annotation] -> SpanData
SpanData
    [ Text -> [Annotation] -> Annotation
SpanData.objectAnnotation "sdk"
        [ Text -> Text -> Annotation
forall a. ToJSON a => Text -> a -> Annotation
SpanData.simpleAnnotation "name" Text
spanName_
        , Text -> Text -> Annotation
forall a. ToJSON a => Text -> a -> Annotation
SpanData.simpleAnnotation "type" Text
sdkKind
        ]
    ]