CloudWatch RUM を使って NuxtJS アプリのクライアント側のエラーをモニタリングしてみた

2022.03.01

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

プロフィールビューアーサービス Proflly(プロフリー)の運用にて、エラーが発生している原因を調査することがあります。その際に再現性があるエラーの場合は、原因を突き止めやすいのですが、再現性がないエラーの場合、調査して原因を突き止めるのが難しい場合があります。 そういった場合に、クライアント側のエラー(Javascript のエラーなど)をモニタリングできると、エラー時の挙動がより調査しやすくなるので、CloudWatch RUM を実際に触って試してみました。

やってみること

  • CloudWatch RUM にアプリケーションモニターを作成
  • NuxtJS アプリ(Single Page Application)に Amazon CloudWatch RUM Web Client を組み込む
  • NuxtJS アプリでエラー(例外)を発生させる
  • エラー内容をアプリケーションモニターから確認

実行環境

  • Amazon CloudWatch RUM Web Client: 1.1.0
  • NuxtJS: 2.15.8
  • Node.js: 14.17.1

CloudWatch RUM にアプリケーションモニターを作成

エラー情報を収集するためには、CloudWatch RUM にアプリケーションモニターを作成する必要があります。アプリケーションモニターを作成すると、JavaScript コードスニペットが生成されるので、このスニペットをアプリケーションに組み込む必要があります。

CloudWatch から RUM を開き、アプリケーションモニターを追加 ボタンをクリックして、アプリケーションモニターの作成を開始します。 基本的に環境に合わせて設定していけば問題なさそうですが、主な設定値は以下の通り設定しました。

  • 詳細の指定
    • アプリケーションドメイン
      • アプリで利用するドメイン名を指定する
      • dev.classmethod.jp というドメイン名を利用したアプリの場合は、dev.classmethod.jp を指定するか、classmethod.jp を指定して、サブドメインを含める をチェックしておけば大丈夫そうです
  • RUM データ収集を設定する
    • JavaScript エラー
      • チェック
      • 今回 JavaScript のエラーをモニタリングしたいので、チェックを入れる
  • Cookie を許可する
    • チェック
      • チェック することで、ユーザーやセッションごとにエラー情報を集約してくれるようです
  • セッションサンプル
    • 100
  • 承諾
    • 新しい ID プールを作成する
      • CloudWatch RUM にデータを送信する際に認証とアクセス許可が必要となりますので、そのプロバイダを設定します。新しい ID プールを作成するを選択すると、Cognito ID プールが新規で作成され、設定されているロールを引き受けることにより PutRumEvents API を実行することが許可されるようです。既存の Cognito ID プールの利用することや、サードパーティのプロバイダーを設定することもできるようです。

各項目を設定し、作成すると JavaScript コードスニペット が生成されるので、コピーまたはダウンロードすることができます。(後からでもコピーまたはダウンロードすることができます) また、コードスニペットはアプリケーションモニターにて指定したパラメータを元に作成されていますが、Amazon CloudWatch RUM Web Client に指定するパラメータをカスタマイズすることができます。指定できるパラメータについてはこちらを参照ください。

NuxtJS アプリに Amazon CloudWatch RUM Web Client を組み込む

JavaScript コードスニペットを組み込むことで、Amazon CloudWatch RUM Web Client を CDN からインストールします。NuxtJS アプリのすべての画面に JavaScript コードスニペットを組み込みたいので、nuxt.config.jshead で読み込むように組み込んでみました。 組み込み方法は、アプリケーションの構成によって組み込みやすい方法でよいかと思いますが、body要素またはその他のscript要素より前に組み込む必要があるようです。

今回は、JavaScript コードスニペットをダウンロードし static/rumTracker.js として配置し他ファイルを読み込むようにしました。 なお、ダウンロードしたファイルの中身を確認すると、script タグが含まれていたので、今回のように JavaScript のファイルとして読み込む場合は、script タグを削除しておいたほうが良さそうです。

nuxt.config.js

export default {
  ...
  // Global page headers: https://go.nuxtjs.dev/config-head
  head: {
    ...
    link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }],
    script: [
      {src: '/rumTracker.js'}
    ]
  },
  ...
}

これで基本的に組み込みは良いのですが、NuxtJS のようにフレームワークを利用している場合、ハンドリングしていない JavaScript エラーをインターセプトしていることがあります。エラーの収集は、アプリケーションでハンドリングしていないエラーが収集対象となるので、このままだとエラーとしてデータが収集されません。 そこで、recordError コマンドを利用して、エラーデータを収集します。今回は、エラーをハンドリングするプラグインを作成して、その処理の中で recordError コマンドを実行するように組み込んでみました。

error-handler.ts

import Vue from 'vue'
declare function cwr(operation: string, payload: any): void

export default (context: any, inject: any) => {
  Vue.config.errorHandler = (err, vm, info) => {
    console.error(err)
    cwr('recordError', err)
    
    // エラーハンドリング処理
  }
}

nuxt.config.js

export default {
  ...
  plugins: [
    {src: '~/plugins/error-handler'}
  ],
  ...
}

NuxtJS アプリをデプロイし、エラー(例外)を発生させる

ここまででアプリケーションへの組み込みは、完了したので、実際にエラーを発生させる処理を実装して、デプロイします。今回は、S3 + CloudFront + Route 53 で独自ドメインにデプロイしました。なお、ドメインはアプリケーションモニターを作成した時に指定したドメインとしています。ここが合っていないと、正しく情報が収集されませんので注意が必要です。 デプロイが完了したら、実際にエラー(例外)を発生させてみます。

エラー内容をアプリケーションモニターから確認

エラーを記録したので、CloudWatch から確認してみます。反映まで最大で15分とドキュメントに記載がありましたが、実際に試してみるとおおよそ1,2分程度で確認することができました。

対象のエラーのセッションをたどっていくと、エラーのスタックトレースを確認することができます。

これくらいの情報がわかれば、クライアントでなにが起こっているのか?を判断するのに役立ちそうですね!

さいごに

今回は調査の際に、サーバー側のログだけでは、なかなか原因がつかめない場合などにクライアント側のエラー情報を確認したいと思い、調査してみました。実際に触ってみると、アプリケーションに組み込むのも簡単ですし、コストも収集する情報やサンプリングするレートである程度調整できるので、プロダクション環境でぜひ利用してみたいなぁと思いました。
いろいろなクライアントエラーの収集方法がある中の、手段の1つとして、どなたかの参考になれば幸いです。

参考