{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
module Control.Monad.Trace.Class (
  
  Span(..), spanIsSampled, spanIsDebug,
  Context(..),
  TraceID(..), decodeTraceID, encodeTraceID,
  SpanID(..), decodeSpanID, encodeSpanID,
  Reference(..),
  
  
  MonadTrace(..),
  Builder(..), Name, builder,
  
  rootSpan, rootSpanWith, childSpan, childSpanWith,
  
  SamplingDecision(..),
  SamplingPolicy, alwaysSampled, neverSampled, sampledWithProbability, sampledWhen, debugEnabled,
  
  
  
  
  Key, Value, tagDoubleValue, tagInt64Value, tagTextValue, logValue, logValueAt
) where
import Control.Monad.Trace.Internal
import Control.Monad.Except (ExceptT(..))
import Control.Monad.Identity (Identity(..))
import Control.Monad.Reader (ReaderT(..))
import qualified Control.Monad.RWS.Lazy as RWS.Lazy
import qualified Control.Monad.RWS.Strict as RWS.Strict
import qualified Control.Monad.State.Lazy as State.Lazy
import qualified Control.Monad.State.Strict as State.Strict
import Control.Monad.Trans.Class (MonadTrans, lift)
import qualified Control.Monad.Writer.Lazy as Writer.Lazy
import qualified Control.Monad.Writer.Strict as Writer.Strict
import qualified Data.Aeson as JSON
import Data.ByteString (ByteString)
import Data.Int (Int64)
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.Set (Set)
import qualified Data.Set as Set
import Data.String (IsString(..))
import Data.Text (Text)
import qualified Data.Text as T
import Data.Time.Clock.POSIX (POSIXTime)
import System.Random (randomRIO)
class Monad m => MonadTrace m where
  
  
  
  trace :: Builder -> m a -> m a
  
  activeSpan :: m (Maybe Span)
  default activeSpan :: (MonadTrace n, MonadTrans t, m ~ t n) => m (Maybe Span)
  activeSpan = lift activeSpan
  
  addSpanEntry :: Key -> Value -> m ()
  default addSpanEntry :: (MonadTrace n, MonadTrans t, m ~ t n) => Key -> Value -> m ()
  addSpanEntry key = lift . addSpanEntry key
instance MonadTrace m => MonadTrace (ExceptT e m) where
  trace name (ExceptT actn) = ExceptT $ trace name actn
instance MonadTrace m => MonadTrace (ReaderT r m) where
  trace name (ReaderT actn) = ReaderT $ \r -> trace name (actn r)
instance (MonadTrace m, Monoid w) => MonadTrace (RWS.Lazy.RWST r w s m) where
  trace name (RWS.Lazy.RWST actn) = RWS.Lazy.RWST $ \r s -> trace name (actn r s)
instance (MonadTrace m, Monoid w) => MonadTrace (RWS.Strict.RWST r w s m) where
  trace name (RWS.Strict.RWST actn) = RWS.Strict.RWST $ \r s -> trace name (actn r s)
instance MonadTrace m => MonadTrace (State.Lazy.StateT s m) where
  trace name (State.Lazy.StateT actn) = State.Lazy.StateT $ \s -> trace name (actn s)
instance MonadTrace m => MonadTrace (State.Strict.StateT s m) where
  trace name (State.Strict.StateT actn) = State.Strict.StateT $ \s -> trace name (actn s)
instance (MonadTrace m, Monoid w) => MonadTrace (Writer.Lazy.WriterT w m) where
  trace name (Writer.Lazy.WriterT actn) = Writer.Lazy.WriterT $ trace name actn
instance (MonadTrace m, Monoid w) => MonadTrace (Writer.Strict.WriterT w m) where
  trace name (Writer.Strict.WriterT actn) = Writer.Strict.WriterT $ trace name actn
instance MonadTrace Identity where
  trace _ = id
  activeSpan = pure Nothing
  addSpanEntry _ _ = pure ()
data Builder = Builder
  { builderName :: !Name
  
  , builderTraceID :: !(Maybe TraceID)
  
  
  , builderSpanID :: !(Maybe SpanID)
  
  , builderReferences :: !(Set Reference)
  
  , builderTags :: !(Map Key JSON.Value)
  
  , builderBaggages :: !(Map Key ByteString)
  
  , builderSamplingPolicy :: !(Maybe SamplingPolicy)
  
  
  }
builder :: Name -> Builder
builder name = Builder name Nothing Nothing Set.empty Map.empty Map.empty Nothing
instance IsString Builder where
  fromString = builder . T.pack
type SamplingPolicy = IO SamplingDecision
alwaysSampled :: SamplingPolicy
alwaysSampled = pure Always
neverSampled :: SamplingPolicy
neverSampled = pure Never
debugEnabled :: SamplingPolicy
debugEnabled = pure Debug
sampledWhen :: Bool -> SamplingPolicy
sampledWhen b = pure $ if b then Always else Never
sampledWithProbability :: Double -> SamplingPolicy
sampledWithProbability r = randomRIO (0, 1) >>= sampledWhen . (< r)
rootSpanWith :: MonadTrace m => (Builder -> Builder) -> SamplingPolicy -> Name -> m a -> m a
rootSpanWith f policy name = trace $ (f $ builder name) { builderSamplingPolicy = Just policy }
rootSpan :: MonadTrace m => SamplingPolicy -> Name -> m a -> m a
rootSpan = rootSpanWith id
childSpanWith :: MonadTrace m => (Builder -> Builder) -> Name -> m a -> m a
childSpanWith f name actn = activeSpan >>= \case
  Nothing -> actn
  Just spn -> do
    let
      ctx = spanContext spn
      bldr = f $ builder name
      bldr' = bldr
        { builderTraceID = Just $ contextTraceID ctx
        , builderReferences = Set.insert (ChildOf $ contextSpanID ctx) (builderReferences bldr) }
    trace bldr' actn
childSpan :: MonadTrace m => Name -> m a -> m a
childSpan = childSpanWith id
tagDoubleValue :: Double -> Value
tagDoubleValue = TagValue . JSON.toJSON
tagInt64Value :: Integral a => a -> Value
tagInt64Value = TagValue . (JSON.toJSON @Int64) . fromIntegral
tagTextValue :: Text -> Value
tagTextValue = TagValue . JSON.toJSON
logValue :: JSON.ToJSON a => a -> Value
logValue v = LogValue (JSON.toJSON v) Nothing
logValueAt :: JSON.ToJSON a => POSIXTime -> a -> Value
logValueAt t v = LogValue (JSON.toJSON v) (Just t)