module LittleLogger ( textLogStr , LogAction (..) , defaultLogAction , filterActionSeverity , newLogAction , runLogAction , handleLogAction , openLoggingHandle , closeLoggingHandle , fileLogAction , HasLogAction (..) , WithLogAction , askLogAction , LogActionWrapperM (..) , LogActionT (..) , runLogActionT , LogActionM , runLogActionM -- Re-exports , MonadLogger (..) , Loc (..) , LogSource , LogLevel (..) , LogStr , ToLogStr (..) , logDebugN , logInfoN , logWarnN , logErrorN , logOtherN ) where import Control.Monad.IO.Class (MonadIO (..)) import Control.Monad.IO.Unlift (MonadUnliftIO, askRunInIO) import Control.Monad.Logger.CallStack (Loc (..), LogLevel (..), LogSource, LogStr, MonadLogger (..), ToLogStr (..), defaultOutput, fromLogStr, logDebugN, logErrorN, logInfoN, logOtherN, logWarnN) import Control.Monad.Reader (MonadReader, ReaderT (..), asks) import Data.Text (Text) import Data.Text.Encoding (decodeUtf8) import Lens.Micro (Lens') import Lens.Micro.Extras (view) import System.IO (BufferMode (LineBuffering), Handle, IOMode (AppendMode), hClose, hSetBuffering, openFile, stderr, withFile) textLogStr :: LogStr -> Text textLogStr = decodeUtf8 . fromLogStr newtype LogAction = LogAction { unLogAction :: Loc -> LogSource -> LogLevel -> LogStr -> IO () } instance Semigroup LogAction where LogAction act1 <> LogAction act2 = LogAction (\loc src lvl msg -> act1 loc src lvl msg >> act2 loc src lvl msg) instance Monoid LogAction where mempty = LogAction (\_ _ _ _ -> pure ()) mappend = (<>) newLogAction :: MonadUnliftIO m => (Loc -> LogSource -> LogLevel -> LogStr -> m ()) -> m LogAction newLogAction act = fmap (\run -> LogAction (\loc src lvl msg -> run (act loc src lvl msg))) askRunInIO runLogAction :: MonadIO m => LogAction -> Loc -> LogSource -> LogLevel -> LogStr -> m () runLogAction (LogAction act) loc src lvl msg = liftIO (act loc src lvl msg) defaultLogAction :: LogAction defaultLogAction = LogAction (defaultOutput stderr) filterActionSeverity :: LogLevel -> LogAction -> LogAction filterActionSeverity lim (LogAction act) = LogAction (\loc src lvl msg -> if lvl >= lim then act loc src lvl msg else pure ()) handleLogAction :: Handle -> LogAction handleLogAction = LogAction . defaultOutput openLoggingHandle :: MonadIO m => FilePath -> m Handle openLoggingHandle fp = do handle <- liftIO (openFile fp AppendMode) liftIO (hSetBuffering handle LineBuffering) pure handle closeLoggingHandle :: MonadIO m => Handle -> m () closeLoggingHandle = liftIO . hClose fileLogAction :: MonadUnliftIO m => FilePath -> (LogAction -> m a) -> m a fileLogAction fp f = do run <- askRunInIO liftIO $ withFile fp AppendMode $ \handle -> do hSetBuffering handle LineBuffering run (f (handleLogAction handle)) class HasLogAction env where logActionL :: Lens' env LogAction instance HasLogAction LogAction where logActionL = id type WithLogAction env m = (MonadIO m, MonadReader env m, HasLogAction env) askLogAction :: (MonadReader env m, HasLogAction env) => m LogAction askLogAction = asks (view logActionL) -- | Use deriving-via with this wrapper to add MonadLogger instances to your types newtype LogActionWrapperM env m a = LogActionM { unLogActionM :: m a } deriving newtype (Functor, Applicative, Monad, MonadIO, MonadReader env) instance WithLogAction env m => MonadLogger (LogActionWrapperM env m) where monadLoggerLog loc src lvl msg = do LogAction act <- askLogAction liftIO (act loc src lvl (toLogStr msg)) newtype LogActionT m a = LogActionT { unLogActionT :: ReaderT LogAction m a } deriving newtype (Functor, Applicative, Monad, MonadIO, MonadReader LogAction) deriving MonadLogger via (LogActionWrapperM LogAction (LogActionT m)) runLogActionT :: LogActionT m a -> LogAction -> m a runLogActionT = runReaderT . unLogActionT type LogActionM a = LogActionT IO a runLogActionM :: LogActionM a -> LogAction -> IO a runLogActionM = runLogActionT