bbarker
bbarker

Reputation: 13088

How to log all exceptions in Haskell?

Original Title: How to deal with multiple instances of Exception types when inspecting all Exceptions?

I have the following imports (note my prelude is actually ClassyPrelude, which uses UnliftIO.Exception). Note that System.Logger is from tinylog, a thin library on top of fast-logger.

import           Prelude                   hiding(log)
import           System.Logger             hiding(log)
import qualified System.Logger             as TL

And the following function:

logExceptions :: MonadUnliftIO m => Logger -> m a -> m a
logExceptions logger program = withException program
  (\ex -> do
    logIt Warn logger ["Logging exception: ", (show ex)]
    flush logger
    )

Putting the lambda into a local function with a type may make it slightly more clear:

logExceptions :: MonadUnliftIO m => Logger -> m a -> m a
logExceptions logger program = withException program logEx
  where
    logEx :: (MonadUnliftIO m, Exception e) => e -> m ()
    logEx ex = do
      logIt Warn logger ["Logging exception: ", (show ex)]
      flush logger

This results in the following compile error:

    * Could not deduce (Exception e0)
        arising from a use of `withException'
      from the context: MonadUnliftIO m
        bound by the type signature for:
                   logExceptions :: forall (m :: * -> *) a.
                                    MonadUnliftIO m =>
                                    Logger -> m a -> m a
        at src/FDS/Logging.hs:19:1-56
      The type variable `e0' is ambiguous
      These potential instances exist:
        instance Exception SomeException -- Defined in `GHC.Exception.Type'
        instance Exception IOException -- Defined in `GHC.IO.Exception'
        instance Exception SomeAsyncException
          -- Defined in `GHC.IO.Exception'
        ...plus four others         
        ...plus 30 instances involving out-of-scope types
        (use -fprint-potential-instances to see them all)
    * In the expression: withException program logEx
      In an equation for `logExceptions':
          logExceptions logger program
            = withException program logEx
            where                   
                logEx :: (MonadUnliftIO m, Exception e) => e -> m ()
                logEx ex            
                  = do logIt Warn logger ...
                       ....         
   |                                
20 | logExceptions logger program = withException program logEx
   |         

The most worrisome bit is the plus 30 instances involving out-of-scope types. I could hide these imports to slightly improve the situation:

import           GHC.Exception.Type        hiding(SomeException)
import           GHC.IO.Exception          hiding(IOException, SomeAsyncException)

But it hardly seems reasonable to go through and find all 30+ exception types and mask them all out in this fashion. I assume I'm doing something quite wrong here, or do I really need to go through and mask everything?

Note:

  1. My logIt function is just a thin wrapper around the log function from tinylog - feel free to substitute with whatever feels ergonomic.

Upvotes: 1

Views: 219

Answers (1)

bbarker
bbarker

Reputation: 13088

I now understand my issue was that a concrete type was needed for the Exception argument, since it is a polymorphic function as stated in my question, and there is no call site to narrow this down to a specific type. The right answer is described in Catch 'em all! here, and it is to use the concrete SomeException type. The resulting code is:

logExceptions :: MonadUnliftIO m => Logger -> m a -> m a
logExceptions logger program = withException program logEx
  where
    logEx :: MonadUnliftIO m => SomeException -> m ()
    logEx ex = do
      logIt Warn logger ["Logging exception: ", (show ex)]
      flush logger

Upvotes: 1

Related Questions