{-# LANGUAGE UndecidableInstances #-}
module Control.Eff.Log.Handler
(
logMsg
, logWithSeverity
, logEmergency
, logAlert
, logCritical
, logError
, logWarning
, logNotice
, logInfo
, logDebug
, includeLogMessages
, excludeLogMessages
, setLogPredicate
, modifyLogPredicate
, askLogPredicate
, LogWriterReader
, setLogWriter
, addLogWriter
, withLogFileAppender
, censorLogs
, censorLogsM
, askLogWriter
, modifyLogWriter
, Logs()
, LogsTo
, withConsoleLogging
, withIoLogging
, withLogging
, withSomeLogging
, LoggingAndIo
, runLogs
, respondToLogMessage
, interceptLogMessages
, runLogWriterReader
)
where
import Control.DeepSeq
import Control.Eff as Eff
import Control.Eff.Extend
import Control.Eff.Log.Message
import Control.Eff.Log.Writer
import qualified Control.Exception.Safe as Safe
import Control.Lens
import Control.Monad ( when, (>=>) )
import Control.Monad.Base ( MonadBase() )
import qualified Control.Monad.Catch as Catch
import Control.Monad.Trans.Control ( MonadBaseControl
( restoreM
, liftBaseWith
, StM
)
, liftBaseOp
)
import Data.Default
import Data.Function ( fix )
import GHC.Stack ( HasCallStack
, callStack
, withFrozenCallStack
)
import qualified System.IO as IO
import System.Directory ( canonicalizePath
, createDirectoryIfMissing
)
import System.FilePath ( takeDirectory )
data Logs v where
AskLogFilter
:: Logs LogPredicate
WriteLogMessage
:: !LogMessage -> Logs ()
instance forall e a k. Handle Logs e a (LogPredicate -> k) where
handle h q AskLogFilter p = h (q ^$ p ) p
handle h q (WriteLogMessage _) p = h (q ^$ ()) p
instance forall m e. (MonadBase m m, LiftedBase m e, SupportsLogger m (Logs ': e), SetMember LogWriterReader (LogWriterReader m) (Logs ': e))
=> MonadBaseControl m (Eff (Logs ': e)) where
type StM (Eff (Logs ': e)) a = StM (Eff e) a
liftBaseWith f = do
lf <- askLogPredicate
raise (liftBaseWith (\runInBase -> f (runInBase . runLogs @m lf)))
restoreM = raise . restoreM
instance (LiftedBase m e, Catch.MonadThrow (Eff e))
=> Catch.MonadThrow (Eff (Logs ': e)) where
throwM exception = raise (Catch.throwM exception)
instance (Applicative m, LiftedBase m e, Catch.MonadCatch (Eff e), SupportsLogger m (Logs ': e), SetMember LogWriterReader (LogWriterReader m) (Logs ': e))
=> Catch.MonadCatch (Eff (Logs ': e)) where
catch effect handler = do
lf <- askLogPredicate
let lower = runLogs @m lf
nestedEffects = lower effect
nestedHandler exception = lower (handler exception)
raise (Catch.catch nestedEffects nestedHandler)
instance (Applicative m, LiftedBase m e, Catch.MonadMask (Eff e), SupportsLogger m (Logs ': e), SetMember LogWriterReader (LogWriterReader m) (Logs ': e))
=> Catch.MonadMask (Eff (Logs ': e)) where
mask maskedEffect = do
lf <- askLogPredicate
let
lower :: Eff (Logs ': e) a -> Eff e a
lower = runLogs @m lf
raise
(Catch.mask
(\nestedUnmask -> lower
(maskedEffect
( raise . nestedUnmask . lower )
)
)
)
uninterruptibleMask maskedEffect = do
lf <- askLogPredicate
let
lower :: Eff (Logs ': e) a -> Eff e a
lower = runLogs @m lf
raise
(Catch.uninterruptibleMask
(\nestedUnmask -> lower
(maskedEffect
( raise . nestedUnmask . lower )
)
)
)
generalBracket acquire release useIt = do
lf <- askLogPredicate
let
lower :: Eff (Logs ': e) a -> Eff e a
lower = runLogs @m lf
raise
(Catch.generalBracket
(lower acquire)
(((.).(.)) lower release)
(lower . useIt)
)
type LogsTo h e = (Member Logs e, SupportsLogger h e, SetMember LogWriterReader (LogWriterReader h) e)
withConsoleLogging
:: SetMember Lift (Lift IO) e
=> String
-> Facility
-> LogPredicate
-> Eff (Logs : LogWriterReader IO : e) a
-> Eff e a
withConsoleLogging = withIoLogging consoleLogWriter
withIoLogging
:: SetMember Lift (Lift IO) e
=> LogWriter IO
-> String
-> Facility
-> LogPredicate
-> Eff (Logs : LogWriterReader IO : e) a
-> Eff e a
withIoLogging lw appName facility defaultPredicate =
withLogging (defaultIoLogWriter appName facility lw)
. setLogPredicate defaultPredicate
withLogging ::
forall h e a. (Applicative h, LogsTo h (Logs ': LogWriterReader h ': e))
=> LogWriter h -> Eff (Logs ': LogWriterReader h ': e) a -> Eff e a
withLogging lw = runLogWriterReader lw . runLogs allLogMessages
withSomeLogging ::
forall h e a.
(Applicative h, LogsTo h (Logs ': LogWriterReader h ': e))
=> Eff (Logs ': LogWriterReader h ': e) a
-> Eff e a
withSomeLogging = withLogging (noOpLogWriter @h)
type LoggingAndIo = '[Logs, LogWriterReader IO, Lift IO]
runLogs
:: forall h e b .
(LogsTo h (Logs ': e), SupportsLogger h (Logs ': e))
=> LogPredicate
-> Eff (Logs ': e) b
-> Eff e b
runLogs p m =
fix (handle_relay (\a _ -> return a)) (sendLogMessageToLogWriter m) p
logMsg :: forall e m . (HasCallStack, Member Logs e, ToLogMessage m) => m -> Eff e ()
logMsg (toLogMessage -> msgIn) =
withFrozenCallStack $ do
lf <- askLogPredicate
when (lf msgIn) $
msgIn `deepseq` send @Logs (WriteLogMessage msgIn)
logWithSeverity
:: forall e .
( HasCallStack
, Member Logs e
)
=> Severity
-> String
-> Eff e ()
logWithSeverity !s =
withFrozenCallStack
$ logMsg
. setCallStack callStack
. set lmSeverity s
. flip (set lmMessage) def
logEmergency
:: forall e .
( HasCallStack
, Member Logs e
)
=> String
-> Eff e ()
logEmergency = withFrozenCallStack (logWithSeverity emergencySeverity)
logAlert
:: forall e .
( HasCallStack
, Member Logs e
)
=> String
-> Eff e ()
logAlert = withFrozenCallStack (logWithSeverity alertSeverity)
logCritical
:: forall e .
( HasCallStack
, Member Logs e
)
=> String
-> Eff e ()
logCritical = withFrozenCallStack (logWithSeverity criticalSeverity)
logError
:: forall e .
( HasCallStack
, Member Logs e
)
=> String
-> Eff e ()
logError = withFrozenCallStack (logWithSeverity errorSeverity)
logWarning
:: forall e .
( HasCallStack
, Member Logs e
)
=> String
-> Eff e ()
logWarning = withFrozenCallStack (logWithSeverity warningSeverity)
logNotice
:: forall e .
( HasCallStack
, Member Logs e
)
=> String
-> Eff e ()
logNotice = withFrozenCallStack (logWithSeverity noticeSeverity)
logInfo
:: forall e .
( HasCallStack
, Member Logs e
)
=> String
-> Eff e ()
logInfo = withFrozenCallStack (logWithSeverity informationalSeverity)
logDebug
:: forall e .
( HasCallStack
, Member Logs e
)
=> String
-> Eff e ()
logDebug = withFrozenCallStack (logWithSeverity debugSeverity)
askLogPredicate :: forall e . (Member Logs e) => Eff e LogPredicate
askLogPredicate = send @Logs AskLogFilter
setLogPredicate
:: forall r b
. (Member Logs r, HasCallStack)
=> LogPredicate
-> Eff r b
-> Eff r b
setLogPredicate = modifyLogPredicate . const
modifyLogPredicate
:: forall e b
. (Member Logs e, HasCallStack)
=> (LogPredicate -> LogPredicate)
-> Eff e b
-> Eff e b
modifyLogPredicate lpIn e = askLogPredicate >>= fix step e . lpIn
where
ret x _ = return x
step :: (Eff e b -> LogPredicate -> Eff e b) -> Eff e b -> LogPredicate -> Eff e b
step k (E q (prj -> Just (WriteLogMessage !l))) lp = do
logMsg l
respond_relay @Logs ret k (q ^$ ()) lp
step k m lp = respond_relay @Logs ret k m lp
includeLogMessages
:: forall e a . (Member Logs e)
=> LogPredicate -> Eff e a -> Eff e a
includeLogMessages p = modifyLogPredicate (\p' m -> p' m || p m)
excludeLogMessages
:: forall e a . (Member Logs e)
=> LogPredicate -> Eff e a -> Eff e a
excludeLogMessages p = modifyLogPredicate (\p' m -> not (p m) && p' m)
respondToLogMessage
:: forall r b
. (Member Logs r)
=> (LogMessage -> Eff r ())
-> Eff r b
-> Eff r b
respondToLogMessage f e = askLogPredicate >>= fix step e
where
step :: (Eff r b -> LogPredicate -> Eff r b) -> Eff r b -> LogPredicate -> Eff r b
step k (E q (prj -> Just (WriteLogMessage !l))) lp = do
f l
respond_relay @Logs ret k (q ^$ ()) lp
step k m lp = respond_relay @Logs ret k m lp
ret x _lf = return x
interceptLogMessages
:: forall r b
. (Member Logs r)
=> (LogMessage -> Eff r LogMessage)
-> Eff r b
-> Eff r b
interceptLogMessages f = respondToLogMessage (f >=> logMsg)
sendLogMessageToLogWriter
:: forall h e b .
(LogsTo h e, SupportsLogger h e, Member Logs e)
=> Eff e b -> Eff e b
sendLogMessageToLogWriter = respondToLogMessage messageCallback
where
messageCallback msg = do
lw <- askLogWriter
liftLogWriter lw msg
data LogWriterReader h v where
AskLogWriter :: LogWriterReader h (LogWriter h)
instance Handle (LogWriterReader h) e a (LogWriter h -> k) where
handle k q AskLogWriter lw = k (q ^$ lw) lw
instance forall h m r. (MonadBase m m, LiftedBase m r)
=> MonadBaseControl m (Eff (LogWriterReader h ': r)) where
type StM (Eff (LogWriterReader h ': r)) a = StM (Eff r) a
liftBaseWith f = do
lf <- askLogWriter
raise (liftBaseWith (\runInBase -> f (runInBase . runLogWriterReader lf)))
restoreM = raise . restoreM
instance (LiftedBase m e, Catch.MonadThrow (Eff e))
=> Catch.MonadThrow (Eff (LogWriterReader h ': e)) where
throwM exception = raise (Catch.throwM exception)
instance (Applicative m, LiftedBase m e, Catch.MonadCatch (Eff e))
=> Catch.MonadCatch (Eff (LogWriterReader h ': e)) where
catch effect handler = do
lf <- askLogWriter
let lower = runLogWriterReader lf
nestedEffects = lower effect
nestedHandler exception = lower (handler exception)
raise (Catch.catch nestedEffects nestedHandler)
instance (Applicative m, LiftedBase m e, Catch.MonadMask (Eff e))
=> Catch.MonadMask (Eff (LogWriterReader h ': e)) where
mask maskedEffect = do
lf <- askLogWriter
let
lower :: Eff (LogWriterReader h ': e) a -> Eff e a
lower = runLogWriterReader lf
raise
(Catch.mask
(\nestedUnmask -> lower
(maskedEffect
( raise . nestedUnmask . lower )
)
)
)
uninterruptibleMask maskedEffect = do
lf <- askLogWriter
let
lower :: Eff (LogWriterReader h ': e) a -> Eff e a
lower = runLogWriterReader lf
raise
(Catch.uninterruptibleMask
(\nestedUnmask -> lower
(maskedEffect
( raise . nestedUnmask . lower )
)
)
)
generalBracket acquire release useIt = do
lf <- askLogWriter
let
lower :: Eff (LogWriterReader h ': e) a -> Eff e a
lower = runLogWriterReader lf
raise
(Catch.generalBracket
(lower acquire)
(((.).(.)) lower release)
(lower . useIt)
)
runLogWriterReader :: LogWriter h -> Eff (LogWriterReader h ': e) a -> Eff e a
runLogWriterReader e m = fix (handle_relay (\x _ -> return x)) m e
askLogWriter :: SetMember LogWriterReader (LogWriterReader h) e => Eff e (LogWriter h)
askLogWriter = send AskLogWriter
modifyLogWriter
:: forall h e a. LogsTo h e => (LogWriter h -> LogWriter h) -> Eff e a -> Eff e a
modifyLogWriter f = localLogWriterReader . sendLogMessageToLogWriter
where
localLogWriterReader m =
f <$> askLogWriter >>= fix (respond_relay @(LogWriterReader h) (\x _ -> return x)) m
setLogWriter :: forall h e a. LogsTo h e => LogWriter h -> Eff e a -> Eff e a
setLogWriter = modifyLogWriter . const
censorLogs :: LogsTo h e => (LogMessage -> LogMessage) -> Eff e a -> Eff e a
censorLogs = modifyLogWriter . mappingLogWriter
censorLogsM
:: (LogsTo h e, Monad h)
=> (LogMessage -> h LogMessage) -> Eff e a -> Eff e a
censorLogsM = modifyLogWriter . mappingLogWriterM
addLogWriter :: forall h e a .
(HasCallStack, LogsTo h e, Monad h)
=> LogWriter h -> Eff e a -> Eff e a
addLogWriter lw2 = modifyLogWriter (\lw1 -> MkLogWriter (\m -> runLogWriter lw1 m >> runLogWriter lw2 m))
withLogFileAppender
:: ( Lifted IO e
, LogsTo IO e
, MonadBaseControl IO (Eff e)
)
=> FilePath
-> Eff e b
-> Eff e b
withLogFileAppender fnIn e = liftBaseOp withOpenedLogFile (`addLogWriter` e)
where
withOpenedLogFile
:: HasCallStack
=> (LogWriter IO -> IO a)
-> IO a
withOpenedLogFile ioE =
Safe.bracket
(do
fnCanon <- canonicalizePath fnIn
createDirectoryIfMissing True (takeDirectory fnCanon)
h <- IO.openFile fnCanon IO.AppendMode
IO.hSetBuffering h (IO.BlockBuffering (Just 1024))
return h
)
(\h -> Safe.try @IO @Catch.SomeException (IO.hFlush h) >> IO.hClose h)
(\h -> ioE (ioHandleLogWriter h))