{-# LANGUAGE OverloadedStrings, TypeSynonymInstances, FlexibleInstances, ExistentialQuantification, TypeFamilies, GeneralizedNewtypeDeriving, StandaloneDeriving, MultiParamTypeClasses, UndecidableInstances, FlexibleContexts, Rank2Types, ScopedTypeVariables #-}
-- | This is the main module of @heavy-logger@ package. In most cases, you need
-- to import this module. You will also need other modules in specific cases.
-- All modules that are required always are re-exported by this module.
--
-- Example of usage is:
--
-- @
--  import System.Log.Heavy
--  import System.Log.Heavy.Shortcuts
--  import Data.Text.Format.Heavy
--  ...
--
--  withLoggingT settings $ do
--      liftIO $ putStr "Your name? "
--      liftIO $ hFlush stdout
--      name <- liftIO $ getLine
--      info "name was {}" (Single name)
--      liftIO $ putStrLn $ "Hello, " ++ name
-- @
--
-- Please refer to @examples/@ directory for compiling examples.
-- 
-- There are, in general, following ways to use this package:
-- 
-- * Use @LoggingT@ monad transformer. It can be the simplest, if you already have
--   monadic transformers stack of 1-2 transformers and you do not mind to add yet
--   another. With @LoggingT@, you do not need to write any adapter instances, since
--   @LoggingT@ is already an instance of all required classes. This implementation
--   automatically solves all threading-related problems, since in fact it does not
--   have any shared state.
--
-- * Use @System.Log.Heavy.IO@ module. If you do not have monadic transformers at all,
--   and your application works in pure IO, this may be the simplest way. However,
--   this is a bit fragile, because you have to be sure that you always call logging
--   functions only when logging state is initialized, i.e. within @withLoggingIO@
--   call. This implementation stores required state in thread-local storage.
--
-- * Implement required class instances for monadic stack that you already use in
--   your application. For example, if you already have something like
--   @ReaderT StateT ExceptT IO@, it will be probably better to add a couple of 
--   fields to StateT's state to track logging state, than change your stack to
--   @ReaderT StateT LoggingT ExceptT IO@. If you wish to store logging state in some
--   kind of shared storage (global IORef or whatever), then you should think about
--   thread-safety by yourself.
-- 
-- When you decided which monadic context you will use, you will call one of
-- @withLogging*@ functions to run the entire thing, and inside that you will construct
-- instances of @LogMessage@ type and call @logMessage@ or @logMessage'@ function on them
-- to actually log a message. You probably will want to use some shortcut functions to
-- construct @LogMessage@ instances and log them.  There are some provided
-- by this package:
--
-- * @System.Log.Heavy.Shortcuts@ module exports simple functions, that can be used
--   in simple cases, when you do not want to write or check message source.
--
-- * @System.Log.Heavy.TH@ module exports TH macros, which correctly fill message
--   source and location.
--
module System.Log.Heavy
  (
    -- * Reexports
    module System.Log.Heavy.Types,
    module System.Log.Heavy.Level,
    module System.Log.Heavy.LoggingT,
    module System.Log.Heavy.Backends,
    module System.Log.Heavy.Backends.Dynamic,
    -- * Logging functions
    logMessage,
    -- * Run actions with logging
    withLogging, withLoggingF, withLoggingT,
    -- * Check current settings
    isLevelEnabledByBackend, isLevelEnabled
  ) where

import Control.Monad.Trans
import Control.Monad.Trans.Control
import Control.Exception.Lifted (bracket)
import qualified Data.Text.Lazy as TL

import System.Log.Heavy.Types
import System.Log.Heavy.Level
import System.Log.Heavy.Util
import System.Log.Heavy.LoggingT
import System.Log.Heavy.Backends
import System.Log.Heavy.Backends.Dynamic

-- | Execute actions with logging backend.
-- This is mostly an utility function to be used to construct custom 
-- logging frameworks for custom monad transformer stacks.
withLoggingF :: (MonadBaseControl IO m, MonadIO m)
            => LoggingSettings                        -- ^ Settings of arbitrary logging backend.
            -> (forall b. IsLogBackend b => b -> m a) -- ^ Actions to execute with logging backend.
                                                      --   Note that this type declaration binds argument
                                                      --   to work with *any* implementation of backend.
            -> m a
withLoggingF :: LoggingSettings -> (forall b. IsLogBackend b => b -> m a) -> m a
withLoggingF (LoggingSettings LogBackendSettings b
settings) forall b. IsLogBackend b => b -> m a
actions = LogBackendSettings b -> (b -> m a) -> m a
forall b (m :: * -> *) a.
(IsLogBackend b, MonadBaseControl IO m, MonadIO m) =>
LogBackendSettings b -> (b -> m a) -> m a
withLoggingB LogBackendSettings b
settings b -> m a
forall b. IsLogBackend b => b -> m a
actions

-- | Execute actions with logging.
-- This function can be useful for monad stacks that store logging backend
-- in State-like structure.
withLogging :: (MonadBaseControl IO m, MonadIO m, HasLogger m)
            => LoggingSettings -- ^ Settings of arbitrary logging backend
            -> m a             -- ^ Actions to be executed
            -> m a
withLogging :: LoggingSettings -> m a -> m a
withLogging (LoggingSettings LogBackendSettings b
settings) m a
actions = 
    m b -> (b -> m ()) -> (b -> m a) -> m a
forall (m :: * -> *) a b c.
MonadBaseControl IO m =>
m a -> (a -> m b) -> (a -> m c) -> m c
bracket (IO b -> m b
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO b -> m b) -> IO b -> m b
forall a b. (a -> b) -> a -> b
$ LogBackendSettings b -> IO b
forall b. IsLogBackend b => LogBackendSettings b -> IO b
initLogBackend LogBackendSettings b
settings)
            (IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> (b -> IO ()) -> b -> m ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. b -> IO ()
forall b. IsLogBackend b => b -> IO ()
cleanupLogBackend)
            (\b
b -> b -> m a -> m a
forall b (m :: * -> *) a.
(IsLogBackend b, HasLogger m) =>
b -> m a -> m a
applyBackend b
b m a
actions)

