Vinicius Ronconi
Vinicius Ronconi

Reputation: 181

How to integrate KeystoneJs with Sentry.io

I integrated projects on other platforms with Sentry before and it is quite simple. But at this time, I'm using KeystoneJs, which is created on the top of Express.

Sentry offers this tutorial to let Express users integrate their applications with Sentry: https://sentry.io/for/express/

But it is not clear to me how to apply it on a KeystoneJs project, since we can't just invoke keystone.use()

I know we have some hooks available on keystone, so I also tried this:

const Sentry = require('@sentry/node'); 
Sentry.init({ dsn: 'https://[email protected]/1301672' });
keystone.set('pre:routes', Sentry.Handlers.requestHandler());
keystone.set('pre:routes', Sentry.Handlers.errorHandler());

This lead to this error:

TypeError: next is not a function
    at sentryErrorMiddleware (/Users/ronconi/Workspaces/OfficePools/victoria-royals-api/src/node_modules/@sentry/node/src/handlers.ts:268:5)
    at createApp (/Users/ronconi/Workspaces/OfficePools/victoria-royals-api/src/node_modules/keystone/server/createApp.js:119:29)
    at Keystone.initExpressApp (/Users/ronconi/Workspaces/OfficePools/victoria-royals-api/src/node_modules/keystone/lib/core/initExpressApp.js:9:47)
    at Keystone.start (/Users/ronconi/Workspaces/OfficePools/victoria-royals-api/src/node_modules/keystone/lib/core/start.js:47:7)
    at Object.<anonymous> (/Users/ronconi/Workspaces/OfficePools/victoria-royals-api/src/keystone.js:69:10)
    at Module._compile (internal/modules/cjs/loader.js:689:30)
    at loader (/Users/ronconi/Workspaces/OfficePools/victoria-royals-api/src/node_modules/babel-register/lib/node.js:144:5)
    at Object.require.extensions.(anonymous function) [as .js] (/Users/ronconi/Workspaces/OfficePools/victoria-royals-api/src/node_modules/babel-register/lib/node.js:154:7)
    at Module.load (internal/modules/cjs/loader.js:599:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
    at Function.Module._load (internal/modules/cjs/loader.js:530:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:742:12)
    at Object.<anonymous> (/Users/ronconi/Workspaces/OfficePools/victoria-royals-api/src/node_modules/babel-cli/lib/_babel-node.js:154:22)
    at Module._compile (internal/modules/cjs/loader.js:689:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
    at Module.load (internal/modules/cjs/loader.js:599:32)

I'm wondering about the best way to integrate Keystone and Sentry, but I couldn't find it anywhere. If anyone made this before, please let me know!

Next thing I will try it add a separate express app to the project and link it with keystone, like described here: https://github.com/keystonejs/keystone/wiki/How-to-Add-Keystone-to-an-Already-Existing-Express-App

Since it is not exactly my case, I'm not sure if it is the best way to go or just find a way to use the regular express app.use() with KeystoneJs.

Upvotes: 3

Views: 415

Answers (2)

buzzb0x
buzzb0x

Reputation: 780

On Keystone 6, after adding the Sentry.init call, you'll have to add two things in order to log both GraphQL errors, and errors that could come from custom endpoints.

In your keystone.ts file:

export default config({
  // …
  server: {
    // …

    // Handle express errors:
    extendExpressApp: (app, createContext) => {
      // Must be placed before any other middleware.
      app.use(Sentry.Handler.requestHandler());

     // Your other custom middlewares and controllers…

     // Must be placed after every other controller.
     app.use(Sentry.Handlers.errorHandler();
    }

  // Handle GraphQL errors
  graphql: {
    appolloConfig: {
      plugins: [{
        // Taken from https://blog.sentry.io/2020/07/22/handling-graphql-errors-using-sentry/#reporting-errors-to-sentry-with-apollo-server-nodejs-typescript
        requestDidStart(_) {
          return {
            didEncounterErrors(ctx) {
              ctx.errors.map(error => {
                Sentry.withScope(scope => {
                  scope.setTag('kind', ctx.operation.operation);

                  scope.setExtra('query', ctx.request.query);
                  scope.setExtra('variables', ctx.request.variables);

                  if (error.path) {
                    scope.addBreadcrumb({
                      category: 'query-path',
                      message: error.path.join(' > '),
                      level: severityLevelFromString('debug')
                    });
                  }

                  Sentry.captureException(error);
                });
              });
            }
          }
        }
      }]
    }
  }
});

Upvotes: 0

Vinicius Ronconi
Vinicius Ronconi

Reputation: 181

Answering my own question. No need to add a new express app for this. All I had to do was use keystone.set() instead of app.use().

Only note: When working directly with Express, you can add several error handling middlewares while with keystone, I already had something like keystone.set('500', myCustomErrorHandlingMiddleware);

So I moved the code to use sentry.Handlers.errorHandler() to inside of it.

Upvotes: 1

Related Questions