{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} module Houseman.Logger ( newLogger , installLogger , runLogger , readLogger , stopLogger ) where import Control.Concurrent import Data.Char import Data.Monoid import Data.Text (Text) import Data.Time import GHC.IO.Handle import qualified Data.ByteString as ByteString import qualified Data.Text as Text import qualified Data.Text.Encoding as Text import qualified Data.Text.Encoding.Error as Text import qualified Data.Text.IO as Text import qualified System.IO.Streams as Streams import Houseman.Types -- | Instantiates new 'Logger'. newLogger :: IO Logger newLogger = do c <- newChan m <- newEmptyMVar return $ Logger c m -- | Reads one `Log` from given `Logger`. readLogger :: Logger -> IO Log readLogger (Logger logger _) = readChan logger -- | Runs given `Logger`. Logs will be output to `System.IO.stdout`. When `Logger` gets -- `LogStop`, logging will be stopped. runLogger :: Logger -> IO () runLogger (Logger logger done) = do _ <- forkIO $ go [] return () where go cs = do log' <- readChan logger case log' of Log (name,l) -> do let (color, cs') = name `lookupOrInsertNewColor` cs t <- Text.pack <$> formatTime defaultTimeLocale "%H:%M:%S" <$> getZonedTime Text.putStrLn (colorString color (t <> " " <> Text.pack name <> ": ") <> l ) go cs' LogStop -> putMVar done () lookupOrInsertNewColor :: String -> [(String, Color)] -> (Color, [(String, Color)]) lookupOrInsertNewColor x xs = case x `lookup` xs of Just color -> (color,xs) Nothing -> let color = colors !! length xs in (color,(x,color):xs) colors :: [Color] colors = cycle [32..36] colorString :: Color -> Text -> Text colorString c x = "\x1b[" <> Text.pack (show c) <> "m" <> x <> "\x1b[0m" -- | Installs new `Handle` as a source of given `Logger`. installLogger :: String -- ^ Name of source -> Logger -- ^ `Logger` instance -> Handle -- ^ The source -> IO () installLogger name (Logger logger _) handle = do is <- Streams.handleToInputStream handle >>= Streams.lines >>= Streams.map (Text.decodeUtf8With Text.lenientDecode . ByteString.filter (/= fromIntegral (ord '\r'))) os <- Streams.makeOutputStream out Streams.connect is os where out (Just l) = writeChan logger (Log (name,l)) out Nothing = return () -- | Stops `Logger`. stopLogger :: Logger -> IO () stopLogger (Logger logger stop) = do -- Wait a while to flush logs -- FIXME This won't guarantee all logs will be flushed out. threadDelay 1000 writeChan logger LogStop takeMVar stop