Closed4

ApolloServerを使ったGraphQLアプリケーションにエラー検知ツールを導入

はやぶさはやぶさ

背景

サーバー側のGraphQLアプリケーションにエラー検知ツールを導入したい
ApolloServer + node.js + TypeScript という構成
なるべく無料で済ませたい

はやぶさはやぶさ

Sentry

https://sentry.io/pricing/

無料枠の懸念点

  • SlackのIntegrationがない
    • webhookのintegrationを試したが以下のようなエラーが出て動かず
    • https://webhook.site/ に送ってみた感じwebhook自体は動いているがslackが受け付ける形式ではなかったのか
    • そもそもwebhookのitengrationはlegacyっぽいので率先して使いたくないので調査終了

  • メールの通知はある
    • メールだと色々ごちゃごちゃするのでSlackが良い
    • 特定の件名だったらSlackに通知みたいな仕組み作れそうだが面倒なのでやってない

導入方法

https://blog.sentry.io/2020/07/22/handling-graphql-errors-using-sentry に準拠

npm install @sentry/node @sentry/tracing
import * as Sentry from '@sentry/node';
import {ApolloError} from 'apollo-server';
import {ApolloServerPlugin} from 'apollo-server-plugin-base';

Sentry.init({
  // Set tracesSampleRate to 1.0 to capture 100%
  // of transactions for performance monitoring.
  // We recommend adjusting this value in production
  tracesSampleRate: 1.0,
});

export const sentryPlugin = (): ApolloServerPlugin => {
  return {
    async requestDidStart(_) {
      /* Within this returned object, define functions that respond
           to request-specific lifecycle events. */
      return {
        async didEncounterErrors(ctx) {
          // If we couldn't parse the operation, don't
          // do anything here
          if (!ctx.operation) {
            return;
          }
          for (const err of ctx.errors) {
            // Only report internal server errors,
            // all errors extending ApolloError should be user-facing
            if (err instanceof ApolloError) {
              continue;
            }
            // Add scoped report details and send to Sentry
            Sentry.withScope((scope) => {
              // Annotate whether failing operation was query/mutation/subscription
              scope.setTag('kind', ctx.operation?.operation);
              // Log query and variables as extras
              // (make sure to strip out sensitive data!)
              scope.setExtra('query', ctx.request.query);
              scope.setExtra('variables', ctx.request.variables);
              if (err.path) {
                // We can also add the path as breadcrumb
                scope.addBreadcrumb({
                  category: 'query-path',
                  message: err.path.join(' > '),
                  level: Sentry.Severity.Debug,
                });
              }
              Sentry.captureException(err);
            });
          }
        },
      };
    },
  };
};

export const server = new ApolloServer({
  schema,
  context,
  plugins: [sentryPlugin],
});
はやぶさはやぶさ

Bugsnag

https://www.bugsnag.com/

無料プラン

250エラー/1d
slack連携あり

導入方法

上記のSentryの例をBugsnagに書き換えた

export const bugsnagPlugin = (): ApolloServerPlugin => {
  return {
    async requestDidStart(_) {
      /* Within this returned object, define functions that respond
           to request-specific lifecycle events. */
      return {
        async didEncounterErrors(ctx) {
          log.error('error!!');
          // If we couldn't parse the operation, don't
          // do anything here
          if (!ctx.operation) {
            return;
          }
          for (const err of ctx.errors) {
            // Only report internal server errors,
            // all errors extending ApolloError should be user-facing
            if (err instanceof ApolloError) {
              continue;
            }

            Bugsnag.notify(err, (event) => {
              event.addMetadata('graphql', {
                operation: ctx.operation?.operation,
                query: ctx.request.query,
                variables: ctx.request.variables,
                errorPath: err.path?.join(' > '),
              });
       
              // contextにuserIdを詰めている場合は以下のような感じで取得できる
              // ここで言うcontextとはhttps://www.apollographql.com/docs/apollo-server/data/resolvers/#the-context-argument を指す
              event.addMetadata('user', {
                fuid: ctx.context.useId,
              });
            });
          }
        },
      };
    },
  };
};

採用しなかった導入方法

https://www.npmjs.com/package/apollo-server-plugin-bugsnag を使う

採用しなかった理由

  1. ユーザー由来のエラーの場合はBugsnagに送らないといった制御が出来なそうだった
  2. 採用しなくても上記の導入方法のように書けば十分シンプルなコードで動くため
はやぶさはやぶさ

最終的に採用したエラー検知ツール

Bugsnagにした

理由

無料枠でslack integration がついてくるため

このスクラップは2022/01/21にクローズされました