{-# LANGUAGE OverloadedStrings, TypeSynonymInstances, FlexibleInstances, ExistentialQuantification, TypeFamilies, GeneralizedNewtypeDeriving, StandaloneDeriving, MultiParamTypeClasses, UndecidableInstances, AllowAmbiguousTypes, ScopedTypeVariables, FunctionalDependencies, FlexibleContexts, ConstraintKinds #-}
module System.Log.Heavy.Types
(
LogSource, LogMessage (..), LogFilter, LogContextFrame (..), LogContext,
IsLogBackend (..), LogBackendSettings (..), LoggingSettings (..),
AnyLogBackend (..), LogContextFilter (..),
include, exclude, noChange,
Logger,SpecializedLogger,
HasLogBackend (..), HasLogContext (..), HasLogging,
HasLogger (..),
logMessage',
applyBackend,
defaultLogFilter,
withLogVariable,
splitString, splitDots,
) where
import Control.Monad.Reader
import Control.Monad.Logger (MonadLogger (..))
import Control.Monad.Trans.Control
import Control.Exception.Lifted (bracket)
import Language.Haskell.TH
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Format.Heavy as F
import System.Log.FastLogger
import System.Log.Heavy.Level
type LogSource = [String]
data LogMessage = forall vars. F.ClosedVarContainer vars => LogMessage {
lmLevel :: Level
, lmSource :: LogSource
, lmLocation :: Loc
, lmFormatString :: TL.Text
, lmFormatVars :: vars
, lmContext :: LogContext
}
type LogFilter = [(LogSource, Level)]
defaultLogFilter :: LogFilter
defaultLogFilter = [([], info_level)]
data LogContextFrame = LogContextFrame {
lcfVariables :: [(TL.Text, F.Variable)]
, lcfFilter :: LogContextFilter
}
deriving (Show)
data LogContextFilter =
LogContextFilter {
setInclude :: Maybe LogFilter
, setExclude :: Maybe LogFilter
}
deriving (Eq, Show)
noChange :: LogContextFilter
noChange = LogContextFilter Nothing Nothing
include :: LogFilter -> LogContextFilter
include f = LogContextFilter (Just f) Nothing
exclude :: LogFilter -> LogContextFilter
exclude f = LogContextFilter Nothing (Just f)
type LogContext = [LogContextFrame]
class IsLogBackend b where
data LogBackendSettings b
makeLogger :: Logger b
initLogBackend :: LogBackendSettings b -> IO b
wouldWriteMessage :: b -> LogMessage -> IO Bool
wouldWriteMessage _ _ = return True
cleanupLogBackend :: b -> IO ()
withLoggingB :: (MonadBaseControl IO m, MonadIO m)
=> LogBackendSettings b
-> (b -> m a)
-> m a
withLoggingB settings actions = do
bracket (liftIO $ initLogBackend settings)
(liftIO . cleanupLogBackend)
(actions)
data AnyLogBackend = forall b. IsLogBackend b => AnyLogBackend b
instance IsLogBackend AnyLogBackend where
data LogBackendSettings AnyLogBackend =
AnyLogBackendSettings LoggingSettings
makeLogger (AnyLogBackend backend) = makeLogger backend
wouldWriteMessage (AnyLogBackend backend) msg =
wouldWriteMessage backend msg
initLogBackend (AnyLogBackendSettings (LoggingSettings settings)) =
AnyLogBackend `fmap` initLogBackend settings
cleanupLogBackend (AnyLogBackend backend) = cleanupLogBackend backend
class IsLogBackend b => HasLogBackend b m where
getLogBackend :: m b
data LoggingSettings = forall b. IsLogBackend b => LoggingSettings (LogBackendSettings b)
type Logger backend = backend -> LogMessage -> IO ()
type SpecializedLogger = LogMessage -> IO ()
class Monad m => HasLogger m where
getLogger :: m SpecializedLogger
localLogger :: SpecializedLogger -> m a -> m a
applyBackend :: (IsLogBackend b, HasLogger m) => b -> m a -> m a
applyBackend b actions = do
let logger = makeLogger b
localLogger logger actions
class Monad m => HasLogContext m where
withLogContext :: LogContextFrame -> m a -> m a
getLogContext :: m LogContext
type HasLogging m = (HasLogger m, HasLogContext m)
withLogVariable :: (HasLogContext m, F.Formatable v)
=> TL.Text
-> v
-> m a
-> m a
withLogVariable name value =
withLogContext (LogContextFrame [(name, F.Variable value)] noChange)
instance (Monad m, MonadIO m, HasLogging m) => MonadLogger m where
monadLoggerLog loc src level msg = do
logger <- getLogger
context <- getLogContext
liftIO $ logger $ LogMessage {
lmLevel = logLevelToLevel level,
lmSource = src',
lmLocation = loc,
lmFormatString = textFromLogStr msg,
lmFormatVars = (),
lmContext = context
}
where
src' = splitDots $ T.unpack src
textFromLogStr :: ToLogStr str => str -> TL.Text
textFromLogStr str = TL.fromStrict $ TE.decodeUtf8 $ fromLogStr $ toLogStr str
instance F.Formatable LogStr where
formatVar fmt str = F.formatVar fmt $ fromLogStr str
splitString :: Char -> String -> [String]
splitString _ "" = []
splitString c s = let (l, s') = break (== c) s
in l : case s' of
[] -> []
(_:s'') -> splitString c s''
splitDots :: String -> [String]
splitDots = splitString '.'
logMessage' :: forall m. (HasLogger m, MonadIO m) => LogMessage -> m ()
logMessage' msg = do
logger <- getLogger
liftIO $ logger msg