You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I'm trying to centralize my exception reporting with react-relay-network-modern and haven't found a very good way to do so yet within the confines of either the existing middleware or providing my own custom middleware. In my case, I'm looking to report GraphQL errors to Sentry, but any exception reporting or log aggregation service would run into the same issue. I'm deliberately not looking to add logging code to every QueryRenderer because it's too easy to miss a case and any change in logging requires updating it in N places.
There are two basic error classes I'd like to capture: fetch errors and GraphQL responses that come back with an errors field. As far as I can tell, the only way to centrally handle the fetch errors is with custom middleware:
That part works fine, although I also get errors about requests being canceled if a user goes to a different page while the query is still running. So a real solution will require a bit more filtering.
Logging the errors field, however, is proving to be a lot more complicated. It exists as a field on the response object that's later turned into a RRNLRequestError. The conversion to an error appears to happen outside the middleware stack, so the catch statement from my custom middleware doesn't catch it.
The errors field will populate on the response object, so I could do something like:
next => async req => {
Sentry.setTag('graphql_query', req.getID())
try {
const res = await next(req)
if (res.errors) {
Sentry.captureException(res.errors)
}
return res
} catch (e) {
Sentry.captureException(e)
throw e
}
}
But, res.errors isn't really an exception, so that makes the logging in Sentry a bit weird. Since errors is typed as any and has no formatting functions that I could find, trying to treat it as a string and use Sentry.captureMessage(res.errors.toString()) doesn't produce useful output.
Another option is using the errorMiddleware, like so:
I didn't really want to split the error handling up, but I could live with that. The real problem with this option is the message argument really expects to be used with console. The docs mention the logger value is "log function (default: console.error.bind(console))", but if you use anything other than console you end up with a bunch of noisy formatting codes (%c) with no way to disable them. However, at least you have an otherwise nicely formatted message.
It'd be nice if I could invoke that formatter code from my custom middleware and handle logging of res.errors, but that formatting code is private to errorMiddleware.
The final option is to just handle RRNLRequestError in every QueryRenderer. I don't really like this option because it's easy to miss a logging statement and it litters the exception handling code around. But, with this approach you get a nicely formatted error message and a stack trace. Perhaps exposing formatGraphQLErrors and createRequestError is the best option?
I'm happy to provide a PR with documentation enhancement. I just don't know what the best way to centralize exception handling currently is. Any guidance would be much appreciated.
The text was updated successfully, but these errors were encountered:
exportconstapolloServerErrorsMiddleware=(): Middleware=>{return(next)=>async(req)=>{constres=awaitnext(req);// @ts-ignoreif(res&&res.errors&&Array.isArray(res.errors)&&res.errors.length){// @ts-ignoreres.errors.forEach(({ extensions })=>{if(extensions&&extensions.code){if(extensions.code==="UNAUTHENTICATED"){// @ts-ignoreres.status=401;}if(extensions.code==="FORBIDDEN"){// @ts-ignoreres.status=403;}}});// @ts-ignoreif(res.status===401||res.status===403){console.error(res);Sentry.captureException(newError());// This will force new token down the line, the error might be fucked thoreturnPromise.reject({ res });// eslint-disable-line prefer-promise-reject-errors}console.error(res.errors[0]);Sentry.captureException(res.errors[0]);returnPromise.reject(res.errors[0]);// eslint-disable-line prefer-promise-reject-errors}returnres;};};
on mutation error, error.message will actually contain the apollo server code, without the formatting fluff
I'm trying to centralize my exception reporting with react-relay-network-modern and haven't found a very good way to do so yet within the confines of either the existing middleware or providing my own custom middleware. In my case, I'm looking to report GraphQL errors to Sentry, but any exception reporting or log aggregation service would run into the same issue. I'm deliberately not looking to add logging code to every
QueryRenderer
because it's too easy to miss a case and any change in logging requires updating it in N places.There are two basic error classes I'd like to capture:
fetch
errors and GraphQL responses that come back with anerrors
field. As far as I can tell, the only way to centrally handle thefetch
errors is with custom middleware:That part works fine, although I also get errors about requests being canceled if a user goes to a different page while the query is still running. So a real solution will require a bit more filtering.
Logging the
errors
field, however, is proving to be a lot more complicated. It exists as a field on the response object that's later turned into aRRNLRequestError
. The conversion to an error appears to happen outside the middleware stack, so thecatch
statement from my custom middleware doesn't catch it.The
errors
field will populate on the response object, so I could do something like:But,
res.errors
isn't really an exception, so that makes the logging in Sentry a bit weird. Sinceerrors
is typed asany
and has no formatting functions that I could find, trying to treat it as a string and useSentry.captureMessage(res.errors.toString())
doesn't produce useful output.Another option is using the
errorMiddleware
, like so:I didn't really want to split the error handling up, but I could live with that. The real problem with this option is the
message
argument really expects to be used withconsole
. The docs mention thelogger
value is "log function (default:console.error.bind(console)
)", but if you use anything other thanconsole
you end up with a bunch of noisy formatting codes (%c
) with no way to disable them. However, at least you have an otherwise nicely formatted message.It'd be nice if I could invoke that formatter code from my custom middleware and handle logging of
res.errors
, but that formatting code is private toerrorMiddleware
.The final option is to just handle
RRNLRequestError
in everyQueryRenderer
. I don't really like this option because it's easy to miss a logging statement and it litters the exception handling code around. But, with this approach you get a nicely formatted error message and a stack trace. Perhaps exposingformatGraphQLErrors
andcreateRequestError
is the best option?I'm happy to provide a PR with documentation enhancement. I just don't know what the best way to centralize exception handling currently is. Any guidance would be much appreciated.
The text was updated successfully, but these errors were encountered: