SentryのIssueをGraphQLオペレーションごとにグルーピングする
🌼 はじめに
最近 Sentry に飛んでくる Issue をよく見てますが、500エラー(Internal Server Error)同士にグルーピングされてしまう問題で結構悩んでました。
例えば画像アップロードの500エラーが飛んできて、それを resolve する前にユーザー登録の500エラーが飛んできたらその2つがグルーピングされてしまうということです。
同じ500エラーでも違うオペレーションだったら違うIssueにしたいのに、グルーピングされちゃうから確認しづらい!ということで頑張ってグルーピング設定した話をやりたいと思います。
※Apollo Client を使ってます
1. Sentry は fingerprint を基準にグルーピングする
グルーピングを設定するためにはまず Sentry が何を基準にグルーピングしているのかを把握する必要があります。結論から言うと、Sentry は fingerprint を基準に Issue をグルーピングします。
では fingerprint ってなに?ということですが、それに関しては公式サイトで説明しています。
A fingerprint is a way to uniquely identify an event. Sentry sets a fingerprint for you by default, using built-in grouping algorithms based on information such as a stack trace, exception type, and message. Events with the same fingerprint are grouped together.
fingerprint はイベントを一意に識別する方法で、同じ fingerprint を持つイベントは一緒にグルーピングされます。
Sentry はデフォルトで fingerprint を設定し、stack trace、 exception type、メッセージなどの情報に基づいた組み込みのグルーピングアルゴリズムを使用します。
要はデフォルトで Sentry がよしなにグルーピングしてくれてるから、特に設定しなくても Issue がグルーピングされるということです。
これは便利ですが、そのよしなに任せて今回のような問題が出てきてるので、これをある程度カスタムしたいと思います。
2. fingerprint をカスタムする方法
fingerprint をカスタムする方法については、公式サイトで Basic Sample を公開しています。
function makeRequest(method, path, options) {
return fetch(method, path, options).catch(function (err) {
Sentry.withScope(function (scope) {
// group errors together based on their request and response
scope.setFingerprint([method, path, String(err.statusCode)]);
Sentry.captureException(err);
});
});
}
withScope
は新しいスコープを作成し、その中で与えられた操作を実行するメソッドです。そのスコープは操作が終了するか、throw されると自動的に削除されます。
そのコールバック関数の引数である scope
は色んなメソッドを持っていて、それを使うと追加情報を送ることができます。setFingerprint
もその一つです。
setFingerprint
は該当スコープの fingerprint をカスタムします。
上記の例だと method
, path
, err.statusCode
を fingerprint として設定していますね。つまり、この3つの情報を基準にグルーピングするように指定してるということです。
ではこのメソッドを活用してグルーピングを設定してみましょう。
3. Apollo Client の Link を使ってオペレーションごとにグルーピングする
目的としては GraphQL オペレーションごとにグルーピングしたいです。そのために、Apollo Client の Link を活用します。
Linkを使うと Apollo Client と GraphQL サーバー間のデータの流れをカスタマイズできます。その中でも onError
はサーバのレスポンスにエラーがないかどうかをチェックするので、今回触っていきます。
Apollo Client の Link について、詳しくは公式サイトをご参考ください。
では公式に従って、graphQLErrors
の場合に setFingerprint
を入れます。
onError(({ graphQLErrors, networkError, operation }) => {
if (graphQLErrors) {
for (let err of graphQLErrors) {
Sentry.withScope((scope) => {
// オペレーション名を fingerprint として設定する
scope.setFingerprint([operation.operationName]);
Sentry.captureException(new Error(err.message), { level: "fatal" });
});
}
}
if (networkError) {
// ...
}
});
operation
はエラーが発生した GraphQL オペレーションの詳細を提供するオプションで、ここからオペレーション名(operationName
)が取得できます。これを setFingerprint
の引数の配列に渡せばOK!
…かもしれませんが、実は1つ注意点があります。
setFingerprint
は既存の fingerprint に指定した値を追加するものではなくて、上書きします。
つまり今のままだた Sentry 側でよしなにやってくれてたデフォルトのグルーピング設定は失われて、オペレーション名だけでグルーピングする設定になってしまいます。
できればデフォルトのグルーピング設定にオペレーション名を追加したいです。
そのためには {{ default }}
という変数を引数の配列に追加する必要があります。
onError(({ graphQLErrors, networkError, operation }) => {
if (graphQLErrors) {
for (let err of graphQLErrors) {
Sentry.withScope((scope) => {
- scope.setFingerprint([operation.operationName]);
+ scope.setFingerprint(["{{ default }}", operation.operationName]);
Sentry.captureException(new Error(err.message), { level: "fatal" });
});
}
}
if (networkError) {
// ...
}
});
setFingerprint
の引数の型は string[]
なので、 {{ default }}
をダブルクォーテーションで囲んであげましょう。
これで既存の fingerprint にオペレーション名を追加できました👏
4. 設定した fingerprint が効いてるかどうか確認する
設定まで完了したらこれがちゃんと効いてるかどうか確認する必要があります。その確認は Sentry のウェブ画面で行います。
Sentry の Issue 詳細画面にアクセスすると、1番下に 「Event Grouping Information 」 というフィールドがあります。その右側に 「Show Detail」 をクリックしたらグルーピング情報が確認できます。
この記事でグルーピング情報確認方法を紹介してるので、ぜひご参考ください。
もし setFingerprint
がちゃんと効いてるなら、グルーピング情報の 「Fingerprint values」 というフィールドに以下のような値が入ってるはずです。
[
{{ default }} ,
オペレーション名
]
ここまで確認できたらもうばっちりでしょう!
🌷 終わり
余談ですが、このタスクで1番の難問は500エラーを好きなタイミングに再現させることでした( ´∀`)
確認のためにバンバンエラー飛ばしたかったので、こないだテックリードさんに教えてもらった charles というツールを使いました。
charles を使うと通信をキャッチしてリクエスト・レスポンスに手を加えることができるので、レスポンスを500エラーのものに上書きして再現しました(超強引)。
便利ツールなのでぜひ使いましょう。設定はこちらを参考にさせていただきました。
Discussion