{-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} module Keter.Logger ( Logger , start , attach , detach , LogPipes (..) , LogPipe , mkLogPipe , dummy ) where import Keter.Prelude import qualified Prelude as P import qualified Keter.LogFile as LogFile import Control.Concurrent (killThread) import qualified Data.ByteString as S import Data.Conduit (Sink, await) import qualified Control.Concurrent.MVar as M import Control.Monad.Trans.Class (lift) data LogPipes = LogPipes { stdOut :: LogPipe , stdErr :: LogPipe } data LogPipe = LogPipe { readLogPipe :: KIO (Maybe S.ByteString) , closeLogPipe :: KIO () } mkLogPipe :: KIO (LogPipe, Sink S.ByteString P.IO ()) mkLogPipe = do toSink <- newEmptyMVar fromSink <- newEmptyMVar let pipe = LogPipe { readLogPipe = do putMVar toSink True takeMVar fromSink , closeLogPipe = do _ <- tryTakeMVar toSink putMVar toSink False } sink = do toCont <- lift $ M.takeMVar toSink if toCont then do mbs <- await lift $ M.putMVar fromSink mbs maybe (return ()) (P.const sink) mbs else return () return (pipe, sink) newtype Logger = Logger (Command -> KIO ()) data Command = Attach LogPipes | Detach start :: LogFile.LogFile -- ^ stdout -> LogFile.LogFile -- ^ stderr -> KIO Logger start lfout lferr = do chan <- newChan forkKIO $ loop chan Nothing Nothing return $ Logger $ writeChan chan where killOld tid = do res <- liftIO $ killThread tid case res of Left e -> $logEx e Right () -> return () loop chan moldout molderr = do c <- readChan chan maybe (return ()) killOld moldout maybe (return ()) killOld molderr case c of Detach -> do LogFile.close lfout LogFile.close lferr Attach (LogPipes out err) -> do LogFile.addChunk lfout "\n\nAttaching new process\n\n" LogFile.addChunk lferr "\n\nAttaching new process\n\n" let go logpipe lf = do etid <- forkKIO' $ listener logpipe lf case etid of Left e -> do $logEx e closeLogPipe logpipe return Nothing Right tid -> return $ Just tid newout <- go out lfout newerr <- go err lferr loop chan newout newerr listener :: LogPipe -> LogFile.LogFile -> KIO () listener out lf = loop where loop = do mbs <- readLogPipe out case mbs of Nothing -> return () Just bs -> do LogFile.addChunk lf bs loop attach :: Logger -> LogPipes -> KIO () attach (Logger f) h = f (Attach h) detach :: Logger -> KIO () detach (Logger f) = f Detach dummy :: Logger dummy = Logger $ P.const $ return ()