This is a partial answer because it does not handle file truncation using logrotate
. This avoids lazy I / O and uses bytestring , streaming , streaming-bytestring and hinotify .
Some pre-import deliveries:
{-
Here is the tailing function:
tailing :: FilePath -> (B.ByteString IO () -> IO r) -> IO r tailing filepath continuation = withINotify $ \i -> do sem <- newQSem 1 addWatch i [Modify] filepath (\_ -> signalQSem sem) withFile filepath ReadMode (\h -> continuation (handleToStream sem h)) where handleToStream sem h = B.concat . Streaming.repeats $ do lift (waitQSem sem) readWithoutClosing h -- Can't use B.fromHandle here because annoyingly it closes handle on EOF -- instead of just returning, and this causes problems on new appends. readWithoutClosing h = do c <- lift (Data.ByteString.hGetSome h defaultChunkSize) if Data.ByteString.null c then return () else do B.chunk c readWithoutClosing h
A file path is required - a callback that consumes streaming bytes.
The idea is that each time before reading from the descriptor to EOF, we reduce the semaphore, which increases only by the callback that is called when the file is modified.
We can test this function as follows:
main :: IO () main = do filepath : _ <- getArgs tailing filepath B.stdout
source share