こんにちは。エンジニアリング事業本部の@1010realです。 今年初めからスマートショッピングに入社して、既存サービスの管理画面のリプレースや運用・新規機能開発におけるフロントエンド開発を全般的に行っております。
今回はWebフロントエンド開発における本番環境のエラートラッキング及びそのバグフィックスについて、Sentryを用いて効率的に行う方法を紹介しようと思います。 内容としては、Webフロントエンドに限らず、バックエンドやアプリなど、Sentryが対応しているPlatformであれば、参考にできる内容となっています。
Sentryはオープンソースのエラートラッキングソフトウェアです。 ユーザはランタイムエラーが発生したタイミングでSentryに適切にログを送ることで、エラーの可視化と管理を行うことができます。 スマートショッピングではSaaS利用しています。
Sentry自体はかなり昔から存在していて、オープンソースと言うこともあり、試してみた系の記事はたくさん見つかります。事実、エラーをトラッキングするだけなら、ほんの数行のコードを設定するだけで可能です。 ただし、実際にサービスを運用していく中での活用事例はあまり見つけることができず、効率的にエラーのモニタリングと解決を行うためには、もう一歩踏み込んだ設定が必要だと感じている今日この頃です。 なので今回は、現時点での自分なりのベストプラクティスを紹介しようと思います。
Sentryにはじめてログインすると、管理画面のサイドメニューに「Setup Sentry」と言うメニューが表示され、Todoがリストアップされています。これを終わらせる=「ちゃんとセットアップできた」と定義しています。
導入手順もこれに沿って紹介していこうと思います(細かい部分は割愛します)
Sentryを利用するためにまずはアカウントを登録します。 https://sentry.io/welcome/ ※ちなみに今回の記事は、Teams以上のプランでの活用を前提としています。Freeプランで利用する場合には適宜設定をスキップしてください。
管理画面が表示できたら、「Settings」 -> 「Members」から、チームメンバーを招待しましょう。 その後、「Settings」 -> 「Teams」で新しいTeamを作り、メンバーを紐つけます。
「Projects」メニューからプロジェクトを追加します。 プラットフォームを選択し、プロジェクト名と先程のTeamを指定するだけです。 プロジェクトができたら、「Settings」 -> 「Projects」から該当のプロジェクトを選択し、「Client Keys (DSN)」を確認しておきます。 これがSDKがエラーイベントをこのプロジェクトに対して、送信する際のエンドポイントとなります。
クライアントにSDKをセットアップし、先ほどのDSNに対してエラーを送信するように設定します。 Nuxt.jsを使用している場合は、@nuxtjs/sentryのインストールとnuxt.config.jsへの設定のみです。
$ yarn add @nuxtjs/sentry
nuxt.config.js
...
modules: [
...
'@nuxtjs/sentry' // 追加
],
sentry: {
dsn: process.env.ENV === 'production'
? 'https://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@sentry.io/xxxxxxx'
: false // DSNを設定
},
他の主要なプラットフォームを使用している場合には、「Settings」 -> 「Projects」から該当のプロジェクトを選択し、「Error Tracking」を確認すると導入方法が見つけられると思います。 存在しない場合は、このサイトからも検索できます。
この設定を反映後、数分あるいは数時間後に、「Issues」内に、新たなIssueが追加されるはずです。
もし、1日経っても追加されない場合は、設定が間違っているか、あなたのサイトが全くエラーを起こさない完璧なサイト(あるいは握り潰している)かのいずれかでしょう。
ここまでで、Sentry-Setup内、以下項目は完了となります。
Sentryはこれまでの設定だけでも非常に多くの情報を開発者にもたらしてくれます。 OS, Browser, IP Address, Stack Trace, User Activity... ただ、それぞれのプラットフォームで管理しているユーザ情報は、別途ログに付与して送信してあげないとSentryは検知することができません。(勝手に収集されたら困りますよね。。) どんなユーザがどの程度影響を受けているかどうかも、Issueの重要度に関与してきますので、問題のない情報は追加してあげると良いと思います。
Sentry.configureScopeを使用して、エラーログにユーザ情報を付与することができます。 Nuxt.jsの場合は、以下のようになります。
// ユーザ認証直後の処理に追加
const user = { id: "xxx", email: "yyyyy@zzzzz.com" }
this.$sentry.configureScope((scope) => {
scope.setUser({ id: user.id, email: user.email })
})
※@nuxtjs/sentryを利用すると、this.$sentryでSentryインスタンスにアクセスできます。
これで、Sentry-Setupの項目は完了となります。
エラーの原因調査において、そのエラーがどのリリースに起因して起きているかを特定することも解決への近道です。 事前に、Sentryにリリースバージョンとその内容を登録しておいて、Sentryへのエラーログ送信時にリリースバージョンを付与してあげることで、どのバージョンでどんなエラーが起こったかを追跡できるようにすることができます。
また、リリースの内容にソースマップを紐づけることで、StackTraceがMinifyされたコードではなく、ソースマップ上の行数で表示できるようになります。 Minifyされたコードだと、結局どこが問題かわからないため、開発環境で再現するのに苦労すると思いますが、その手間がなくなり、ぱっと見で原因のコードが特定できて爆速で修正できるIssueが増えるので、それも行っておくと良いと思います。
Sentryへのリリース内容の登録は sentry-cli を利用して行うことができます。 コマンドラインからも実行できますが、CIに組み込むのが良いと思います。 スマートショッピングでは、github workflowを使ってCIを行っていますので、その前提で説明します。
※ちなみに、Nuxt単体であれば、sentry-cliを使わなくても、リリース管理は可能みたい(詳しく調べてない)ですが、他プラットフォームでも再利用できるように、今回はsentry-cliを利用してCIに組み込んでいます。
sentry-cliを叩くためのApiKeyを以下で発行します。 https://sentry.io/settings/account/api/auth-tokens/
Sentryをビジネスプラン以上で利用している場合は、「Setting」 -> 「General Setting」から組織名も確認しておきます。
1,2で確認した値をCI実行時の環境変数として定義します。 SENTRY_AUTH=[先程発行したApiKey] SENTRY_ORG=[組織名]
CI時にソースマップをSentryにアップロードしたいので、必要に応じてci用のビルドを定義します。
nuxt.config.js
...
build: {
...
extend(config, ctx) {
if (process.env.MODE === 'ci' && ctx.isClient) {
config.devtool = 'source-map'
}
}
},
...
package.json
"scripts": {
...
"build:ci": "VERSION=$VERSION ENV=production MODE=ci nuxt build",
...
※$VERSIONについては、追って説明します
nuxt.config.js
...
sentry: {
dsn: process.env.ENV === 'production'
? 'https://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@sentry.io/xxxxxxx'
: false
config: {
release: `xxxxxx@${process.env.VERSION}` // 追加。xxxxxxにはプロジェクト名を指定
}
},
...
package.json
...
"scripts": {
...
"start:prd": "VERSION=$VERSION ENV=production nuxt start",
...
リリース内容にコミット情報を付与するために、Sentryと開発対象のリポジトリを連携します。 「Organization Settings」 > 「Integrations」 から、Githubを連携します。 インストール後、「Configure」ボタンから、開発対象のリポジトリを追加します。
下準備はできたので、デプロイ時にリリース情報をSentryへ登録するジョブを作成していきます。
.github/workflows/xxxx.yml (本番環境へのデプロイ用定義)
...
sentry-releases-new:
needs: build
name: Create new releases
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 12.x
- name: Create new releases by sentry-cli
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
run: |
SENTRY_PROJECT_NAME="xxxxxx"
GIT_REF=$(git describe --always)
SENTRY_RELEASE_VERSION="${SENTRY_PROJECT_NAME}@${GIT_REF}"
# create sourcemaps
yarn install
yarn build:ci
# sentry releases integration
curl -sL https://sentry.io/get-cli/ | bash
sentry-cli releases new -p $SENTRY_PROJECT_NAME $SENTRY_RELEASE_VERSION
sentry-cli releases -p $SENTRY_PROJECT_NAME files $SENTRY_RELEASE_VERSION upload-sourcemaps .nuxt/dist/client --rewrite --url-prefix '~/_nuxt/'
sentry-cli releases set-commits --auto $SENTRY_RELEASE_VERSION
sentry-cli releases finalize $SENTRY_RELEASE_VERSION
sentry-cli releases deploys $SENTRY_RELEASE_VERSION new -e production
...
deploy:
needs: [build, sentry-releases-new]
内容について補足しておきます。
GIT_REF=$(git describe --always)
コミットハッシュをバージョンの一部としています。(タグでも良いと思います)
curl -sL https://sentry.io/get-cli/ | bash
sentry-cliのインストールsentry-cli releases new -p $SENTRY_PROJECT_NAME $SENTRY_RELEASE_VERSION
指定したプロジェクトに新たなリリース定義を作成しますsentry-cli releases deploys $SENTRYRELEASEVERSION new -e production 確定させたリリースが、正式にデプロイされたことを通知します。
あとは本番環境での実行時に、作成したリリース定義と同じVERSIONを渡して、起動してあげるようにしてください。
以上で設定は終わりです。設定が正常であれば、以下のような情報が確認できるようになります。
リリース毎にデプロイした日付やこのリリースに関連するIssueを確認できます。
詳細を見ると、リリース後どのくらいのエラーがトラッキングされているかをグラフで確認することができます。
また、含まれるコミットやコード差分、リソース及びソースマップを確認できます。
Issueについても、releaseタグが追加され、どのバージョンのソースで起きたエラーかがわかります。
また、StackTraceの表示も、OriginalとMinifiedを選択できるようになり、ダイレクトに問題が起こっている行数が確認できます。
また、同様のエラーがリリースバージョン毎にどのくらいの割合で起こっているかも表示されます。
エラーの起こっている行数が判明すれば、対応は容易です。また、そこまでわからなかったとしても、どのリリース以降起きているかを絞り込めれば、疑うべき差分が明確になり、作業時間は大幅に短縮できます。
これで、Sentry-Setupの項目は完了となります。
まずは、自分のよく見るツールに通知を送れるようにしましょう。Slackでも、DataDogでも良いと思います。 「Organization Settings」 > 「Integrations」 から連携しましょう。
次にアラートを送るルールを追加しましょう。 プロジェクトの「Settings」 > 「Alerts」 から任意のアラート設定を追加することができます。 新しいエラーの場合、一定期間に何回/何ユーザで発生したかなど、様々なルールが設定できます。
通知は多すぎると形骸化して見なくなりますし、とはいえクリティカルなものはすぐに確認したいものです。 適切なルールを設定するためには、何度か試行錯誤が必要になると思います。 個人的なおすすめは、まず下記を設定し、あとは微調整していくと良いと思います。 - An issue is first seen 初めて検知したエラー - An issue changes state from resolved to unresolved 修正したはずのエラーを再検知 - An issue is seen by more than {value} users in {interval} 期間内に一定以上のユーザに影響が出ているエラー (valueとintervalはサイトのPVやユーザ数による)
これで、Sentry-Setupの項目は完了となります。
Sentry-Setupには含まれていませんが、以下は有効にしておくと良いと思います。
プロジェクトの「Setting」 > 「General Setting」から有効にできます。
以下2点を心がけています。 - Issueのトリアージを意識する どんなエラーに対しても全て100%で向き合っていたら、おそらくいつまでたっても新規開発が進められません。 Issueを確認する際には必ず問題の重要性と影響範囲(ユーザ数・対象ブラウザ等)を把握し、対応の優先順位付けを行うようにします。 一人当たりにアサインするIssueは常時2つ以下としています。
実装にも寄りますが、あくまで例外処理なので外部環境に依存するApiリクエストのような処理はTry-Catchしておくべきだというのが一般的だと思います。 ただ、どんな理由で例外に入ったかはトラッキングしておくと、予期しないエラーが起きていることを知る手立てになるかもしれません。 ※特に400 Bad Requestなどは、フロントでのリクエストパラメータ生成が間違っており、ユーザが何かしらの操作をできなくなっている可能性が高いので、要注意です。
もちろんエラーをThrowしてあげれば、Sentryにログが送信されますが、ユーザに見せたくはないと思います。その場合は、Sentry.captureException
を使うと良いです。
async asyncData ({ params, $sentry }) {
try {
let { data } = await axios.get(`https://my-api/posts/${params.id}`)
return { title: data.title }
} catch (error) {
this.$sentry.captureException(error)
}
}
最近別のプロジェクトにも新たにSentryを導入したのですが、Issueがガンガン作られているので、より一層エラーのトリアージが大事だなと感じています。限られた人数の中でも、効率よく進めていきたいです。 また、Sentryに限らず、エラーのトラッキングがしっかりできていると、開発者が物怖じせずガンガンコードをリファクタしていけるので、こういう仕組みは大事だなと感じています。