-- | Execute actions with logging.
-- This function is most convinient if you use @LoggingT@ as
-- @HasLogging@ implementation.
withLoggingT :: (MonadBaseControl IO m, MonadIO m)
                  => LoggingSettings   -- ^ Settings of arbitrary logging backend
                  -> LoggingT m a      -- ^ Actions to be executed
                  -> m a
withLoggingT :: LoggingSettings -> LoggingT m a -> m a
withLoggingT (LoggingSettings LogBackendSettings b
settings) LoggingT m a
actions =
  LogBackendSettings b -> (b -> m a) -> m a
forall b (m :: * -> *) a.
(IsLogBackend b, MonadBaseControl IO m, MonadIO m) =>
LogBackendSettings b -> (b -> m a) -> m a
withLoggingB LogBackendSettings b
settings ((b -> m a) -> m a) -> (b -> m a) -> m a
forall a b. (a -> b) -> a -> b
$ \b
backend ->
      let logger :: LogMessage -> IO ()
logger = Logger b
forall b. IsLogBackend b => Logger b
makeLogger b
backend
      in  LoggingT m a -> LoggingTState -> m a
forall (m :: * -> *) a. LoggingT m a -> LoggingTState -> m a
runLoggingT LoggingT m a
actions (LoggingTState -> m a) -> LoggingTState -> m a
forall a b. (a -> b) -> a -> b
$ (LogMessage -> IO ())
-> AnyLogBackend -> LogContext -> LoggingTState
LoggingTState LogMessage -> IO ()
logger (b -> AnyLogBackend
forall b. IsLogBackend b => b -> AnyLogBackend
AnyLogBackend b
backend) []

-- | Check if logging of events of specified level from specified source
-- is enabled by backend.
--
-- This function assumes that if some events filtering is enabled by the
-- backend, it does not depend on message text, only on source and 
-- severity level.
isLevelEnabledByBackend :: forall m. (Monad m, MonadIO m, HasLogBackend AnyLogBackend m) => LogSource -> Level -> m Bool
isLevelEnabledByBackend :: LogSource -> Level -> m Bool
isLevelEnabledByBackend LogSource
src Level
level = do
  AnyLogBackend
backend <- m AnyLogBackend
forall b (m :: * -> *). HasLogBackend b m => m b
getLogBackend :: m AnyLogBackend
  let msg :: LogMessage
msg = Level -> LogSource -> Loc -> Text -> () -> LogContext -> LogMessage
forall vars.
ClosedVarContainer vars =>
Level
-> LogSource -> Loc -> Text -> vars -> LogContext -> LogMessage
LogMessage Level
level LogSource
src Loc
forall a. HasCallStack => a
undefined Text
TL.empty () []
  IO Bool -> m Bool
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO Bool -> m Bool) -> IO Bool -> m Bool
forall a b. (a -> b) -> a -> b
$ AnyLogBackend -> LogMessage -> IO Bool
forall b. IsLogBackend b => b -> LogMessage -> IO Bool
wouldWriteMessage AnyLogBackend
backend LogMessage
msg

-- | Check if logging of events of specified level from specified source
-- is enabled by both context and backend filter.
--
-- This function assumes that if some events filtering is enabled by the
-- backend, it does not depend on message text, only on source and 
-- severity level.
isLevelEnabled :: forall m. (Monad m, MonadIO m, HasLogBackend AnyLogBackend m, HasLogContext m) => LogSource -> Level -> m Bool
isLevelEnabled :: LogSource -> Level -> m Bool
isLevelEnabled LogSource
src Level
level = do
  let msg :: LogMessage
msg = Level -> LogSource -> Loc -> Text -> () -> LogContext -> LogMessage
forall vars.
ClosedVarContainer vars =>
Level
-> LogSource -> Loc -> Text -> vars -> LogContext -> LogMessage
LogMessage Level
level LogSource
src Loc
forall a. HasCallStack => a
undefined Text
TL.empty () []
  AnyLogBackend
backend <- m AnyLogBackend
forall b (m :: * -> *). HasLogBackend b m => m b
getLogBackend :: m AnyLogBackend
  Bool
isEnabledByBackend <- IO Bool -> m Bool
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO Bool -> m Bool) -> IO Bool -> m Bool
forall a b. (a -> b) -> a -> b
$ AnyLogBackend -> LogMessage -> IO Bool
forall b. IsLogBackend b => b -> LogMessage -> IO Bool
wouldWriteMessage AnyLogBackend
backend LogMessage
msg
  Bool
isEnabledByContext <- LogMessage -> m Bool
forall (m :: * -> *). HasLogContext m => LogMessage -> m Bool
checkContextFilterM LogMessage
msg
  Bool -> m Bool
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> m Bool) -> Bool -> m Bool
forall a b. (a -> b) -> a -> b
$ Bool
isEnabledByContext Bool -> Bool -> Bool
&& Bool
isEnabledByBackend