2011-02-21 01:56:57 +00:00
|
|
|
-- | Produce pretty, thread-safe logs
|
|
|
|
--
|
|
|
|
{-# LANGUAGE BangPatterns #-}
|
|
|
|
module Hakyll.Core.Logger
|
|
|
|
( Logger
|
|
|
|
, makeLogger
|
|
|
|
, flushLogger
|
|
|
|
, section
|
|
|
|
, timed
|
2011-02-21 12:15:11 +00:00
|
|
|
, report
|
2011-03-06 14:56:22 +00:00
|
|
|
, thrown
|
2011-02-21 01:56:57 +00:00
|
|
|
) where
|
|
|
|
|
2011-02-21 09:56:04 +00:00
|
|
|
import Control.Monad (forever)
|
2011-02-21 01:56:57 +00:00
|
|
|
import Control.Monad.Trans (MonadIO, liftIO)
|
2011-05-25 09:23:50 +00:00
|
|
|
import Control.Applicative (pure, (<$>), (<*>))
|
2011-02-21 01:56:57 +00:00
|
|
|
import Control.Concurrent (forkIO)
|
|
|
|
import Control.Concurrent.Chan.Strict (Chan, newChan, readChan, writeChan)
|
|
|
|
import Control.Concurrent.MVar.Strict (MVar, newEmptyMVar, takeMVar, putMVar)
|
|
|
|
import Text.Printf (printf)
|
|
|
|
|
|
|
|
import Data.Time (getCurrentTime, diffUTCTime)
|
|
|
|
|
|
|
|
-- | Logger structure. Very complicated.
|
|
|
|
--
|
|
|
|
data Logger = Logger
|
2011-05-25 09:23:50 +00:00
|
|
|
{ loggerChan :: Chan (Maybe String) -- ^ Nothing marks the end
|
|
|
|
, loggerSync :: MVar () -- ^ Used for sync on quit
|
|
|
|
, loggerSink :: String -> IO () -- ^ Out sink
|
2011-02-21 01:56:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
-- | Create a new logger
|
|
|
|
--
|
2011-05-25 09:23:50 +00:00
|
|
|
makeLogger :: (String -> IO ()) -> IO Logger
|
|
|
|
makeLogger sink = do
|
|
|
|
logger <- Logger <$> newChan <*> newEmptyMVar <*> pure sink
|
2011-02-21 01:56:57 +00:00
|
|
|
_ <- forkIO $ loggerThread logger
|
|
|
|
return logger
|
|
|
|
where
|
2011-02-21 09:56:04 +00:00
|
|
|
loggerThread logger = forever $ do
|
2011-02-21 01:56:57 +00:00
|
|
|
msg <- readChan $ loggerChan logger
|
|
|
|
case msg of
|
|
|
|
-- Stop: sync
|
|
|
|
Nothing -> putMVar (loggerSync logger) ()
|
|
|
|
-- Print and continue
|
2011-05-25 09:23:50 +00:00
|
|
|
Just m -> loggerSink logger m
|
2011-02-21 01:56:57 +00:00
|
|
|
|
|
|
|
-- | Flush the logger (blocks until flushed)
|
|
|
|
--
|
|
|
|
flushLogger :: Logger -> IO ()
|
|
|
|
flushLogger logger = do
|
|
|
|
writeChan (loggerChan logger) Nothing
|
|
|
|
() <- takeMVar $ loggerSync logger
|
|
|
|
return ()
|
|
|
|
|
|
|
|
-- | Send a raw message to the logger
|
|
|
|
--
|
|
|
|
message :: Logger -> String -> IO ()
|
|
|
|
message logger = writeChan (loggerChan logger) . Just
|
|
|
|
|
|
|
|
-- | Start a section in the log
|
|
|
|
--
|
|
|
|
section :: MonadIO m
|
|
|
|
=> Logger -- ^ Logger
|
|
|
|
-> String -- ^ Section name
|
|
|
|
-> m () -- ^ No result
|
|
|
|
section logger = liftIO . message logger
|
|
|
|
|
|
|
|
-- | Execute a monadic action and log the duration
|
|
|
|
--
|
|
|
|
timed :: MonadIO m
|
|
|
|
=> Logger -- ^ Logger
|
|
|
|
-> String -- ^ Message
|
|
|
|
-> m a -- ^ Action
|
|
|
|
-> m a -- ^ Timed and logged action
|
|
|
|
timed logger msg action = do
|
|
|
|
start <- liftIO getCurrentTime
|
|
|
|
!result <- action
|
|
|
|
stop <- liftIO getCurrentTime
|
|
|
|
let diff = fromEnum $ diffUTCTime stop start
|
|
|
|
ms = diff `div` 10 ^ (9 :: Int)
|
|
|
|
formatted = printf " [%4dms] %s" ms msg
|
|
|
|
liftIO $ message logger formatted
|
|
|
|
return result
|
2011-02-21 12:15:11 +00:00
|
|
|
|
|
|
|
-- | Log something at the same level as 'timed', but without the timing
|
|
|
|
--
|
|
|
|
report :: MonadIO m
|
|
|
|
=> Logger -- ^ Logger
|
|
|
|
-> String -- ^ Message
|
|
|
|
-> m () -- ^ No result
|
|
|
|
report logger msg = liftIO $ message logger $ " [ ] " ++ msg
|
2011-03-06 14:56:22 +00:00
|
|
|
|
|
|
|
-- | Log an error that was thrown in the compilation phase
|
|
|
|
--
|
|
|
|
thrown :: MonadIO m
|
|
|
|
=> Logger -- ^ Logger
|
|
|
|
-> String -- ^ Message
|
|
|
|
-> m () -- ^ No result
|
|
|
|
thrown logger msg = liftIO $ message logger $ " [ ERROR] " ++ msg
|