------------------------------------------------------------------------ -- | -- Module : ALife.Creatur.Tools.Logger -- Copyright : (c) Amy de Buitléir 2011-2013 -- License : BSD-style -- Maintainer : amy@nualeargais.ie -- Stability : experimental -- Portability : portable -- -- A simple rotating log, tailored to the needs of the Créatúr -- framework. -- ------------------------------------------------------------------------ {-# LANGUAGE UnicodeSyntax #-} {-# OPTIONS_GHC -fno-warn-type-defaults #-} module ALife.Creatur.Logger ( Logger(..), SimpleRotatingLogger, mkSimpleRotatingLogger ) where import Control.Monad (when, unless) import Control.Monad.IO.Class (liftIO) import Control.Monad.State (StateT, get, gets, put) import Data.Eq.Unicode ((≡)) import Data.Time (formatTime, getZonedTime) import System.Directory (createDirectoryIfMissing, doesFileExist, renameFile) import System.Locale (defaultTimeLocale) class Logger l where -- | @'write' msg@ formats and writes a new log message. writeToLog ∷ String → StateT l IO () -- | A rotating logger. data SimpleRotatingLogger = SimpleRotatingLogger { initialised ∷ Bool, directory ∷ FilePath, logFilename ∷ FilePath, expFilename ∷ FilePath, maxRecordsPerFile ∷ Int, recordCount ∷ Int } deriving Show -- | @'mkSimpleRotatingLogger' d prefix n@ creates a logger that will write to -- directory @d@. The log \"rotates\" (starts a new log file) every @n@ -- records. Log files follow the naming convention @prefix@./k/, where /k/ -- is the number of the last log record contained in the file. If logging -- has already been set up in @directory@, then logging will continue where -- it left off; appending to the most recent log file. mkSimpleRotatingLogger ∷ FilePath → String → Int → SimpleRotatingLogger mkSimpleRotatingLogger d pre n = SimpleRotatingLogger False d fLog fExp n (-1) where fLog = d ++ "/" ++ pre ++ ".log" fExp = d ++ "/" ++ pre ++ ".exp" instance Logger SimpleRotatingLogger where writeToLog msg = do initIfNeeded logger ← get logger' ← liftIO $ bumpRecordCount logger put logger' liftIO $ write' logger' msg initIfNeeded ∷ StateT SimpleRotatingLogger IO () initIfNeeded = do isInitialised ← gets initialised unless isInitialised $ do logger ← get logger' ← liftIO $ initialise logger put logger' initialise ∷ SimpleRotatingLogger → IO SimpleRotatingLogger initialise logger = do createDirectoryIfMissing True (directory logger) let fExp = expFilename logger expFileExists ← doesFileExist fExp if expFileExists then do s ← readFile fExp return $ logger { initialised=True, recordCount=read s} else return $ logger { initialised=True, recordCount=0} write' ∷ SimpleRotatingLogger → String → IO () write' logger msg = do timestamp ← fmap (formatTime defaultTimeLocale "%y%m%d%H%M%S%z") getZonedTime appendFile (logFilename logger) $ timestamp ++ "\t" ++ msg ++ "\n" bumpRecordCount ∷ SimpleRotatingLogger → IO SimpleRotatingLogger bumpRecordCount logger = do let n = 1 + recordCount logger when (0 ≡ n `mod` maxRecordsPerFile logger) $ liftIO $ rotateLog logger writeFile (expFilename logger) (show n) return logger{ recordCount=n } rotateLog ∷ SimpleRotatingLogger → IO () rotateLog logger = do let f = logFilename logger renameFile f $ f ++ '.' : (show . recordCount) logger