はじめに
Sentry は OSS のエラー検知プラットフォームです.様々な言語に対応した SDK が用意されており,例えば JavaScript モジュールを使うとブラウザ上で発生したエラーや Node 上のエラー通知が可能になります.
SPA でフロントエンドでの責務が多くなってきた昨今,エラーの検知は課題になりがちです.ユーザーの申告でしか気づけなかったバグや,特定のブラウザ・デバイスでしか発生しないエラーに気づけるようになります.
Nuxt.js アプリケーションと Sentry を組み合わせた本番運用で,考慮が必要になった Nuxt.js モジュールと環境変数の扱いに関して紹介したいと思います.
TL;DR;
- 本番環境のエラー検知のため Sentry を使うときの DSN 管理に困った
- DSN をサーバの環境変数として設定し,クライアント側には動的に渡したい
- nuxt-env は,Nuxt 本家の Setnry モジュールと一緒に使うことができない
- nuxt-env と一緒に扱える @tanakaworld/nuxt-sentry を実装した
動作確認用のサンプルプロジェクトはこちら
Sentry
登録
リアルタイムにエラー検知するだけなら無料で使うことができます.
Slack 連携や分析ができる有料プランもあります.
- ここから新規登録
- プロジェクトを新規作成
通知に必要な URL を発行
DSN というアプリケーションに設定する URL を確認します.
https://xxx:yyy@sentry.io/zzz
という形式の URL が DSNで,設定に必要なのはこれだけです.
※ 注意
configure-the-sdk に記載あるように,古いバージョンだと DSN に secret key を含んでいたようですが,最新の Sentry だとそれは Lagacy URL として deprecated 扱いになっています.本記事は最新バージョンの Sentry を前提として書きます.
Nuxt コミュニティ本家の Sentry モジュール
👉 nuxt-community/sentry-module
ブラウザ向けの @sentry/browser と,Node.js 向けの @sentry/node をラップしたモジュールです.
$ npm i -S @nuxtjs/sentry
nuxt.config.js に次のような記述で設定します.
module.exports = {
// •••
modules: [ '@nuxtjs/sentry' ],
sentry: { dsn: 'https://xxx:yyy@sentry.io/zzz' }
}
エラー通知例
わざと例外が発生するページを用意してエラーを通知してみます.
<script>
export default {
data() {
// エラー
console.log(this.user.name)
return {}
}
}
</script>
ページ遷移でエラーページに到達したとき,及び SSR でエラーページを表示したときに次のようにエラー通知されます.
- 例外が発生したときの Stack Trace
- ユーザー環境の情報 (IP アドレス,UA,URL)
- ユーザーのブラウザ上での動作
などが確認できます.
SDK 情報から sentry.javascript.browser
で送信されたわかります.SSR 時に発生した場合は sentry.javascript.node
で送信されます.
実際に本番運用するときは,DSN を nuxt.config.js にハードコーディングするのは DSN が git 管理されることになり望ましくないでしょう.そこで環境変数で DSN を設定する方法を考えてみます.
DSN を環境変数で設定する(ビルド時・実行時)
module.exports = {
// •••
modules: [ '@nuxtjs/sentry' ],
sentry: { dsn: process.env.SENTRY_DSN }
}
// or
module.exports = {
// •••
modules: [ '@nuxtjs/sentry' ]
// "process.env.SENTRY_DSN" がデフォルトで設定されるので,明示的に記述しなくても OK
// https://github.com/nuxt-community/sentry-module#dsn
}
# ビルド
$ SENTRY_DSN=https://xxx:yyy@sentry.io/zzz npm run build
# 実行
$ SENTRY_DSN=https://xxx:yyy@sentry.io/zzz npm run start
これでハードコーディングするときと同じように動作させることができます.
npm run start
時にも SENTRY_DSN
を設定する必要があるのは,ブラウザで実行されるコードはビルド時の環境変数を用いて値が直接バンドルに埋め込まれ,サーバー(node) 上で実行されるコードは実行時の環境変数が使われるためです.
仮に SENTRY_DSN
を指定せずに npm run start
と実行した場合,次のような info ログが表示され,SSR 時には Sentry に通知されません.
このようにビルドする環境・実行する環境それぞれに SENTRY_DSN
を設定する必要があります.それぞれの環境の場合は DSN の二重管理や引き回しが発生し煩雑になりそうです.
管理しやすくするために,実行時にのみだけ設定する方法を考えます.
DSN を環境変数で設定する(実行時のみ)
# ビルド
$ npm run build
# 実行
$ SENTRY_DSN=https://xxx:yyy@sentry.io/zzz npm run start
実行時にのみ指定する場合は,ビルド時にその環境変数は参照できません.
その結果, SSR 時のエラーは Sentry に通知されるが,ブラウザ のエラーは Sentry に通知されないという状態になります.
コードが webpack でコンパイルされると、 process.env.your_var と記述されたすべての箇所が、定義した値に置き換えられます。
と env プロパティ に解説がある通り,nuxt build
はブラウザでも動くことを想定してビルドされた bundle を生成するので,当然ながらサーバのランタイム環境変数を参照できないためです.
この問題を解決するために,実行時の環境変数をブラウザに動的に渡す方法を考えます.
nuxt-env
👉 nuxt-env
これを使うと,実行時の環境変数をブラウザ上で参照できるようになります.
$ npm i -S nuxt-env
modules: [
['nuxt-env', {
keys: ['SENTRY_DSN']
}]
]
export default {
computed: {
testValue () { return this.$env.SENTRY_DSN }
}
}
export default {
asyncData ({ app }) {
console.log(app.$env.SENTRY_DSN)
}
}
$env
経由で参照できるようになります.
仕組みは単純で,サーバーランタイムの process.env
から nuxt.config.js で指定したキー の値をオブジェクトにマッピングして,$env
として inject しているだけです.
@tanakaworld/nuxt-sentry
本家の sentry-module は nuxt-env と一緒に使うことを想定していないため,モジュールを自作しました.
$ npm i -S @tanakaworld/nuxt-sentry
nuxt.config.js に次のような記述で設定します.
module.exports = {
// •••
modules: [ '@tanakaworld/nuxt-sentry' ]
}
このモジュールはサーバ上の環境変数で定義した SENTRY_DSN
がブラウザでも使われます.
本家との違いは
- plugin コードで
$env.SENTRY_DSN
を参照 - Nuxt 旧バージョンの互換性を排除
- オプションの生成方法を簡略化
でよりシンプルな構成にしています.
const Sentry = require("@sentry/node");
const path = require("path");
const logger = require("consola").withScope("nuxt:sentry");
module.exports = function Module(moduleOptions) {
// •••
// Setup sentry
Sentry.init(options);
// Register the client plugin
this.addPlugin({
src: path.resolve(__dirname, "plugin.template.js"),
fileName: "nuxt-sentry-client.js",
ssr: false,
options
});
// NOTE: nuxt-sentry-client.js is depends on nuxt-env.
this.requireModule([
"nuxt-env",
{
keys: ["SENTRY_DISABLED", "SENTRY_DSN", "SENTRY_ENVIRONMENT", "SENTRY_RELEASE"]
}
]);
// •••
};
import Vue from 'vue'
import * as Sentry from '@sentry/browser'
export default function (context, inject) {
// •••
// Set DSN via nuxt-env if it's exists
if (context.app.$env.SENTRY_DSN) {
opts.dsn = context.app.$env.SENTRY_DSN;
}
Sentry.init(opts);
// Inject Sentry to the context as $sentry
context.$sentry = Sentry;
inject('sentry', Sentry);
}
まとめ
@tanakaworld/nuxt-sentry によって SENTRY_DSN
が一元管理できるようになりました.
nuxt-env は便利なモジュールですが,シークレットキーなどサーバー外には公開したくない値は扱わないように注意が必要です.