heavy-logger-0.3.0.0: src/System/Log/Heavy/IO.hs
{-# LANGUAGE OverloadedStrings, TypeSynonymInstances, FlexibleInstances, ExistentialQuantification, TypeFamilies, GeneralizedNewtypeDeriving, StandaloneDeriving, MultiParamTypeClasses, UndecidableInstances, AllowAmbiguousTypes, ScopedTypeVariables, FunctionalDependencies, FlexibleContexts, ConstraintKinds #-}
-- | This module contains implementation of @HasLogger@, @HasLogBackend@, @HasLogContext@ instances for IO monad.
-- This implementation uses thread-local storage, so each thread will have it's own logging state (context and logger).
--
-- This module is not automatically re-exported by System.Log.Heavy, because in many cases it is more convinient to maintain
-- logging state within monadic context, than in global variable.
--
-- Note: implementations of @HasLogger@, @HasLogBackend@, @HasLogContext@ for IO, provided by this module, work only inside
-- @withLoggingIO@ call. If you try to call logging functions outside, you will get runtime error.
--
module System.Log.Heavy.IO
( withLoggingIO
) where
import Control.Exception
import Data.IORef
import Data.TLS.GHC
import System.IO.Unsafe (unsafePerformIO)
import System.Log.Heavy.Types
-- | Logging state stored in TLS (thread-local storage)
data LoggingIOState = LoggingIOState {
liosLogger :: SpecializedLogger
, liosBackend :: AnyLogBackend
, liosContext :: LogContext
}
-- | This global variable stores logging state.
-- Nothing inside IORef means that logging state is not initialized yet
-- or is already deinitialized.
loggingTLS :: TLS (IORef (Maybe LoggingIOState))
loggingTLS = unsafePerformIO $ mkTLS $ do
newIORef Nothing
{-# NOINLINE loggingTLS #-}
-- | Execute IO actions with logging.
--
-- Note 1: logging methods calls in IO monad are only valid inside @withLoggingIO@.
-- If you try to call them outside of this function, you will receive runtime error.
--
-- Note 2: if you will for some reason call @withLoggingIO@ inside @withLoggingIO@ within one
-- thread, you will receive runtime error.
--
-- Note 3: You can call @withLoggingIO@ syntactically inside @withLoggingIO@, but within other
-- thread. I.e., the construct like following is valid:
--
-- @
-- withLoggingIO settings $ do
-- \$info "message" ()
-- ...
-- forkIO $ do
-- withLoggingIO settings $ do
-- \$info "message" ()
-- ...
-- @
--
withLoggingIO :: LoggingSettings -- ^ Settings of arbitrary logging backend
-> IO a -- ^ Actions to be executed with logging
-> IO a
withLoggingIO (LoggingSettings settings) actions =
bracket (init settings)
(cleanup)
(\tls -> withBackend tls actions)
where
init settings = do
ioref <- getTLS loggingTLS
mbState <- readIORef ioref
case mbState of
Just _ -> fail "Logging IO state is already initialized. withLoggingIO was called twice?"
Nothing -> do
backend <- initLogBackend settings
let logger = makeLogger backend
let st = LoggingIOState logger (AnyLogBackend backend) []
writeIORef ioref (Just st)
return st
cleanup st = do
ioref <- getTLS loggingTLS
freeAllTLS loggingTLS
writeIORef ioref Nothing
withBackend st actions = actions
-- | Get current logging state. Fail if it is not initialized yet.
getLogginngIOState :: IO LoggingIOState
getLogginngIOState = do
ioref <- getTLS loggingTLS
mbState <- readIORef ioref
case mbState of
Nothing -> fail "get: Logging IO state is not initialized. See withLoggingIO."
Just st -> return st
-- | Modify logging state with pure function.
-- Fail if logging state is not initialized yet.
modifyLoggingIOState :: (LoggingIOState -> LoggingIOState) -> IO ()
modifyLoggingIOState fn = do
ioref <- getTLS loggingTLS
modifyIORef ioref $ \mbState ->
case mbState of
Nothing -> error "modify: Logging IO state is not initialized. See withLoggingIO."
Just st -> Just (fn st)
instance HasLogBackend AnyLogBackend IO where
getLogBackend = do
st <- getLogginngIOState
return $ liosBackend st
instance HasLogger IO where
getLogger = do
st <- getLogginngIOState
return $ liosLogger st
localLogger logger actions = do
oldLogger <- getLogger
modifyLoggingIOState $ \st -> st {liosLogger = logger}
result <- actions
modifyLoggingIOState $ \st -> st {liosLogger = oldLogger}
return result
instance HasLogContext IO where
getLogContext = do
st <- getLogginngIOState
return $ liosContext st
withLogContext frame actions = do
oldContext <- getLogContext
modifyLoggingIOState $ \st -> st {liosContext = frame:oldContext}
result <- actions
modifyLoggingIOState $ \st -> st {liosContext = oldContext}
return result