typeclasses / haskell-phrasebook

The Haskell Phrasebook: a quick intro to Haskell via small annotated example programs
https://typeclasses.com/phrasebook
210 stars 22 forks source link

Logging #15

Open chris-martin opened 4 years ago

chris-martin commented 4 years ago

I like concurrent logging as the subject as a Phrasebook example because it's a common need and a good excuse to concisely put together a lot of topics.

Here's some code that comes straight out of the repo for the Type Classes server. It probably contains a few too many topics and needs to be significantly simplified and better focused.

import Control.Concurrent.Async (withAsync)
import Control.Concurrent.STM
import Control.Exception.Safe (Exception (displayException), catchAny, finally)
import Control.Monad (forever)
import Control.Monad.Trans.Cont
import Data.Text (Text)
import qualified Data.Text as Text
import qualified Data.Text.IO as Text
import System.IO (stderr)

data Log =
  Log
    { logText :: Text -> IO ()
    , logString :: String -> IO ()
    }

withLogging :: ContT a IO Log
withLogging = ContT \continue ->
  do
    q <- atomically newTQueue

    let
        logText msg = atomically (writeTQueue q $! msg)
        logString = logText . Text.pack
        l = Log {..}

    withAsync (runLogger q) \_ -> (continue l)

runLogger :: TQueue Text -> IO ()
runLogger q = finally runForever runUntilEmpty
  where
    runForever = forever $ atomically (readTQueue q) >>= write

    runUntilEmpty =
        atomically (tryReadTQueue q) >>=
        \case
            Nothing -> return ()
            Just msg -> write msg *> runUntilEmpty

    write msg = Text.hPutStrLn stderr msg

recover :: Log -> IO a -> IO (Maybe a)
recover log a = catchAny (fmap Just a) (\e -> logException log e *> return Nothing)

logException :: Exception e => Log -> e -> IO ()
logException log e = logString log (displayException e)