{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeOperators #-} module Control.Eff.Log ( Log , LogM , Logger , stdoutLogger , stderrLogger , LogMessage(..) , logE , logM , filterLog , filterLog' , runLogPure , runLog , runLogM ) where import Control.Applicative ((<$>), (<*), (<$)) import Control.Eff import Control.Eff.Extend import Control.Eff.Lift (Lifted, lift) import Control.Monad (when) import Control.Monad.Base (MonadBase(..)) import Control.Monad.Trans.Control (MonadBaseControl(..)) import Data.ByteString (ByteString) import qualified Data.ByteString.Char8 as Char8 import Data.Text (Text) import Data.Text.Encoding (encodeUtf8) import Data.Typeable (Typeable) import System.IO (stderr, stdout) -- | Simple log effect, useful in pure code data Log l v where Log :: l -> Log l () instance ( MonadBase m m , Lifted m r , MonadBaseControl m (Eff r) ) => MonadBaseControl m (Eff (Log l ': r)) where type StM (Eff (Log l ': r)) a = StM (Eff r) (a, [l]) liftBaseWith f = raise $ liftBaseWith $ \runInBase -> f (runInBase . runLogPure) restoreM x = do (a, ls :: [l]) <- raise (restoreM x) mapM_ logE ls return a logLine :: Log a v -> a logLine (Log l) = l -- | Monadic action that does the real logging type Logger m l = l -> m () -- | Logger that outputs messages to stdout stdoutLogger :: (LogMessage l, MonadBase IO m) => Logger m l stdoutLogger = liftBase . Char8.hPutStrLn stdout . toMsg -- | Logger that outputs messages to stderr stderrLogger :: (LogMessage l, MonadBase IO m) => Logger m l stderrLogger = liftBase . Char8.hPutStrLn stderr . toMsg -- | Log something. logE :: Member (Log l) r => l -> Eff r () logE = send . Log -- | Collect log messages in a list. runLogPure :: Eff (Log l ': r) a -> Eff r (a, [l]) runLogPure = handle_relay (\x -> return (x, [])) (\(Log l) k -> k () >>= \(x, ls) -> return (x, l:ls)) -- | Run the 'Logger' action in the base monad for every log line. runLog :: Lifted m r => Logger m l -> Eff (Log l ': r) a -> Eff r a runLog logger = handle_relay return (\(Log l) k -> lift (logger l) >> k ()) -- | Filter Log entries with a predicate. -- -- Note that, most of the time an explicit type signature for the predicate -- will be required. filterLog :: forall l r a. Member (Log l) r => (l -> Bool) -> Eff r a -> Eff r a filterLog f = interpose return h where h :: Log l v -> (v -> Eff r b) -> Eff r b h (Log l) k = when (f l) (logE l) >> k () -- | Filter Log entries with a predicate and a proxy. -- -- This is the same as 'filterLog' but with a proxy l for type inference. filterLog' :: Member (Log l) r => (l -> Bool) -> proxy l -> Eff r a -> Eff r a filterLog' predicate _ = filterLog predicate -- | A more advanced version of 'Log'. Adds an ability to log from multiple threads. data LogM m l v where AskLogger :: LogM m l (Logger m l) askLogger :: Member (LogM m l) r => Eff r (Logger m l) askLogger = send AskLogger -- | Log something using `LogM` effect logM :: (Member (LogM m l) r, Lifted m r) => l -> Eff r () logM l = do logger <- askLogger lift (logger l) -- | Run the 'Logger' action in the base monad for every log line. runLogM :: Lifted m r => Logger m l -> Eff (LogM m l ': r) a -> Eff r a runLogM logger = handle_relay return (\AskLogger -> ($ logger)) instance ( MonadBase m m , Lifted m r , MonadBaseControl m (Eff r) ) => MonadBaseControl m (Eff (LogM m l ': r)) where type StM (Eff (LogM m l ': r)) a = StM (Eff r) a liftBaseWith f = do l <- askLogger raise $ liftBaseWith $ \runInBase -> f (runInBase . runLogM l) restoreM = raise . restoreM -- | Handy typeclass to convert log messages for output class LogMessage l where toMsg :: l -> ByteString {-# MINIMAL toMsg #-} instance LogMessage ByteString where toMsg = id instance LogMessage [Char] where toMsg = Char8.pack instance LogMessage Text where toMsg = encodeUtf8