-- Copyright (c) 2014-2015 PivotCloud, Inc. -- -- System.Logger.Types -- -- Please feel free to contact us at licensing@pivotmail.com with any -- contributions, additions, or other feedback; we would love to hear from -- you. -- -- Licensed under the Apache License, Version 2.0 (the "License"); you may -- not use this file except in compliance with the License. You may obtain a -- copy of the License at http://www.apache.org/licenses/LICENSE-2.0 -- -- Unless required by applicable law or agreed to in writing, software -- distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -- WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -- License for the specific language governing permissions and limitations -- under the License. -- | -- Module: System.Logger.Types -- Description: Basic Types of Yet Another Logger -- Copyright: Copyright (c) 2014-2015 PivotCloud, Inc. -- License: Apache License, Version 2.0 -- Maintainer: Lars Kuhtz -- Stability: experimental -- {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE UnicodeSyntax #-} module System.Logger.Types ( -- * LogLevel LogLevel(..) , logLevelText , readLogLevel , pLogLevel -- * LogPolicy , LogPolicy(..) , logPolicyText , readLogPolicy , pLogPolicy -- * LogLabel , LogLabel , LogScope -- * Logger Backend , LogMessage(..) , logMsg , logMsgLevel , logMsgScope , LoggerBackend -- * Logger Frontend , LogFunction , LogFunctionIO -- * LoggerCtx , LoggerCtx(..) , LoggerCtxT , runLoggerCtxT -- * MonadLog , MonadLog(..) , withLabel , clearScope , popLabel , MonadLogIO(..) ) where import Configuration.Utils hiding (Lens', Error) import Control.DeepSeq import Control.Lens hiding ((.=)) import Control.Monad.Base import Control.Monad.Except import Control.Monad.Reader import Control.Monad.Trans.Control import Control.Monad.Trans.Either import Control.Monad.State import Control.Monad.Trace import Control.Monad.Trans.Trace import Control.Monad.Writer import Control.Monad.Unicode import qualified Data.CaseInsensitive as CI import Data.Monoid.Unicode import Data.String import qualified Data.Text as T import Data.Text.Lens import Data.Typeable import GHC.Generics import qualified Options.Applicative as O import Prelude.Unicode -- -------------------------------------------------------------------------- -- -- Log-Level data LogLevel = Quiet | Error | Warn | Info | Debug deriving (Show, Read, Eq, Ord, Enum, Bounded, Typeable, Generic) instance NFData LogLevel readLogLevel ∷ (MonadError e m, Eq a, Show a, CI.FoldCase a, IsString a, IsString e, Monoid e) ⇒ a → m LogLevel readLogLevel x = case CI.mk x of "quiet" → return Quiet "error" → return Error "warn" → return Warn "info" → return Info "debug" → return Debug e → throwError $ "unexpected log level value: " ⊕ fromString (show e) ⊕ ", expected \"quiet\", \"error\", \"warn\", \"info\", or \"debug\"" logLevelText ∷ IsString a ⇒ LogLevel → a logLevelText Quiet = "quiet" logLevelText Error = "error" logLevelText Warn = "warn" logLevelText Info = "info" logLevelText Debug = "debug" instance ToJSON LogLevel where toJSON = String ∘ logLevelText instance FromJSON LogLevel where parseJSON = withText "LogLevel" $ either fail return ∘ readLogLevel pLogLevel ∷ O.Parser LogLevel pLogLevel = option (eitherReader readLogLevel) × long "loglevel" ⊕ metavar "quiet|error|warn|info|debug" ⊕ help "threshold for log messages" -- -------------------------------------------------------------------------- -- -- Log Policy -- | Policy that determines how the case of a congested logging -- pipeline is addressed. -- data LogPolicy = LogPolicyDiscard | LogPolicyRaise | LogPolicyBlock deriving (Show, Read, Eq, Ord, Bounded, Enum, Typeable, Generic) instance NFData LogPolicy logPolicyText ∷ IsString s ⇒ LogPolicy → s logPolicyText LogPolicyDiscard = "discard" logPolicyText LogPolicyRaise = "raise" logPolicyText LogPolicyBlock = "block" readLogPolicy ∷ (MonadError e m, Eq a, Show a, CI.FoldCase a, IsText a, IsString e, Monoid e) ⇒ a → m LogPolicy readLogPolicy x = case CI.mk tx of "discard" → return LogPolicyDiscard "raise" → return LogPolicyRaise "block" → return LogPolicyBlock e → throwError $ "invalid log policy value " ⊕ fromString (show e) ⊕ ";" ⊕ " the log policy value must be one of \"discard\", \"raise\", or \"block\"" where tx = packed # x instance ToJSON LogPolicy where toJSON = toJSON ∘ (logPolicyText ∷ LogPolicy → T.Text) instance FromJSON LogPolicy where parseJSON = withText "LogPolicy" $ either fail return ∘ readLogPolicy pLogPolicy ∷ O.Parser LogPolicy pLogPolicy = option (eitherReader readLogPolicy) × long "log-policy" ⊕ metavar "block|raise|discard" ⊕ help "how to deal with a congested logging pipeline" -- -------------------------------------------------------------------------- -- -- Log-Label type LogLabel = (T.Text, T.Text) type LogScope = [LogLabel] -- -------------------------------------------------------------------------- -- -- Backend -- | The Internal log message type. -- -- The type parameter @a@ is expected to provide intances -- of 'Show', 'Typeable', and 'NFData'. -- -- If we need to support different backends, we may consider -- including the backend here... -- data LogMessage a = LogMessage { _logMsg ∷ !a , _logMsgLevel ∷ !LogLevel , _logMsgScope ∷ !LogScope -- ^ efficiency of this depends on whether this is shared -- between log messsages. Usually this should be just a pointer to -- a shared list. } deriving (Show, Read, Eq, Ord, Typeable, Generic) logMsg ∷ Lens' (LogMessage a) a logMsg = lens _logMsg $ \a b → a { _logMsg = b } logMsgLevel ∷ Lens' (LogMessage a) LogLevel logMsgLevel = lens _logMsgLevel $ \a b → a { _logMsgLevel = b } logMsgScope ∷ Lens' (LogMessage a) LogScope logMsgScope = lens _logMsgScope $ \a b → a { _logMsgScope = b } instance NFData a ⇒ NFData (LogMessage a) -- | This is given to logger when it is created. It formats and delivers -- individual log messages synchronously. -- -- The type parameter @a@ is expected to provide instances for 'Show' -- 'Typeable', and 'NFData'. -- -- The 'Left' values of the argument allows the generation of log messages -- that are independent of the parameter @a@. The motivation for this is -- reporting issues in Logging system itself, like a full logger queue -- or providing statistics about the fill level of the queue. There may -- be other uses of this, too. -- -- TODO there may be scenarios where chunked processing is beneficial. -- While this can be done in a closure of this function a more direct -- support might be desirable. -- type LoggerBackend a = Either (LogMessage T.Text) (LogMessage a) → IO () -- -------------------------------------------------------------------------- -- -- Frontend -- | This function is provided by the logger. -- type LogFunctionIO a = LogLevel → a → IO () type LogFunction a m = LogLevel → a → m () -- -------------------------------------------------------------------------- -- -- MonadLog class Monad m ⇒ MonadLog a m | m → a where logg ∷ LogFunction a m withLevel ∷ LogLevel → m α → m α withPolicy ∷ LogPolicy → m α → m α localScope ∷ (LogScope → LogScope) → m α → m α withLabel ∷ MonadLog a m ⇒ LogLabel → m α → m α withLabel = localScope ∘ (:) popLabel ∷ MonadLog a m ⇒ m α → m α popLabel = localScope $ \case { [] → []; (_:t) → t } clearScope ∷ MonadLog a m ⇒ m α → m α clearScope = localScope $ const [] -- | Instances of 'MonadLog' that allow to obtain a 'LogFunctionIO' as plain -- value. This is helpful when dealing with frameworks that take a logging -- function in 'IO' as parameter. -- -- An instance of this class should apply the 'LogLevel', 'LogScope', and -- 'LogPolicy' at the time when 'logFunIO' is called and not when the returned -- action is excecuted. If the returned action is excecuted after the logger -- got released or otherwise invalidated the behavior should match the behavior -- on a congested logging pipeling accorrding to the log-policy that was in -- scope when 'logFunIO' was called. -- -- Even though it can be very convenient, instances of this class must be used -- with care. The action may contain in its closure a reference to some -- internal state of the 'MonadLog' instance. Beside of being a source of -- potential memory leaks, there also is nothing that prevents a programer to -- call this action outside of the valid scope of the 'MonadLog' instance. In -- case that the context of the 'MonadLog' instance depends on some state that -- gets explicitely deallocated this action may have unexectped behavior. -- class MonadLog a m ⇒ MonadLogIO a m where logFunIO ∷ m (LogFunctionIO a) -- -------------------------------------------------------------------------- -- -- Logger Context -- | Abstraction of a logger context that can be used without dependening on -- a specific monadic context. -- -- The 'loggerFunIO' incorporates a 'LoggerBackend'. An instance of a 'LoggerCtx' -- is free to use a hard coded 'LoggerBackend' or to be usable with different -- 'LoggerBackend' functions. The latter is recommended but not required. -- -- You don't have to provide an instance of this for your logger. Instead you -- may just provide an instance of 'MonadLog' directly. -- -- If this doesn't fit your needs you may use a newtype wrapper and define -- your own instances. -- class LoggerCtx ctx msg | ctx → msg where loggerFunIO ∷ (Show msg, Typeable msg, NFData msg) ⇒ ctx → LogFunctionIO msg setLoggerLevel ∷ Setter' ctx LogLevel setLoggerScope ∷ Setter' ctx LogScope setLoggerPolicy ∷ Setter' ctx LogPolicy withLoggerLevel ∷ LogLevel → ctx → (ctx → α) → α withLoggerLevel level ctx f = f $ ctx & setLoggerLevel .~ level {-# INLINE withLoggerLevel #-} withLoggerLabel ∷ LogLabel → ctx → (ctx → α) → α withLoggerLabel label ctx f = f $ ctx & setLoggerScope %~ (:) label {-# INLINE withLoggerLabel #-} withLoggerPolicy ∷ LogPolicy → ctx → (ctx → α) → α withLoggerPolicy policy ctx f = f $ ctx & setLoggerPolicy .~ policy {-# INLINE withLoggerPolicy #-} newtype LoggerCtxT ctx m α = LoggerCtxT { unLoggerCtxT ∷ ReaderT ctx m α } deriving (Functor, Applicative, Monad, MonadIO, MonadTrans, MonadReader ctx, MonadError a, MonadState a, MonadWriter a, MonadBase a, MonadTrace t) -- This should eventually be defined in Control.Monad.Trace.Class instance (Monad m, MonadTrace t m) ⇒ MonadTrace t (ReaderT ctx m) where traceScope s inner = liftWith (\run → traceScope s (run inner)) ≫= restoreT ∘ return readTrace = lift readTrace instance MonadTransControl (LoggerCtxT ctx) where type StT (LoggerCtxT ctx) a = StT (ReaderT ctx) a liftWith = defaultLiftWith LoggerCtxT unLoggerCtxT restoreT = defaultRestoreT LoggerCtxT instance MonadBaseControl b m ⇒ MonadBaseControl b (LoggerCtxT ctx m) where type StM (LoggerCtxT ctx m) a = ComposeSt (LoggerCtxT ctx) m a liftBaseWith = defaultLiftBaseWith restoreM = defaultRestoreM runLoggerCtxT ∷ LoggerCtxT ctx m α → ctx → m α runLoggerCtxT = runReaderT ∘ unLoggerCtxT instance (Show a, Typeable a, NFData a, MonadIO m, LoggerCtx ctx a) ⇒ MonadLog a (LoggerCtxT ctx m) where logg l m = ask ≫= \ctx → liftIO (loggerFunIO ctx l m) withLevel level = local $ setLoggerLevel .~ level withPolicy policy = local $ setLoggerPolicy .~ policy localScope f = local $ setLoggerScope %~ f {-# INLINE logg #-} {-# INLINE withLevel #-} {-# INLINE withPolicy #-} {-# INLINE localScope #-} instance (Show a, Typeable a, NFData a, MonadIO m, LoggerCtx ctx a) ⇒ MonadLogIO a (LoggerCtxT ctx m) where logFunIO = ask ≫= return ∘ loggerFunIO -- -------------------------------------------------------------------------- -- -- Boilerplate Instances {- -- Not sure if this instance is a good idea instance (Show a, Typeable a, NFData a, MonadIO m, LoggerCtx ctx a, MonadReader ctx m) ⇒ MonadLog a m where logg l m = ask ≫= \ctx → liftIO (loggerFunIO ctx l m) withLevel level = local $ setLoggerLevel .~ level withPolicy policy = local $ setLoggerPolicy .~ policy localScope = local ∘ over setLoggerScope {-# INLINE logg #-} {-# INLINE withLevel #-} {-# INLINE withPolicy #-} {-# INLINE localScope #-} -- Not sure if this instance is a good idea instance MonadLog a m ⇒ MonadLog a (ReaderT σ m) where logg l = lift ∘ logg l withLevel level inner = liftWith (\run → withLevel level (run inner)) ≫= restoreT ∘ return withPolicy policy inner = liftWith (\run → withPolicy policy (run inner)) ≫= restoreT ∘ return localScope f inner = liftWith (\run → localScope f (run inner)) ≫= restoreT ∘ return {-# INLINE logg #-} {-# INLINE withLevel #-} {-# INLINE withPolicy #-} {-# INLINE localScope #-} -} instance (Monoid σ, MonadLog a m) ⇒ MonadLog a (WriterT σ m) where logg l = lift ∘ logg l withLevel level inner = liftWith (\run → withLevel level (run inner)) ≫= restoreT ∘ return withPolicy policy inner = liftWith (\run → withPolicy policy (run inner)) ≫= restoreT ∘ return localScope f inner = liftWith (\run → localScope f (run inner)) ≫= restoreT ∘ return {-# INLINE logg #-} {-# INLINE withLevel #-} {-# INLINE withPolicy #-} {-# INLINE localScope #-} instance (MonadLog a m) ⇒ MonadLog a (ExceptT ε m) where logg l = lift ∘ logg l withLevel level inner = liftWith (\run → withLevel level (run inner)) ≫= restoreT ∘ return withPolicy policy inner = liftWith (\run → withPolicy policy (run inner)) ≫= restoreT ∘ return localScope f inner = liftWith (\run → localScope f (run inner)) ≫= restoreT ∘ return {-# INLINE logg #-} {-# INLINE withLevel #-} {-# INLINE withPolicy #-} {-# INLINE localScope #-} instance (MonadLog a m) ⇒ MonadLog a (StateT σ m) where logg l = lift ∘ logg l withLevel level inner = liftWith (\run → withLevel level (run inner)) ≫= restoreT ∘ return withPolicy policy inner = liftWith (\run → withPolicy policy (run inner)) ≫= restoreT ∘ return localScope f inner = liftWith (\run → localScope f (run inner)) ≫= restoreT ∘ return {-# INLINE logg #-} {-# INLINE withLevel #-} {-# INLINE withPolicy #-} {-# INLINE localScope #-} instance (MonadLog a m) ⇒ MonadLog a (TraceT t e m) where logg l = lift ∘ logg l withLevel level inner = liftWith (\run → withLevel level (run inner)) ≫= restoreT ∘ return withPolicy policy inner = liftWith (\run → withPolicy policy (run inner)) ≫= restoreT ∘ return localScope f inner = liftWith (\run → localScope f (run inner)) ≫= restoreT ∘ return {-# INLINE logg #-} {-# INLINE withLevel #-} {-# INLINE withPolicy #-} {-# INLINE localScope #-} instance (MonadLog a m) ⇒ MonadLog a (EitherT σ m) where logg l = lift ∘ logg l withLevel level inner = liftWith (\run → withLevel level (run inner)) ≫= restoreT ∘ return withPolicy policy inner = liftWith (\run → withPolicy policy (run inner)) ≫= restoreT ∘ return localScope f inner = liftWith (\run → localScope f (run inner)) ≫= restoreT ∘ return {-# INLINE logg #-} {-# INLINE withLevel #-} {-# INLINE withPolicy #-} {-# INLINE localScope #-} {- -- Uses @OverlappingInstances@ to lift MonadLog in all transformers with an -- instance for 'MonadTransControl'. -- -- It would be really cool if this would work -- instance (MonadLog a m, MonadTransControl t, Monad n, n ~ (t m)) ⇒ MonadLog a n where logg l = lift ∘ logg l withLevel level inner = liftWith (\run → withLevel level (run inner)) ≫= restoreT ∘ return withLabel label inner = liftWith (\run → withLabel label (run inner)) ≫= restoreT ∘ return withPolicy policy inner = liftWith (\run → withPolicy policy (run inner)) ≫= restoreT ∘ return {-# INLINE logg #-} {-# INLINE withLevel #-} {-# INLINE withLabel #-} {-# INLINE withPolicy #-} -} -- MonadLogIO instance (MonadLog a (ReaderT σ m), MonadLogIO a m) ⇒ MonadLogIO a (ReaderT σ m) where logFunIO = lift logFunIO {-# INLINE logFunIO #-} instance (Monoid σ, MonadLogIO a m) ⇒ MonadLogIO a (WriterT σ m) where logFunIO = lift logFunIO {-# INLINE logFunIO #-} instance (MonadLogIO a m) ⇒ MonadLogIO a (ExceptT ε m) where logFunIO = lift logFunIO {-# INLINE logFunIO #-} instance (MonadLogIO a m) ⇒ MonadLogIO a (StateT σ m) where logFunIO = lift logFunIO {-# INLINE logFunIO #-} instance (MonadLogIO a m) ⇒ MonadLogIO a (TraceT t e m) where logFunIO = lift logFunIO {-# INLINE logFunIO #-} instance (MonadLogIO a m) ⇒ MonadLogIO a (EitherT σ m) where logFunIO = lift logFunIO {-# INLINE logFunIO #-}