heavy-logger-0.3.1.0: src/System/Log/Heavy/Backends/Dynamic.hs
{-# LANGUAGE OverloadedStrings, TypeSynonymInstances, FlexibleInstances, ExistentialQuantification, TypeFamilies, GeneralizedNewtypeDeriving, StandaloneDeriving, MultiParamTypeClasses, UndecidableInstances #-}
-- | This module incldues logging backend combinators that allow to change
-- underlying backend or it's settings in runtime.
module System.Log.Heavy.Backends.Dynamic
(
-- * Dynamic backend
DynamicBackend,
DynamicBackendHandle,
newDynamicBackendHandle,
updateDynamicBackendSettings,
LogBackendSettings (..),
-- * Dynamic filtering
FilteringM, filteringM, excludingM
) where
import Control.Monad (when)
import Control.Concurrent
import Control.Concurrent.STM
import System.Log.Heavy.Types
import System.Log.Heavy.Util
-- | Abstract handle, that is used to control @DynamicBackend@.
data DynamicBackendHandle = DynamicBackendHandle {
dbhBroadcast :: TChan LoggingSettings -- ^ Broadcast TChan. It is write-only.
, dbhDefault :: LoggingSettings -- ^ Initial logging settings. Used only before first update in TChan.
-- We cannot put this into TChan at beginning, because after dupTChan
-- newly created TChan's will be empty anyway.
}
-- I put these data into special Handle type, to hide the implementation detail
-- that it uses TChan inside. This detail can be changed in later versions.
-- | Create an instance of @DynamicBackendHandle@.
newDynamicBackendHandle :: LoggingSettings -- ^ Initial logging settings. This can be changed later with @updateDynamicBackendSettings@.
-> IO DynamicBackendHandle
newDynamicBackendHandle settings = do
broadcast <- newBroadcastTChanIO
return $ DynamicBackendHandle broadcast settings
-- | Update settings of @DynamicBackend@, which was created by provided handle.
updateDynamicBackendSettings :: DynamicBackendHandle -- ^ Handle of @DynamicBackend@.
-> LoggingSettings -- ^ New logging settings.
-> IO ()
updateDynamicBackendSettings handle settings = do
atomically $ writeTChan (dbhBroadcast handle) settings
-- | Dynamic logging backend allows to change logging backend or it's settings
-- in runtime. When it sees new backend settings, it deinitializes old backend
-- and initializes new one.
--
-- How to use it:
--
-- * Before creating @DynamicSettings@, you have to select some initial
-- @LoggingSettings@ and create @DynamicBackendHandle@ with it.
-- * When you decide that you want to use new backend settings, call
-- @updateDynamicBackendSettings@ on existing @DynamicBackendHandle@.
-- @DynamicBackend@ will use new settings for the next logging function call.
-- * It is responsibility of caller code to do not change backends too frequently;
-- for example, if you are checking your config file for updates of logging
-- settings each 10s, you have to check that settings actually changed since
-- last time.
--
-- It is possible to create one instance of @DynamicBackendHandle@ and pass it
-- to multiple threads.
--
data DynamicBackend = DynamicBackend {
dbCurrentBackend :: MVar AnyLogBackend -- ^ Currently used logging backend
, dbNewSettings :: TChan LoggingSettings -- ^ TChan with updates of settings. This is read only.
}
-- Implementation is based on idea that Backend instances theirself are thread-safe;
-- i.e. it is safe to create several instances of any IsLogBackend data type in several threads
-- and they will coexist without problems (they do not use any shared state).
-- So, we can have one TChan, in which we can push updated settings when we want;
-- Each instance of DynamicBackend will have its own instance of underlying backend.
instance IsLogBackend DynamicBackend where
data LogBackendSettings DynamicBackend = DynamicSettings DynamicBackendHandle
initLogBackend (DynamicSettings (DynamicBackendHandle broadcast (LoggingSettings dfltSettings))) = do
-- Duplicate broadcast TChan - create "own copy".
mySettingsChan <- atomically $ dupTChan broadcast
-- Initialize default backend
backend <- initLogBackend dfltSettings
backendVar <- newMVar (AnyLogBackend backend)
return $ DynamicBackend backendVar mySettingsChan
cleanupLogBackend (DynamicBackend backendVar _) = do
-- Cleanup currently used backend
backend <- takeMVar backendVar
cleanupLogBackend backend
wouldWriteMessage (DynamicBackend backendVar _) msg = do
-- Delegate the call to currently used backend.
backend <- readMVar backendVar
wouldWriteMessage backend msg
makeLogger (DynamicBackend backendVar settingsChan) msg = do
-- See if there is an update of settings queued.
mbNewSettings <- atomically $ tryReadTChan settingsChan
-- Note: tryReadTChan will remove element from channel, if there is any.
-- But, it will affect only "our own copy" of TChan. If there are other
-- instances of DynamicBackend living in other threads with the same
-- Handle, they will still see settings update in their own copies of
-- TChan.
case mbNewSettings of
Nothing -> do
-- No updates. Use current backend.
backend <- readMVar backendVar
makeLogger backend msg
Just (LoggingSettings newSettings) -> do
-- New settings came. We have to switch backends.
oldBackend <- takeMVar backendVar
-- Cleanup old backend
cleanupLogBackend oldBackend
-- Initialize new backend
newBackend <- initLogBackend newSettings
putMVar backendVar (AnyLogBackend newBackend)
-- Delegate call to new backend
makeLogger newBackend msg
-- | Filtering backend with mutable filter.
data FilteringM b = FilteringBackendM (MVar (LogMessage -> Bool)) b
-- | Specify filter as @LogFilter@. This filter can be changed later.
filteringM :: IsLogBackend b => LogFilter -> LogBackendSettings b -> IO (LogBackendSettings (FilteringM b))
filteringM fltr b = do
fltrVar <- newMVar (checkLogLevel fltr)
return $ FilteringM fltrVar b
-- | Exclude messages by filter. This filter can be changed later.
excludingM :: IsLogBackend b => LogFilter -> LogBackendSettings b -> IO (LogBackendSettings (FilteringM b))
excludingM fltr b = do
fltrVar <- newMVar ex
return $ FilteringM fltrVar b
where
ex msg = not $ checkContextFilter' [LogContextFilter Nothing (Just fltr)] (lmSource msg) (lmLevel msg)
instance IsLogBackend b => IsLogBackend (FilteringM b) where
data LogBackendSettings (FilteringM b) =
FilteringM (MVar (LogMessage -> Bool)) (LogBackendSettings b)
wouldWriteMessage (FilteringBackendM fltrVar _) msg = do
fltr <- readMVar fltrVar
return $ fltr msg
makeLogger (FilteringBackendM fltrVar backend) msg = do
fltr <- readMVar fltrVar
when (fltr msg) $ do
makeLogger backend msg
initLogBackend (FilteringM fltrVar settings) = do
backend <- initLogBackend settings
return $ FilteringBackendM fltrVar backend
cleanupLogBackend (FilteringBackendM _ backend) = cleanupLogBackend backend