module System.Logger.Logger.Internal
(
LoggerConfig(..)
, loggerConfigQueueSize
, loggerConfigThreshold
, loggerConfigScope
, defaultLoggerConfig
, validateLoggerConfig
, pLoggerConfig
, Logger
, loggerScope
, loggerThreshold
, createLogger
, releaseLogger
, withLogger
, loggCtx
, withLogFunction
, LoggerT
, runLoggerT
, runLogT
) where
import Configuration.Utils hiding (Lens', Error)
import Control.Concurrent.Async
import Control.Monad.STM
import Control.Concurrent.STM.TBMQueue
import Control.Concurrent.STM.TVar
import Control.DeepSeq
import Control.Exception.Lifted
import Control.Exception.Enclosed
import Control.Lens hiding ((.=))
import Control.Monad.Except
import Control.Monad.Trans.Control
import Control.Monad.Unicode
import Data.Monoid.Unicode
import Data.Typeable
import GHC.Generics
import Prelude.Unicode
import System.Logger.Internal
import System.Logger.Types
data LoggerConfig = LoggerConfig
{ _loggerConfigQueueSize ∷ !Int
, _loggerConfigThreshold ∷ !LogLevel
, _loggerConfigScope ∷ !LogScope
, _loggerConfigPolicy ∷ !LogPolicy
}
deriving (Show, Read, Eq, Ord, Typeable, Generic)
loggerConfigQueueSize ∷ Lens' LoggerConfig Int
loggerConfigQueueSize = lens _loggerConfigQueueSize $ \a b → a { _loggerConfigQueueSize = b }
loggerConfigThreshold ∷ Lens' LoggerConfig LogLevel
loggerConfigThreshold = lens _loggerConfigThreshold $ \a b → a { _loggerConfigThreshold = b }
loggerConfigScope ∷ Lens' LoggerConfig LogScope
loggerConfigScope = lens _loggerConfigScope $ \a b → a { _loggerConfigScope = b }
loggerConfigPolicy ∷ Lens' LoggerConfig LogPolicy
loggerConfigPolicy = lens _loggerConfigPolicy $ \a b → a { _loggerConfigPolicy = b }
instance NFData LoggerConfig
defaultLoggerConfig ∷ LoggerConfig
defaultLoggerConfig = LoggerConfig
{ _loggerConfigQueueSize = 1000
, _loggerConfigThreshold = Warn
, _loggerConfigScope = []
, _loggerConfigPolicy = LogPolicyDiscard
}
validateLoggerConfig ∷ ConfigValidation LoggerConfig λ
validateLoggerConfig _ = return ()
instance ToJSON LoggerConfig where
toJSON LoggerConfig{..} = object
[ "queue_size" .= _loggerConfigQueueSize
, "log_level" .= _loggerConfigThreshold
, "scope" .= _loggerConfigScope
, "policy" .= _loggerConfigPolicy
]
instance FromJSON (LoggerConfig → LoggerConfig) where
parseJSON = withObject "LoggerConfig" $ \o → id
<$< loggerConfigQueueSize ..: "queue_size" × o
<*< loggerConfigThreshold ..: "log_level" × o
<*< loggerConfigScope ..: "scope" × o
<*< loggerConfigPolicy ..: "policy" × o
pLoggerConfig ∷ MParser LoggerConfig
pLoggerConfig = id
<$< loggerConfigQueueSize .:: option auto
× long "queue-size"
⊕ metavar "INT"
⊕ help "size of the internal logger queue"
<*< loggerConfigThreshold .:: pLogLevel
<*< loggerConfigPolicy .:: pLogPolicy
type LoggerQueue a = TBMQueue (LogMessage a)
data Logger a = Logger
{ _loggerQueue ∷ !(LoggerQueue a)
, _loggerWorker ∷ !(Async ())
, _loggerThreshold ∷ !LogLevel
, _loggerScope ∷ !LogScope
, _loggerPolicy ∷ !LogPolicy
, _loggerMissed ∷ !(TVar Int)
}
deriving (Typeable, Generic)
loggerQueue ∷ Lens' (Logger a) (LoggerQueue a)
loggerQueue = lens _loggerQueue $ \a b → a { _loggerQueue = b }
loggerWorker ∷ Lens' (Logger a) (Async ())
loggerWorker = lens _loggerWorker $ \a b → a { _loggerWorker = b }
loggerThreshold ∷ Lens' (Logger a) LogLevel
loggerThreshold = lens _loggerThreshold $ \a b → a { _loggerThreshold = b }
loggerScope ∷ Lens' (Logger a) LogScope
loggerScope = lens _loggerScope $ \a b → a { _loggerScope = b }
loggerPolicy ∷ Lens' (Logger a) LogPolicy
loggerPolicy = lens _loggerPolicy $ \a b → a { _loggerPolicy = b }
loggerMissed ∷ Lens' (Logger a) (TVar Int)
loggerMissed = lens _loggerMissed $ \a b → a { _loggerMissed = b }
createLogger
∷ MonadIO μ
⇒ LoggerConfig
→ LoggerBackend a
→ μ (Logger a)
createLogger LoggerConfig{..} backend = liftIO $ do
queue ← newTBMQueueIO _loggerConfigQueueSize
missed ← newTVarIO 0
worker ← backendWorker backend queue missed
return $ Logger
{ _loggerQueue = queue
, _loggerWorker = worker
, _loggerThreshold = _loggerConfigThreshold
, _loggerScope = _loggerConfigScope
, _loggerPolicy = _loggerConfigPolicy
, _loggerMissed = missed
}
backendWorker
∷ LoggerBackend a
→ LoggerQueue a
→ TVar Int
→ IO (Async ())
backendWorker backend queue missed = async $ go `catchAny` \e → do
(backend ∘ Left $ backendErrorMsg (sshow e)) `catchAny` (const $ return ())
go
where
go = atomically readMsg ≫= \case
Nothing → return ()
Just msg → backend msg ≫ go
readMsg = do
n ← swapTVar missed 0
if n > 0
then do
return ∘ Just ∘ Left $ discardMsg n
else
fmap Right <$> readTBMQueue queue
discardMsg n = LogMessage
{ _logMsg = "discarded " ⊕ sshow n ⊕ " log messages"
, _logMsgLevel = Warn
, _logMsgScope = [("system", "logger")]
}
backendErrorMsg e = LogMessage
{ _logMsg = e
, _logMsgLevel = Error
, _logMsgScope = [("system", "logger"), ("component", "backend")]
}
releaseLogger
∷ MonadIO μ
⇒ Logger a
→ μ ()
releaseLogger Logger{..} = liftIO $ do
atomically $ closeTBMQueue _loggerQueue
wait _loggerWorker
withLogger
∷ (MonadIO μ, MonadBaseControl IO μ)
⇒ LoggerConfig
→ LoggerBackend a
→ (Logger a → μ α)
→ μ α
withLogger config backend =
bracket (createLogger config backend) releaseLogger
withLogFunction
∷ (Show a, Typeable a, NFData a, MonadIO μ, MonadBaseControl IO μ)
⇒ LoggerConfig
→ LoggerBackend a
→ (LogFunctionIO a → μ α)
→ μ α
withLogFunction config backend f = withLogger config backend $ f ∘ loggCtx
data LoggerException a
= QueueFullException (LogMessage a)
deriving (Show, Eq, Ord, Typeable, Generic)
instance (Typeable a, Show a) ⇒ Exception (LoggerException a)
loggCtx
∷ (Show a, Typeable a, NFData a)
⇒ Logger a
→ LogFunctionIO a
loggCtx Logger{..} level msg = do
case _loggerThreshold of
Quiet → return ()
threshold
| level ≤ threshold → liftIO ∘ atomically $
writeWithLogPolicy $!! LogMessage
{ _logMsg = msg
, _logMsgLevel = level
, _logMsgScope = _loggerScope
}
| otherwise → return ()
where
writeWithLogPolicy lmsg
| _loggerPolicy ≡ LogPolicyBlock = writeTBMQueue _loggerQueue lmsg
| otherwise = tryWriteTBMQueue _loggerQueue lmsg ≫= \case
Just False
| _loggerPolicy ≡ LogPolicyDiscard → modifyTVar' _loggerMissed succ
| _loggerPolicy ≡ LogPolicyRaise → throwSTM $ QueueFullException lmsg
_ → return ()
instance LoggerCtx (Logger a) a where
loggerFunIO = loggCtx
setLoggerLevel = loggerThreshold
setLoggerScope = loggerScope
setLoggerPolicy = loggerPolicy
type LoggerT a = LoggerCtxT (Logger a)
runLoggerT ∷ LoggerT a m α → Logger a → m α
runLoggerT = runLoggerCtxT
runLogT
∷ (MonadBaseControl IO m, MonadIO m)
⇒ LoggerConfig
→ LoggerBackend msg
→ LoggerT msg m α
→ m α
runLogT config backend = withLogger config backend ∘ runLoggerT