{-| Module : Log Description : Logging system for the CLI Copyright : (c) 2017 Patrick Champion License : see LICENSE file Maintainer : chlablak@gmail.com Stability : provisional Portability : portable -} module Log ( Logger , LoggerIO -- ** High-API , start , ok , setLevel , getLevel , flushAll , flushOutOnly , flushOutToFile , pushTag , popTag , pushTask , popTask , autoFlush -- ** Low-API , pushMsg , pushOut -- **Shortcuts , io , none , off , fatal , err , warn , info , debug , trace , abort , out , getLog , task ) where import Control.Monad.State import Data.Char import System.Clock -- |Alias type Tag = String type Level = Int type Task = String -- |Logging messages data Message = PushMsg Level String -- ^Push a new message with a specific level | PushOut String -- ^Push a new message to output | PushTag Tag -- ^Push a new tag | PopTag -- ^Pop the top tag | SetLevel Level -- ^Set veborsity level | AutoFlush Bool -- ^Set auto flush on/off | None -- ^Nothing to do deriving Show -- |Logger type Log = (Bool, Level, [Tag], [(Task, TimeSpec)], [Message], Bool) type Logger = StateT Log IO () type LoggerIO a = StateT Log IO a -- |IO operation io :: IO a -> LoggerIO a io = liftIO -- |Pre-defined shortcuts off = setLevel 1000 fatal m = do { pushTag "fatal"; pushMsg 60 m; popTag; ko } err m = do { pushTag "error"; pushMsg 50 m; popTag } warn m = do { pushTag "warning"; pushMsg 40 m; popTag } info m = do { pushTag "info"; pushMsg 30 m; popTag } debug m = do { pushTag "debug"; pushMsg 20 m; popTag } trace m = do { pushTag "trace"; pushMsg 10 m; popTag } abort = fatal "aborting..." out = pushOut getLog = (False, 0 :: Int, [], [], [], True) task x = x `seq` x -- |Start a new logger start :: Level -> Tag -> Logger start l t = put (False, l, [t], [], [], True) -- |Is the logger ok ? ok :: LoggerIO Bool ok = do (_, _, _, _, _, v) <- get Log.io $ return v -- |Set logger to KO ko :: Logger ko = do (af, l, ts, ks, ms, _) <- get put (af, l, ts, ks, ms, False) -- |Get level getLevel :: LoggerIO Int getLevel = do (_, l, _, _, _, _) <- get Log.io $ return l -- |Nothing to do none :: Logger none = do (af, l, ts, ks, ms, v) <- get put (af, l, ts, ks, None : ms, v) -- |Push a new tag pushTag :: Tag -> Logger pushTag t = do (af, l, ts, ks, ms, v) <- get put (af, l, ts, ks, PushTag t : ms, v) -- |Pop the top tag popTag :: Logger popTag = do (af, l, ts, ks, ms, v) <- get put (af, l, ts, ks, PopTag : ms, v) -- |Push a new message with a specific level pushMsg :: Level -> String -> Logger pushMsg l m = do (af, l2, ts, ks, ms, v) <- get put (af, l2, ts, ks, PushMsg l m : ms, v) when af flushAll -- |Push a new message to be outputed pushOut :: String -> Logger pushOut m = do (af, l, ts, ks, ms, v) <- get put (af, l, ts, ks, PushOut m : ms, v) when af flushAll -- |Set verbosity level setLevel :: Int -> Logger setLevel l = do (af, l, ts, ks, ms, v) <- get put (af, l, ts, ks, SetLevel l : ms, v) -- |Set auto flush on/off autoFlush :: Bool -> Logger autoFlush value = do (af, l, ts, ks, ms, v) <- get put (af, l, ts, ks, AutoFlush value : ms, v) when value flushAll -- |Push a new task pushTask :: Task -> Logger pushTask k = do (af, l, ts, ks, ms, v) <- get start <- io $ getTime Monotonic put (af, l, ts, (k,start):ks, ms, v) pushTag "task" pushTag "start" pushMsg 30 k popTag popTag -- |Pop the top task popTask :: Logger popTask = do (af, l, ts, (k,start):ks, ms, v) <- get put (af, l, ts, ks, ms, v) end <- io $ getTime Monotonic let TimeSpec { sec = _, nsec = nsec } = diffTimeSpec start end pushTag "task" pushTag "end" pushMsg 30 $ "in " ++ show (div nsec 1000000) ++ "ms, " ++ k popTag popTag -- |Flush the logger to output flushAll :: Logger flushAll = do (af, l, ts, ks, ms, v) <- get flushAll' (af, l, ts, ks, reverse ms, v) where flushAll' l@(_, _, _, _, [], v) = put l flushAll' (af, l, ts, ks, PushMsg l2 m:ms, v) = do when (l <= l2) $ do io $ putTag ts io $ putStrLn m flushAll' (af, l, ts, ks, ms, v) flushAll' (af, l, ts, ks, PushOut m:ms, v) = do io $ putStrLn m flushAll' (af, l, ts, ks, ms, v) flushAll' (af, l, ts, ks, PushTag t:ms, v) = flushAll' (af, l, t:ts, ks, ms, v) flushAll' (af, l, _:ts, ks, PopTag:ms, v) = flushAll' (af, l, ts, ks, ms, v) flushAll' (af, _, ts, ks, SetLevel l:ms, v) = flushAll' (af, l, ts, ks, ms, v) flushAll' (_, l, ts, ks, AutoFlush af:ms, v) = flushAll' (af, l, ts, ks, ms, v) flushAll' (af, l, ts, ks, None:ms, v) = flushAll' (af, l, ts, ks, ms, v) putTag ts = putStr $ concatMap (\t -> "[" ++ map toUpper t ++ "]") (reverse ts) ++ " " -- |Flush the output to a file flushOutToFile :: FilePath -> Logger flushOutToFile f = do (af, l, ts, ks, ms, v) <- get io $ writeFile f "" flushAll' (af, l, ts, ks, reverse ms, v) where flushAll' l@(_, _, _, _, [], v) = put l flushAll' (af, l, ts, ks, PushMsg l2 m:ms, v) = flushAll' (af, l, ts, ks, ms, v) flushAll' (af, l, ts, ks, PushOut m:ms, v) = do io $ appendFile f $ m ++ "\n" flushAll' (af, l, ts, ks, ms, v) flushAll' (af, l, ts, ks, PushTag t:ms, v) = flushAll' (af, l, t:ts, ks, ms, v) flushAll' (af, l, _:ts, ks, PopTag:ms, v) = flushAll' (af, l, ts, ks, ms, v) flushAll' (af, _, ts, ks, SetLevel l:ms, v) = flushAll' (af, l, ts, ks, ms, v) flushAll' (_, l, ts, ks, AutoFlush af:ms, v) = flushAll' (af, l, ts, ks, ms, v) flushAll' (af, l, ts, ks, None:ms, v) = flushAll' (af, l, ts, ks, ms, v) -- |Flush output only flushOutOnly :: Logger flushOutOnly = do (af, l, ts, ks, ms, v) <- get flushAll' (af, l, ts, ks, reverse ms, v) where flushAll' l@(_, _, _, _, [], v) = put l flushAll' (af, l, ts, ks, PushMsg l2 m:ms, v) = flushAll' (af, l, ts, ks, ms, v) flushAll' (af, l, ts, ks, PushOut m:ms, v) = do io $ putStrLn m flushAll' (af, l, ts, ks, ms, v) flushAll' (af, l, ts, ks, PushTag t:ms, v) = flushAll' (af, l, t:ts, ks, ms, v) flushAll' (af, l, _:ts, ks, PopTag:ms, v) = flushAll' (af, l, ts, ks, ms, v) flushAll' (af, _, ts, ks, SetLevel l:ms, v) = flushAll' (af, l, ts, ks, ms, v) flushAll' (_, l, ts, ks, AutoFlush af:ms, v) = flushAll' (af, l, ts, ks, ms, v) flushAll' (af, l, ts, ks, None:ms, v) = flushAll' (af, l, ts, ks, ms, v)