{-|
Module: IHP.Log
Description:  Functions to write logs at all log levels.

Import this module qualified! All code examples
assume you have imported the module as follows:

> import qualified IHP.Log as Log

-}
module IHP.Log
( module IHP.Log.Types
, debug
, info
, warn
, error
, fatal
, unknown
, makeRequestLogger
, defaultRequestLogger
) where

import IHP.HaskellSupport hiding (debug)

import CorePrelude hiding (putStr, putStrLn, print, error, show, log, debug)
import Control.Monad (when)
import IHP.Log.Types
import Network.Wai (Middleware)
import Network.Wai.Middleware.RequestLogger (mkRequestLogger, RequestLoggerSettings, destination)
import qualified Network.Wai.Middleware.RequestLogger as RequestLogger
import Data.Default (Default (def))
import qualified System.Log.FastLogger as FastLogger

-- | Format a log and send it to the logger.
-- Internal use only -- application code should call the
-- function corresponding to the desired log level.
log :: (?context :: context, LoggingProvider context, FastLogger.ToLogStr string) => LogLevel -> string -> IO ()
log :: forall context string.
(?context::context, LoggingProvider context, ToLogStr string) =>
LogLevel -> string -> IO ()
log LogLevel
level string
text = do
    LogLevel -> Logger -> string -> IO ()
forall string.
ToLogStr string =>
LogLevel -> Logger -> string -> IO ()
writeLog LogLevel
level context
?context::context
?context.logger string
text

-- | Log a debug level message.
--
-- > action CreateUserAction { .. } = do
-- >     Log.debug "entered CreateUserAction"
-- >     ...
debug :: (?context :: context, LoggingProvider context, FastLogger.ToLogStr string) => string -> IO ()
debug :: forall context string.
(?context::context, LoggingProvider context, ToLogStr string) =>
string -> IO ()
debug = LogLevel -> string -> IO ()
forall context string.
(?context::context, LoggingProvider context, ToLogStr string) =>
LogLevel -> string -> IO ()
log LogLevel
Debug

-- | Log an info level message.
--
-- > action UsersAction = do
-- >     users <- query @User |> fetch
-- >     Log.info $ show (lengh users) <> " users fetched."
-- >     ...
info :: (?context :: context, LoggingProvider context, FastLogger.ToLogStr string) => string -> IO ()
info :: forall context string.
(?context::context, LoggingProvider context, ToLogStr string) =>
string -> IO ()
info = LogLevel -> string -> IO ()
forall context string.
(?context::context, LoggingProvider context, ToLogStr string) =>
LogLevel -> string -> IO ()
log LogLevel
Info

-- | Log a warning level message.
--
-- > action UsersAction = do
-- >     users <- query @User |> fetch
-- >     whenEmpty users $ Log.warn "No users found. Something might be wrong!"
-- >     ...
warn :: (?context :: context, LoggingProvider context, FastLogger.ToLogStr string) => string -> IO ()
warn :: forall context string.
(?context::context, LoggingProvider context, ToLogStr string) =>
string -> IO ()
warn = LogLevel -> string -> IO ()
forall context string.
(?context::context, LoggingProvider context, ToLogStr string) =>
LogLevel -> string -> IO ()
log LogLevel
Warn

-- |Log a warning level message.
--
-- @
--    action CreatePostAction = do
--        let post = newRecord @Post
--        post
--            |> buildPost
--            |> ifValid \case
--                Left post -> do
--                    Log.error "Invalid post."
--                    render NewView { .. }
--                Right post -> do
--                    post <- post |> createRecord
--                    setSuccessMessage "Post created"
--                    redirectTo PostsAction
-- @
error :: (?context :: context, LoggingProvider context, FastLogger.ToLogStr string) => string -> IO ()
error :: forall context string.
(?context::context, LoggingProvider context, ToLogStr string) =>
string -> IO ()
error = LogLevel -> string -> IO ()
forall context string.
(?context::context, LoggingProvider context, ToLogStr string) =>
LogLevel -> string -> IO ()
log LogLevel
Error

-- | Log a fatal level message.
-- Note this does not exit the program for you -- it only logs to the "Fatal" log level.
--
-- > Log.fatal "Unrecoverable application error!"
fatal :: (?context :: context, LoggingProvider context, FastLogger.ToLogStr string) => string -> IO ()
fatal :: forall context string.
(?context::context, LoggingProvider context, ToLogStr string) =>
string -> IO ()
fatal = LogLevel -> string -> IO ()
forall context string.
(?context::context, LoggingProvider context, ToLogStr string) =>
LogLevel -> string -> IO ()
log LogLevel
Fatal

-- | Log an "unknown" level message.
-- This is the highest log level and will always be output by the logger.
--
-- > Log.unknown "This will be sent to the logger no matter what!"
unknown :: (?context :: context, LoggingProvider context, FastLogger.ToLogStr string) => string -> IO ()
unknown :: forall context string.
(?context::context, LoggingProvider context, ToLogStr string) =>
string -> IO ()
unknown = LogLevel -> string -> IO ()
forall context string.
(?context::context, LoggingProvider context, ToLogStr string) =>
LogLevel -> string -> IO ()
log LogLevel
Unknown

-- | Write a log if the given log level is greater than or equal to the logger's log level.
writeLog :: (FastLogger.ToLogStr string) => LogLevel -> Logger -> string -> IO ()
writeLog :: forall string.
ToLogStr string =>
LogLevel -> Logger -> string -> IO ()
writeLog LogLevel
level Logger
logger string
text = do
    let write :: (FormattedTime -> LogStr) -> IO ()
write = Logger
logger.write
    let formatter :: FormattedTime -> LogLevel -> LogStr -> LogStr
formatter = Logger
logger.formatter
    Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (LogLevel
level LogLevel -> LogLevel -> Bool
forall a. Ord a => a -> a -> Bool
>= Logger
logger.level) do
        (FormattedTime -> LogStr) -> IO ()
write (\FormattedTime
time -> FormattedTime -> LogLevel -> LogStr -> LogStr
formatter FormattedTime
time LogLevel
level (string -> LogStr
forall msg. ToLogStr msg => msg -> LogStr
toLogStr string
text))

-- | Wraps 'RequestLogger' from wai-extra to log to an IHP logger.
-- See 'Network.Wai.Middleware.RequestLogger'.
makeRequestLogger :: RequestLoggerSettings -> Logger -> IO Middleware
makeRequestLogger :: RequestLoggerSettings -> Logger -> IO Middleware
makeRequestLogger RequestLoggerSettings
settings Logger
logger = 
    RequestLoggerSettings -> IO Middleware
mkRequestLogger RequestLoggerSettings
settings {
        destination = RequestLogger.Callback (\LogStr
logStr ->
            let ?context = ?context::Logger
Logger
logger in
                LogStr
logStr LogStr -> (LogStr -> FormattedTime) -> FormattedTime
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> LogStr -> FormattedTime
fromLogStr FormattedTime -> (FormattedTime -> IO ()) -> IO ()
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> FormattedTime -> IO ()
forall context string.
(?context::context, LoggingProvider context, ToLogStr string) =>
string -> IO ()
info
            )
        }

-- | Create a request logger with default settings wrapped in an IHP logger.
-- See 'Network.Wai.Middleware.RequestLogger'.
defaultRequestLogger :: Logger -> IO Middleware
defaultRequestLogger :: Logger -> IO Middleware
defaultRequestLogger = RequestLoggerSettings -> Logger -> IO Middleware
makeRequestLogger RequestLoggerSettings
forall a. Default a => a
def