Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Logging #15

Open
chris-martin opened this issue Sep 30, 2019 · 0 comments
Open

Logging #15

chris-martin opened this issue Sep 30, 2019 · 0 comments
Labels
good for new contributors Pull requests welcome! new example This issue is about writing a new example program.

Comments

@chris-martin
Copy link
Member

chris-martin commented Sep 30, 2019

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.

  • Starting threads with withAsync
  • Concurrent queues TQueue
  • Catching exceptions with catchAny
  • Cleaning up after interrupts with finally
  • Introducing strictness with ($!)
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)
@chris-martin chris-martin added good for new contributors Pull requests welcome! new example This issue is about writing a new example program. labels Sep 30, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
good for new contributors Pull requests welcome! new example This issue is about writing a new example program.
Projects
None yet
Development

No branches or pull requests

1 participant