{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE BangPatterns #-}
module Std.IO.Logger
(
Logger
, LoggerConfig(..)
, newLogger
, loggerFlush
, setStdLogger
, getStdLogger
, withStdLogger
, debug
, info
, warn
, fatal
, otherLevel
, debugWith
, infoWith
, warnWith
, fatalWith
, otherLevelWith
) where
import Control.Monad
import Std.Data.Vector.Base as V
import Std.IO.LowResTimer
import Std.IO.StdStream
import Std.IO.Buffered
import System.IO.Unsafe (unsafePerformIO)
import Std.IO.Exception
import Data.IORef
import Control.Concurrent.MVar
import qualified Std.Data.Builder.Base as B
import qualified Std.Data.Builder.Numeric as B
import qualified Data.Time as Time
import Std.IO.Exception
data Logger = Logger
{ loggerFlush :: IO ()
, loggerThrottledFlush :: IO ()
, loggerBytesList :: {-# UNPACK #-} !(IORef [V.Bytes])
, loggerConfig :: {-# UNPACK #-} !LoggerConfig
}
data LoggerConfig = LoggerConfig
{ loggerMinFlushInterval :: {-# UNPACK #-} !Int
, loggerTsCache :: IO (B.Builder ())
, loggerLineBufSize :: {-# UNPACK #-} !Int
, loggerShowDebug :: Bool
, loggerShowTS :: Bool
}
defaultLoggerConfig :: LoggerConfig
defaultLoggerConfig = LoggerConfig 1 defaultTSCache 128 True True
defaultTSCache :: IO (B.Builder ())
{-# NOINLINE defaultTSCache #-}
defaultTSCache = unsafePerformIO $ do
throttle 1 $ do
t <- Time.getCurrentTime
return . B.string8 $
Time.formatTime Time.defaultTimeLocale "%Y-%m-%dT%H:%M:%S%Z" t
flushLog :: (HasCallStack, Output o) => MVar (BufferedOutput o) -> IORef [V.Bytes] -> IO ()
flushLog oLock bList =
withMVar oLock $ \ o -> do
bss <- atomicModifyIORef' bList (\ bss -> ([], bss))
forM_ (reverse bss) (writeBuffer o)
flushBuffer o
newLogger :: Output o
=> LoggerConfig
-> BufferedOutput o
-> IO Logger
newLogger config o = do
bList <- newIORef []
oLock <- newMVar o
let flush = flushLog oLock bList
throttledFlush <- throttleTrailing_ (loggerMinFlushInterval config) flush
return $ Logger flush throttledFlush bList config
globalLogger :: IORef Logger
{-# NOINLINE globalLogger #-}
globalLogger = unsafePerformIO $
newIORef =<< newLogger defaultLoggerConfig stderrBuf
setStdLogger :: Logger -> IO ()
setStdLogger !logger = atomicWriteIORef globalLogger logger
getStdLogger :: IO Logger
getStdLogger = readIORef globalLogger
flushDefaultLogger :: IO ()
flushDefaultLogger = getStdLogger >>= loggerFlush
pushLog :: IORef [V.Bytes] -> Int -> B.Builder () -> IO ()
pushLog blist bfsiz b = do
let !bs = B.buildBytesWith bfsiz b
atomicModifyIORef' blist (\ bss -> (bs:bss, ()))
withStdLogger :: IO () -> IO ()
withStdLogger = (`finally` flushDefaultLogger)
debug :: B.Builder () -> IO ()
debug = otherLevel "DEBUG" False
info :: B.Builder () -> IO ()
info = otherLevel "INFO" False
warn :: B.Builder () -> IO ()
warn = otherLevel "WARN" False
fatal :: B.Builder () -> IO ()
fatal = otherLevel "FATAL" True
otherLevel :: B.Builder ()
-> Bool
-> B.Builder ()
-> IO ()
otherLevel level flushNow b =
getStdLogger >>= \ logger -> otherLevelWith logger level flushNow b
debugWith :: Logger -> B.Builder () -> IO ()
debugWith logger = otherLevelWith logger "DEBUG" False
infoWith :: Logger -> B.Builder () -> IO ()
infoWith logger = otherLevelWith logger "INFO" False
warnWith :: Logger -> B.Builder () -> IO ()
warnWith logger = otherLevelWith logger "WARN" False
fatalWith :: Logger -> B.Builder () -> IO ()
fatalWith logger = otherLevelWith logger "FATAL" False
otherLevelWith :: Logger
-> B.Builder ()
-> Bool
-> B.Builder ()
-> IO ()
otherLevelWith logger level flushNow b = case logger of
(Logger flush throttledFlush blist (LoggerConfig _ tscache lbsiz showdebug showts)) -> do
ts <- if showts then tscache else return ""
when showdebug $ do
pushLog blist lbsiz $ do
B.char8 '['
level
B.char8 ']'
B.char8 ' '
when showts $ ts >> B.char8 ' '
b
B.char8 '\n'
if flushNow then flush else throttledFlush