{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE RecordWildCards #-} module Logging.Handler.RotatingFileHandler ( RotatingFileHandler(..) ) where import Control.Concurrent.MVar import Control.Monad import GHC.Generics import System.IO import Text.Format import Logging.Class import Logging.Filter import Logging.Level import Logging.Prelude -- | A handler type which writes logging records, appropriately formatted, -- to a file, it will rotate when file is too large. -- -- Since 0.3.0 -- data RotatingFileHandler = RotatingFileHandler { level :: Level , filterer :: Filterer , formatter :: Format1 , file :: FilePath , encoding :: TextEncoding , maxBytes :: Int -- ^ Actual file size may be -- slightly larger than this -- value. , backupCount :: Int , stream :: MVar Handle -- ^ Don't open file manually, -- initialized as empty 'MVar' -- and use 'open' or -- "Logger.Manager.initialize" } deriving (Generic, Eq) instance Handler RotatingFileHandler where open RotatingFileHandler{..} = isEmptyMVar stream >>= safeOpen where safeOpen True = void $ tryPutMVar stream =<< openLogFile file encoding safeOpen False = modifyMVar_ stream $ \h -> do hClose h openLogFile file encoding emit handler@RotatingFileHandler{..} rcd = do let msg = format1 formatter rcd modifyMVar_ stream $ \stream' -> do hPutStrLn stream' msg hFlush stream' pos <- hTell stream' rollover (backupCount > 0) (fromEnum pos >= maxBytes) stream' where rollover :: Bool -> Bool -> Handle -> IO Handle rollover True True h = do hClose h rotate $ backupCount - 1 tryRenameFile file $ appendBaseName file ".1" openLogFile file encoding rollover _ _ h = return h rotate :: Int -> IO () rotate n = when (n > 0) $ do let src = appendBaseName file $ '.' : (show n) dest = appendBaseName file $ '.' : (show (n + 1)) tryRenameFile src dest rotate (n - 1) close RotatingFileHandler{..} = withMVar stream hClose