It is not clear how to log all errors.
This is esp. important for isomorphic services.
@gajus I'm not sure what errors you want to log exactly, but if you want to log all GraphQL errors for all queries, you could start with a custom error-logging afterware.
You can also add a Redux reducer or middleware and catch Apollo events that have an errors
field and log those.
@gajus and perhaps anyone else who wants this, here an example of logging afterware that's working for me:
const logErrors = {
applyAfterware({ response }, next) {
if (!response.ok) {
response.clone().text().then(bodyText => {
console.error(`Network Error: ${response.status} (${response.statusText}) - ${bodyText}`);
next();
});
} else {
response.clone().json().then(({ errors }) => {
if (errors) {
console.error('GraphQL Errors:', errors.map(e => e.message));
}
next();
});
}
},
};
const networkInterface = createNetworkInterface({
uri: 'https://apiserver.com/graphql/',
});
networkInterface.useAfter([logErrors]);
Reopening to remind myself to document this
I wrote a similar implementation that opens the query in a graphiql:
import { print } from 'graphql-tag/printer';
var processedRequests = 0
const requests = {}
const requestIdHeader = 'x-req-id'
const storeQueriesMiddleware = {
applyMiddleware({ request, options }, next) {
const requestId = `r${(processedRequests++)}-${request.operationName}`
if (!options.headers) options.headers = {};
options.headers[requestIdHeader] = requestId
requests[requestId] = request
next()
},
};
const processGqlResponseErrors = (request, requestId) => {
const params = []
console.groupCollapsed(`processGqlResponseErrors(${requestId})`)
Object.keys(request).forEach((key) => {
const value = key == 'query' ? print(request[key]) : JSON.stringify(request[key])
console.debug(key);
console.debug(value)
params.push(`${key}=${encodeURIComponent(value)}`)
})
console.groupEnd(`processGqlResponseErrors(${requestId})`)
if (confirm(`Open failed GQL request: ${request.operationName} query in GraphiQL?`)) window.open(`${window.location.origin}/graphiql?${params.join('&')}`);
}
const processGQLErrors = {
applyAfterware({ response, options }, next) {
const requestId = options.headers[requestIdHeader]
const request = requests[requestId]
delete requests[requestId]
if (!response.ok) {
response.clone().text().then(bodyText => {
console.error(`Network Error: ${response.status} (${response.statusText}) - ${bodyText}`);
processGqlResponseErrors(request, requestId)
next();
});
} else {
response.clone().json().then(({ errors }) => {
if (errors) {
console.error('GraphQL Errors:', errors.map(e => e.message));
processGqlResponseErrors(request, requestId)
}
next();
});
}
},
};
networkInterface.use([storeQueriesMiddleware]);
networkInterface.useAfter([processGQLErrors])
How would you catch network errors (i.e., can't reach server). Afterware is too late?
Trying to follow:
https://github.com/apollostack/react-apollo/issues/345
https://github.com/apollostack/apollo-client/issues/657
https://github.com/apollostack/apollo-client/issues/891
https://github.com/apollostack/apollo-client/pull/950
But still can't see where the network error can be caught.
Do I have to implement my own HTTPFetchNetworkInterface to do this?
I'd like to be able to catch errors from HTTPFetchNetworkInterface.fetchFromRemoteEndpoint
Current workaround is:
window.addEventListener('unhandledrejection', (event) => { ... })
networkInterface.js?2b41:73 POST http://localhost:3000/graphql net::ERR_CONNECTION_REFUSED
HTTPFetchNetworkInterface.fetchFromRemoteEndpoint @ networkInterface.js?2b41:73
(anonymous) @ networkInterface.js?2b41:90
slight adjustment to @chaffeqa snippet for universal use...
npm i -D console-group
require('console-group').install()
var processedRequests = 0
const requests = {}
const requestIdHeader = 'x-req-id'
const storeQueriesMiddleware = {
applyMiddleware({ request, options }, next) {
const requestId = `r${(processedRequests++)}-${request.operationName}`
if (!options.headers) options.headers = {};
options.headers[requestIdHeader] = requestId
requests[requestId] = request
next()
},
};
const processGqlResponseErrors = (request, requestId) => {
const params = []
console.group(`processGqlResponseErrors(${requestId})`)
let value = 'Pood my pants again'
Object.keys(request).forEach((key) => {
if (key == 'query') {
value = print(request[key])
}
else {
const jsonResponseElement = JSON.stringify(request[key])
value = (jsonResponseElement.length > 300) ? jsonResponseElement.substr(0, 300) + ' .... ' : jsonResponseElement
}
console.log(key);
console.log(value)
params.push(`${key}=${encodeURIComponent(value)}`)
})
console.groupEnd(`processGqlResponseErrors(${requestId})`)
if (typeof confirm == "function") {
if (confirm(`Open failed GQL request: ${request.operationName} query in GraphiQL?`)) window.open(`${window.location.origin}/graphiql?${params.join('&')}`);
}
}
const processGQLErrors = {
applyAfterware({ response, options }, next) {
const requestId = options.headers[requestIdHeader]
const request = requests[requestId]
delete requests[requestId]
if (!response.ok) {
response.clone().text().then(bodyText => {
const theBodyText = (bodyText.length > 500) ? bodyText.substr(0, 500) + ' .... ' : bodyText
console.error(`Network Error: ${response.status} (${response.statusText}) - ${theBodyText}`);
processGqlResponseErrors(request, requestId)
next();
});
} else {
response.clone().json().then(({ errors }) => {
if (errors) {
console.error('GraphQL Errors:', errors.map(e => e.message));
processGqlResponseErrors(request, requestId)
}
next();
});
}
},
};
networkInterface.use([storeQueriesMiddleware]);
networkInterface.useAfter([processGQLErrors])
Documented what we used for logging, hopefully it helps!
We considered afterware, but it seemed weird that we would have to catch server returned errors (status codes 500, 404) and network errors (e.g. can't connect to server) in different places.
Recently, there were changes made to throw an error if you have a !response.ok
. Read more here
We made a decorator
We wrapped network interface and implemented the same interface of the networkInterface specified here.
const logErrors = networkInterface => ({
query: request => networkInterface.query(request).catch(e => console.error(e.message, e))
});
And if you wanted to make testing easier (so you could pass in console or an implementation of logger), you could curry it.
const logErrors = logger => networkInterface => ({
query: request => networkInterface.query(request).catch(e => logger.error(e.message, e))
});
We've found it useful in our use case, hopefully this helps!
When I implement any of these patterns on my observable queries, I continue to get an unhandled error every time I perform a new graphQL request (even an unreleated one). Any ideas?
+1 for add to the docs
I don't quite understand when is the afterWare called? My afterware is not triggered on certain network errors. I am using AWS API Gateway and when the session expires, API Gateway authorizer returns 401 status code. However the afterware doesn't get triggered.
I simulated a timeout on the server and the afterware doesn't get triggered either.
I am looking for a single place to catch and log all errors. And perhaps retry requests on timeout errors, for example. Or redirect user to login page on a 401 error. But I can't rely on the afterware if it's not going to be triggered on every scenario.
Can anyone confirm if this is the case please? Thank you
@ferdingler same thing happening for me.
@ferdingler @zackify Me too the same
Have been looking at establishing the correct patterns in our UI for getting server generated messages back the user. It seems odd to me that in the catch block of my query I get an error object with a general error on it and then have to use a utility with a promise in it to get the resulting graphQLErrors array which has the server-generated error messages.
Can anyone shed some light on if this is an intentional design or if its going to soon be adjusted?
@ferdingler @zackify I think the reason these errors don't get passed to the afterware is that afterware gets run inside a then
after fetch, and 401s throw, so the afterware doesn't get called.
@evanshauser is working on a new network interface which should be ready in the next week or two. If you provide him with some test cases, he could make sure that afterware gets called when those network errors happen.
In my case, the afterware logs all errors for me now. The problem is the red screen when you don't grab the error at the component level. Thats the big thing I need fixed :) Hope @evanshauser's solution supports this! Thanks for the hard work guys.
@zackify oh, ok, then the real problem is uncaught errors, right? Those propagate up and you have to catch them at the component level.
I am catching them in the afterware. They log to the console. Yet at the component level it is throwing that they are uncaught unless if I pull out this.props.error in every component. Which is what I don't want. I just want to have a global error catcher.
@ferdingler @zackify we published the first part of the future Apollo network stack with the release of apollo-fetch. It should solve your issue of afterware not getting access to the response status. There is an integration with Apollo Client in the docs. Please open an Issue or let me know if apollo-fetch doesn't hit your use case or you think of any improvements.
CC @jbaxleyiii @stubailo
This issue has been automatically marked as stale becuase it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions to Apollo Client!
This issue has been automatically closed because it has not had recent activity after being marked as stale. If you belive this issue is still a problem or should be reopened, please reopen it! Thank you for your contributions to Apollo Client!
@ferdingler @zackify @llorentegerman Use @choonkending's solution 馃憤 . useAfter
is an approximate solution. For instance, it isn't called on network fails and CORS issues.
You will be better of wrapping the query method, you even have access to the request. However, you don't have access to the response like with the useAfter
.
Most helpful comment
@gajus and perhaps anyone else who wants this, here an example of logging afterware that's working for me: