packages feed

hw-polysemy-0.2.14.8: core/HaskellWorks/Polysemy/Log.hs

{-# LANGUAGE DataKinds           #-}
{-# LANGUAGE FlexibleContexts    #-}
{-# LANGUAGE FlexibleInstances   #-}
{-# LANGUAGE GADTs               #-}
{-# LANGUAGE PolyKinds           #-}
{-# LANGUAGE RankNTypes          #-}
{-# LANGUAGE ScopedTypeVariables #-}

{- HLINT ignore "Use let" -}

module HaskellWorks.Polysemy.Log
  ( interpretDataLogNoop,
    interpretDataLogLocalNoop,
    interpretDataLogToJsonStdout,
    logEntryToJson,
    logMessageToJson,

    annotateCs,
    logCs,

    setLogLevelFromEnv,
  ) where

import           Data.Aeson
import qualified Data.Aeson                               as J
import qualified Data.ByteString.Lazy                     as LBS
import qualified Data.Text                                as T
import qualified Data.Text.Encoding                       as T
import           Data.Time
import qualified GHC.Stack                                as GHC
import           HaskellWorks.Polysemy.System.Environment
import           HaskellWorks.Prelude
import           Polysemy
import           Polysemy.Internal.Tactics                (liftT)
import           Polysemy.Log
import qualified Polysemy.Log.Effect.DataLog              as Log
import           Polysemy.Time
import qualified Polysemy.Time                            as Time

interpretDataLogNoop :: forall a r. ()
  => InterpreterFor (DataLog a) r
interpretDataLogNoop =
  interpretDataLogLocalNoop id

interpretDataLogLocalNoop :: forall a r. ()
  => (a -> a)
  -> InterpreterFor (DataLog a) r
interpretDataLogLocalNoop context =
  interpretH \case
    Log.DataLog _ ->
      liftT (pure ())
    Log.Local f ma ->
      raise . interpretDataLogLocalNoop (f . context) =<< runT ma
{-# inline interpretDataLogLocalNoop #-}

interpretDataLogToJsonStdout :: forall e a r. ()
  => Member (Embed IO) r
  => (e -> J.Value)
  -> Sem (DataLog e : r) a
  -> Sem r a
interpretDataLogToJsonStdout toJson =
  interpretDataLogStdoutWith (T.decodeUtf8 . LBS.toStrict . J.encode . toJson)

-- | Log a datalog message with the given severity and provided callstack.
annotateCs :: forall a r. ()
  => Member GhcTime r
  => CallStack
  -> a
  -> Sem r (LogEntry a)
annotateCs cs msg = do
  time <- Time.now @UTCTime @Day
  pure (LogEntry msg time cs)

-- | Log a text message with the given severity and provided callstack.
logCs :: ()
  => Members [Logger, GhcTime] r
  => CallStack
  -> Severity
  -> Text
  -> Sem r ()
logCs cs severity message =
  withFrozenCallStack do
    send . DataLog =<< annotateCs cs (LogMessage severity message)
{-# inline logCs #-}

logEntryToJson :: forall a. ()
  => (a -> Value)
  -> LogEntry a
  -> Value
logEntryToJson aToJson (LogEntry value time callstack) =
    object
      [ "time" .= time
      , "data" .= aToJson value
      , "callstack" .= fmap callsiteToJson (GHC.getCallStack callstack)
      ]
    where
      callsiteToJson :: ([Char], GHC.SrcLoc) -> Value
      callsiteToJson (caller, srcLoc) =
        object
          [ "caller"    .= caller
          , "package"   .= GHC.srcLocPackage srcLoc
          , "module"    .= GHC.srcLocModule srcLoc
          , "file"      .= GHC.srcLocFile srcLoc
          , "startLine" .= GHC.srcLocStartLine srcLoc
          , "startCol"  .= GHC.srcLocStartCol srcLoc
          , "endLine"   .= GHC.srcLocEndLine srcLoc
          , "endCol"    .= GHC.srcLocEndCol srcLoc
          ]

logMessageToJson :: LogMessage -> Value
logMessageToJson (LogMessage severity message) =
    object
      [ "severity" .= show severity
      , "message"  .= message
      ]

-- | Set the log level for the duration of the computation to the severity provided in the
-- environment variable of the given name or else the default severity for the duration of
-- the computation.
--
-- Values for the log level are case-insensitive and can be one of the following:
--
--   * trace
--   * debug
--   * info
--   * warn
--   * error
--   * crit
setLogLevelFromEnv :: ()
  => HasCallStack
  => Member (DataLog (LogEntry LogMessage)) r
  => Member (Embed IO) r
  => String
  -> Severity
  -> Sem r a
  -> Sem r a
setLogLevelFromEnv envVarName defaultSeverity f = do
  maybeSeverityString <- lookupEnv envVarName

  let maybeSeverity = maybeSeverityString >>= parseSeverity . T.pack

  setLogLevel (maybeSeverity <|> Just defaultSeverity) f