SessionLogger-0.3.0.0: src/System/Log/SessionLogger.hs
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE FlexibleContexts #-}
-- | Example of Usage:
--
-- @
-- runIOLogger "Applicationname" $ do
-- initLogger
-- logNotice "Just a small notice"
-- logWarn "Something more important"
-- logData "filename" "Logfile Info" "data which is logged to file"
-- @
module System.Log.SessionLogger (
-- * Binds ...
(>>>=), (>>>>=)
-- * Data
, LoggerData(..), ExtraData, Logger(..)
-- * Initializing functions
, initLogger, testLogger, runIOLogger, withExtraData
-- * Logging functions
, logNotice, logInfo, logWarn, logError, logInternalInfo
, logData, logGlobInfo
-- * Internal/debugging functions
, getLogId, generateLogId
) where
import System.Locale (defaultTimeLocale)
import System.FilePath.Posix ((</>))
import System.IO (hClose, openFile, hPutStrLn, IOMode(..), hFlush)
import System.Directory (createDirectoryIfMissing)
import System.Log.Logger (errorM, getRootLogger, saveGlobalLogger, setLevel
, Priority(..), noticeM, setLevel
, setHandlers)
import System.Log.Handler.Syslog (openlog,Facility(..))
import System.Random (randomIO)
import Data.Time.LocalTime (getZonedTime)
import Data.Time.Format (formatTime)
import Control.Monad.Trans (MonadIO(..), MonadTrans)
import Control.Monad.Reader (asks, ReaderT, local, runReaderT, MonadReader(..))
import System.IO (hSetEncoding, utf8)
--------------------------------------------------------------------------------
-- Definitions
--------------------------------------------------------------------------------
type Error = String
_LOGPATH_ = "logs/"
-- TODO: main.log with syslog
_MAIN_LOG_ = "logs/main.log"
_INFO_LOG_ = "logs/info.log"
type ExtraData = [String]
data LoggerData = LoggerData { ld_logId :: String
, ld_appName :: String
, ld_extraDatas :: [ExtraData]
, ld_nolog :: Bool
}
--type Logger a = ReaderT LoggerData IO a
newtype Logger m a = Logger (ReaderT LoggerData m a)
deriving (Monad, MonadIO, MonadTrans)
-- TODO: really the right way?
initLogger :: (MonadIO m) => Logger m ()
initLogger = do
logger <- liftIO $ getRootLogger
name <- getAppName
syslog <- liftIO $ openlog name [] LOCAL3 DEBUG
liftIO $ saveGlobalLogger (setHandlers [syslog] (setLevel DEBUG logger))
return ()
testLogger :: MonadIO m => Logger m a -> m a
testLogger (Logger readerT) =
runReaderT readerT (LoggerData "42" "" [] True)
runIOLogger :: MonadIO m => String -> Logger m a -> m a
runIOLogger appname (Logger readerT) = do
logid <- liftIO $ generateLogId
runReaderT readerT (LoggerData logid appname [] False)
--------------------------------------------------------------------------------
-- Public Functions
--------------------------------------------------------------------------------
generateLogId :: IO String
generateLogId = do
rand <- liftIO $ randomIO
time <- createVerySimpleTS
let logid = show (abs $ rand::Int)
return $ time ++ (take 8 $ extend logid '0' 9)
-- Replace this Function. Inline recursion?
extend :: String -> Char -> Int -> String
extend str chr len = if (length str) < len
then (extend (str++(show chr)) chr len)
else str
getLogId ::(Monad m) => Logger m String
getLogId = Logger (asks ld_logId)
getAppName :: Monad m => Logger m String
getAppName = Logger (asks ld_appName)
getExtraDatas :: Monad m => Logger m [ExtraData]
getExtraDatas = Logger (asks ld_extraDatas)
getNolog :: Monad m => Logger m Bool
getNolog = Logger (asks ld_nolog)
(>>>>=) :: (MonadIO m, MonadReader LoggerData (Logger m), Show a) =>
String -> IO a -> Logger m a
(>>>>=) str f = do
ret <- liftIO f
logid <- getLogId
writeLog logid $ str ++ show ret
logExtraDatas str
return ret
(>>>=) :: (MonadIO m, MonadReader LoggerData (Logger m), Show a) =>
String -> IO a -> Logger m a
(>>>=) str f = do
ret <- liftIO f
logid <- getLogId
writeLog logid str
logExtraDatas str
return ret
logNotice :: (MonadIO m) => String -> Logger m ()
logNotice str = do
let text = "NOTICE: " ++ str
logid <- getLogId
writeLog logid text
logInfo :: (MonadIO m) => String -> Logger m ()
logInfo str = do
let text = "INFO: " ++ str
logid <- getLogId
writeLog logid text
logGlobInfo str
logExtraDatas text
return ()
logWarn :: (MonadIO m) => String -> Logger m ()
logWarn str = do
let text = "WARN: " ++ str
logid <- getLogId
writeLog logid text
logExtraDatas text
logMainSafe text
-- Only logs in syslog if initLogger was called first
logError :: (MonadIO m) => String -> Logger m ()
logError str = do
let text = "ERROR: " ++ str
logid <- getLogId
writeLog logid text
logExtraDatas text
logMainSafe text
appName <- getAppName
liftIO $ errorM appName text
return ()
logInternalInfo :: (MonadIO m) => String -> Logger m ()
logInternalInfo str = do
let text = "INTERNAL INFO: " ++ str
logid <- getLogId
logMainSafe text
logData :: (MonadIO m) => String -> String -> String -> Logger m ()
logData filename str dat = do
timestr <- liftIO $ createSimpleTimeString
let file = timestr ++ "-" ++ filename
text = "DATA: " ++ str ++ "\tFile: " ++ file
logid <- getLogId
writeLog logid text
logExtraDatas text
basepath <- liftIO $ createPath
liftIO $ createDirectoryIfMissing True (basepath </> logid)
writeLogFileSafe (logid</>file) dat
return ()
logGlobInfo :: (MonadIO m) => String -> Logger m ()
logGlobInfo text = do
time <- liftIO $ createTimeString
logid <- getLogId
logGlobInfoRaw (time ++ " \t" ++ logid ++ " \t" ++ text) >> return ()
return ()
--------------------------------------------------------------------------------
-- Internal Functions
--------------------------------------------------------------------------------
-- TODO: Ring the bell if error occurs (email, short message etc.)
logMainSafe :: (MonadIO m) => String -> Logger m ()
logMainSafe text = do
time <- liftIO $ createTimeString
logid <- getLogId
logMain (time ++ " \t" ++ logid ++ " \t" ++ text) >> return ()
return ()
logMain :: (MonadIO m) => String -> Logger m (Maybe Error)
logMain text = do
writeFileSafe _MAIN_LOG_ text
writeLogFileSafe "main.log" text
logGlobInfoRaw :: (MonadIO m) => String -> Logger m (Maybe Error)
logGlobInfoRaw text = do
writeFileSafe _INFO_LOG_ text
writeLogFileSafe "info.log" text
--withExtraData :: ExtraData -> Logger m -> Logger m
--withExtraData :: (MonadReader LoggerData m) => ExtraData -> m a -> m a
--withExtraData :: (Monad m) => ExtraData -> Logger m a -> Logger m a
--withExtraData ed = local (\(LoggerData id app eds) -> LoggerData id app (ed:eds))
--withExtraData = undefined
-- local :: (MonadReader r m) => (r -> r) -> m a -> m a
-- Logger (ReaderT LoggerData m a)
withExtraData :: Monad m => ExtraData -> Logger m a -> Logger m a
withExtraData ed (Logger rt) = Logger $ local
(\(LoggerData id app eds nl) ->
LoggerData id app (ed:eds) nl) rt
logExtraDatas :: (MonadIO m) => String -> Logger m ()
logExtraDatas str = do
eds <- getExtraDatas
mapM_ (logExtraData str) eds
logExtraData :: (MonadIO m) => String -> ExtraData -> Logger m ()
logExtraData str ed = do
let path = foldl (</>) "" ed
logid <- getLogId
basepath <- liftIO $ createPath
liftIO $ createDirectoryIfMissing True (basepath </> path)
writeLogFileSafe (path </> logid) str
return ()
writeLog :: (MonadIO m) => String -> String -> Logger m ()
writeLog logid text = do
time <- liftIO $ createTimeString
writeLogFileSafe (logid ++ "/" ++ "log")
(time ++ "\t" ++ text) >> return ()
-- TODO: Ring the bell if error occurs (email, short message etc.)
writeLogFileSafe :: (MonadIO m) => String -> String -> Logger m (Maybe Error)
writeLogFileSafe filename text = writeLogFile filename text
-- catch (writeLogFile filename text)
-- $ \e -> return $ Just
-- $ "Could not open File " ++ filename ++ "\t\t" ++ (show e)
writeLogFile :: (MonadIO m) => String -> String -> Logger m (Maybe Error)
writeLogFile filename text = do
path <- liftIO createPath
liftIO $ createDirectoryIfMissing True path
let logfile = path </> filename
writeFileSafe logfile text
writeFileSafe :: (MonadIO m) => String -> String -> Logger m (Maybe Error)
writeFileSafe file text =
getNolog >>= \dummy ->
if dummy == False then
liftIO $ catch (do
outh <- openFile file AppendMode
hSetEncoding outh utf8
hPutStrLn outh text
hFlush outh
hClose outh
return Nothing)
(\e -> return $ Just ("Could not open File " ++ file ++ "\t\t" ++ (show e)))
else
return Nothing
--------------------------------------------------------------------------------
-- Helper Functions
--------------------------------------------------------------------------------
createPath :: IO String
createPath = do
zt <- getZonedTime
let calDirectory = formatTime defaultTimeLocale "%Y-%m-%d" zt
createDirectoryIfMissing True $ _LOGPATH_ ++ calDirectory
return $ _LOGPATH_ ++ calDirectory
createTimeString :: IO String
createTimeString = do
zt <- getZonedTime
pico <- generatePicoSeconds
return $ (formatTime defaultTimeLocale "%H:%M:%S." zt) ++ pico
createSimpleTimeString :: IO String
createSimpleTimeString = do
zt <- getZonedTime
pico <- generatePicoSeconds
return $ (formatTime defaultTimeLocale "%H%M%S." zt) ++ pico
createVerySimpleTS :: IO String
createVerySimpleTS =
getZonedTime >>= \zt ->
return $ formatTime defaultTimeLocale "%H%M%S" zt
generatePicoSeconds :: IO String
generatePicoSeconds =
getZonedTime >>= \zt ->
return $ take 5 $ formatTime defaultTimeLocale "%q" zt