Error logging with Sentry
In this recipe we will use Sentry to collect the runtime exceptions generated by our application. We will use the raven-haskell package, which is a client for a Sentry event server. Mind that this package is not present on Stackage, so if we are using Stack we’ll need to add it to our extra-deps section in the stack.yaml file.
To exemplify this we will need the following imports
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
import Control.Exception (Exception,
SomeException, throw)
import Data.ByteString.Char8 (unpack)
import Network.Wai (Request, rawPathInfo,
requestHeaderHost)
import Network.Wai.Handler.Warp (defaultOnException,
defaultSettings,
runSettings,
setOnException,
setPort)
import Servant
import System.Log.Raven (initRaven, register,
silentFallback)
import System.Log.Raven.Transport.HttpConduit (sendRecord)
import System.Log.Raven.Types (SentryLevel (Error),
SentryRecord (..))
Just for the sake of the example we will use the following API which will throw an exception
type API = "break" :> Get '[JSON] ()
data MyException = MyException deriving (Show)
instance Exception MyException
server = breakHandler
where breakHandler :: Handler ()
breakHandler = do
throw MyException
return ()
First thing we need to do if we want to intercept and log this exception, we need to look in the section of our code where we run the warp application, and instead of using the simple run function from warp, we use the runSettings functions which allows to customise the handling of requests
main :: IO ()
main =
let
settings =
setPort 8080 $
setOnException sentryOnException $
defaultSettings
in
runSettings settings $ serve (Proxy :: Proxy API) server
The definition of the sentryOnException function could look as follows
sentryOnException :: Maybe Request -> SomeException -> IO ()
sentryOnException mRequest exception = do
sentryService <- initRaven
"https://username:password@senty.host/id"
id
sendRecord
silentFallback
register
sentryService
"myLogger"
Error
(formatMessage mRequest exception)
(recordUpdate mRequest exception)
defaultOnException mRequest exception
It does three things. First it initializes the service which will communicate with Sentry. The parameters it receives are:
the Sentry
DSN, which is obtained when creating a new project on Sentrya default way to update sentry fields, where we use the identity function
an event transport, which generally would be
sendRecord, an HTTPS capable transport which uses http-conduita fallback handler, which we choose to be
silentFallbacksince later we are logging to the console anyway.
In the second step it actually sends our message to Sentry with the register function. Its arguments are:
the configured Sentry service which we just created
the name of the logger
the error level (see SentryLevel for the possible options)
the message we want to send
an update function to handle the specific
SentryRecord
Eventually it just delegates the error handling to the default warp mechanism.
The function formatMessage simply uses the request and the exception to return a string with the error message.
formatMessage :: Maybe Request -> SomeException -> String
formatMessage Nothing exception = "Exception before request could be parsed: " ++ show exception
formatMessage (Just request) exception = "Exception " ++ show exception ++ " while handling request " ++ show request
The only piece left now is the recordUpdate function which allows to decorate with other attributes the default SentryRecord.
recordUpdate :: Maybe Request -> SomeException -> SentryRecord -> SentryRecord
recordUpdate Nothing exception record = record
recordUpdate (Just request) exception record = record
{ srCulprit = Just $ unpack $ rawPathInfo request
, srServerName = fmap unpack $ requestHeaderHost request
}
In this examples we set the raw path as the culprit and we use the Host header to populate the server name field.
You can try to run this code using the cookbook-sentry executable. You should obtain a MyException error in the console and, if you provided a valid Sentry DSN, you should also find your error in the Sentry interface.