diff --git a/docs/source/features/errors.md b/docs/source/features/errors.md index a0cd4dedf34..77ebc1b33d6 100644 --- a/docs/source/features/errors.md +++ b/docs/source/features/errors.md @@ -118,8 +118,17 @@ new ApolloError(message, code, additionalProperties); ## Masking and logging errors +### For the client response + The Apollo server constructor accepts a `formatError` function that is run on each error passed back to the client. This can be used to mask errors as well as for logging. -This example demonstrates masking (or suppressing the stacktrace): + +> Note that while this changes the error which is sent to the client, it +> doesn't change the error which is sent to Apollo Engine. See the +> `rewriteError` function in the _For Apollo Engine reporting_ section below +> if this behavior is desired. + +This example demonstrates masking (or suppressing the stacktrace) which is +returned to the client: ```js line=4-10 const server = new ApolloServer({ @@ -138,3 +147,125 @@ server.listen().then(({ url }) => { console.log(`🚀 Server ready at ${url}`); }); ``` + +### For Apollo Engine reporting + +With the Apollo Platform, it's possible to observe error rates within Apollo +Engine, rather than simply logging them to the console. While all errors are +sent to Apollo Engine by default, depending on the severity of the error, it +may be desirable to not send particular errors which may be caused by a +user's actions. Alternatively, it may be necessary to modify or redact errors +before transmitting them. + +To account for these needs, a `rewriteError` function can be provided within +the `engine` settings to Apollo Server. At a high-level, the function will +receive the error to be reported (i.e. a `GraphQLError` or an `ApolloError`) +as the first argument. The function should then return a modified form of +that error (e.g. after changing the `err.message` to remove potentially +sensitive information), or should return an explicit `null` in order to avoid +reporting the error entirely. + +The following sections give some examples of various use-cases for `rewriteError`. + +#### Avoid reporting lower-severity predefined errors. + +If an application is utilizing the predefined errors noted above (e.g. +`AuthenticationError`, `ForbiddenError`, `UserInputError`, etc.), these can +be used with `rewriteError`. + +For example, if the current server is `throw`ing the `AuthenticationError` +when a mis-typed password is supplied, an implementor can avoid reporting +this to Apollo Engine by defining `rewriteError` as follows: + +```js line=5-15 +const { ApolloServer, AuthenticationError } = require("apollo-server"); +const server = new ApolloServer({ + typeDefs, // (Not defined in this example) + resolvers, // " " " " + engine: { + rewriteError(err) { + // Return `null` to avoid reporting `AuthenticationError`s + if (err instanceof AuthenticationError) { + return null; + } + + // All other errors will be reported. + return err; + } + }, +}); +``` + +This example configuration would ensure that any `AuthenticationError` which +was thrown within a resolver would only be reported to the client, and never +sent to Apollo Engine. All other errors would be transmitted to Apollo Engine +normally. + +#### Avoid reporting an error based on other properties. + +When generating an error (e.g. `new ApolloError("Failure!")`), the `message` +is the most common property (i.e. `err.message`, which is `Failure!` in this +case). However, any number of properties can be attached to the error (e.g. +adding a `code` property). These properties can be checked when determining +whether an error should be reported to Apollo Engine using the `rewriteError` +function as follows: + +```js line=5-16 +const { ApolloServer } = require("apollo-server"); +const server = new ApolloServer({ + typeDefs, // (Not defined in this example) + resolvers, // " " " " + engine: { + rewriteError(err) { + // Using a more stable, known error property (e.g. `err.code`) would be + // more defensive, however checking the `message` might serve most needs! + if (err.message && err.message.match(/^Known error message/) { + return null; + } + + // All other errors should still be reported! + return err; + } + }, +}); +``` + +This example configuration would ensure that any error which started with +`Known error message` was not transmitted to Apollo Engine, but all other +errors would continue to be sent. + +#### Redacting the error message. + +If it is necessary to change the error prior to reporting it to Apollo Engine +– for example, if there is personally identifiable information in the error +`message` — the `rewriteError` function can also help. + +Consider an example where the error contained a piece of information like +an API key (e.g. `throw new ApolloError("The x-api-key:12345 doesn't have +sufficient privileges.");`). + +While a best practice would suggest not including such information in the +error message itself, the `rewriteError` function could be used to make sure +it it's sent to Apollo Engine and potentially revealed outside its intended +scope: + +```js line=5-11 +const { ApolloServer } = require("apollo-server"); +const server = new ApolloServer({ + typeDefs, // (Not defined in this example) + resolvers, // " " " " + engine: { + rewriteError(err) { + // Make sure that a specific pattern is removed from all error messages. + err.message = err.message.replace(/x-api-key:[A-Z0-9-]+/, "REDACTED"); + return err; + } + }, +}); +``` + +In this case, the example error used above would be reported in Apollo Engine as: + +``` +The x-api-key:REDACTED doesn't have sufficient privileges. +```