Help us understand the problem. What is going on with this article?

Sentry で Nuxt.js のエラー検知 + 環境変数の扱いに関する Tips

More than 1 year has passed since last update.

はじめに

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 連携や分析ができる有料プランもあります.

  • ここから新規登録
  • プロジェクトを新規作成 image.png

通知に必要な URL を発行

DSN というアプリケーションに設定する URL を確認します.
https://xxx:yyy@sentry.io/zzz という形式の URL が DSNで,設定に必要なのはこれだけです.

image.png
image.png

※ 注意
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 に次のような記述で設定します.

nuxt.config.js
module.exports = {
  // •••
  modules: [ '@nuxtjs/sentry' ],
  sentry: { dsn: 'https://xxx:yyy@sentry.io/zzz' }
}

エラー通知例

わざと例外が発生するページを用意してエラーを通知してみます.

pages/pageHasError.vue
<script>
export default {
  data() {
    // エラー
    console.log(this.user.name)
    return {}
  }
}
</script>

ページ遷移でエラーページに到達したとき,及び SSR でエラーページを表示したときに次のようにエラー通知されます.

image.png
image.png

  • 例外が発生したときの Stack Trace
  • ユーザー環境の情報 (IP アドレス,UA,URL)
  • ユーザーのブラウザ上での動作

などが確認できます.

image.png
image.png

SDK 情報から sentry.javascript.browser で送信されたわかります.SSR 時に発生した場合は sentry.javascript.node で送信されます.
image.png

実際に本番運用するときは,DSN を nuxt.config.js にハードコーディングするのは DSN が git 管理されることになり望ましくないでしょう.そこで環境変数で DSN を設定する方法を考えてみます.

DSN を環境変数で設定する(ビルド時・実行時)

nuxt.config.js
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 に通知されません.
image.png

このようにビルドする環境・実行する環境それぞれに 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
nuxt.config.js
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 と一緒に使うことを想定していないため,モジュールを自作しました.

👉 @tanakaworld/nuxt-sentry

$ npm i -S @tanakaworld/nuxt-sentry

nuxt.config.js に次のような記述で設定します.

nuxt.config.js
module.exports = {
  // •••
  modules: [ '@tanakaworld/nuxt-sentry' ]
}

このモジュールはサーバ上の環境変数で定義した SENTRY_DSN がブラウザでも使われます.

本家との違いは

  • plugin コードで $env.SENTRY_DSN を参照
  • Nuxt 旧バージョンの互換性を排除
  • オプションの生成方法を簡略化

でよりシンプルな構成にしています.

lib/module.js
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"]
    }
  ]);

  // •••
};
lib/plugin.template.js
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 は便利なモジュールですが,シークレットキーなどサーバー外には公開したくない値は扱わないように注意が必要です.

tanakaworld
A Software Engineer. Creator of proff.io
https://tanaka.world
mercari
フリマアプリ「メルカリ」を、グローバルで開発しています。
https://tech.mercari.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした