Web VitalsとJavaScript Errorの可視化

こんにちは、@watilde です。Amplifyの開発者体験体験の向上をすべく、ツイートのウォッチやGitHubでの反応などしています。もう去年のことですが、最近はcliの改善としてcreate-react-appのようにinitの実行時にREADMEの生成を行うPRなど作ったりしてます。参考: aws-amplify/amplify-cli#5808

この記事は英語で書いた Improve UX by observability in front-end with Amplify and QuickSight を自分で日本語に意訳してみたものです。Node学園 35時限目 オンライントライアル でも同様の内容を発表予定です。


JavaScriptのエラー例

JavaScriptは100%動いているのか

私達の作るWebアプリ・Webサイトが様々なデバイスで100%動作しているかは、実態を観測しない限り断言できません。ユーザーにJavaScriptのエラーが表示され、開発者が発生している問題を詳細に理解したいときに、エラーなどについて詳細を可視化できるAmazon QuickSight(以下QuickSight)の活用方法を最近学びました。また、筆者も開発に参加しているAWS Amplify(以下Amplify)はJavaScript開発者にとってAWSを使い始めるのに丁度よい入口に感じています。ここでは、2つのサービスを主に使用してObservabilityをフロントエンド開発に簡単に導入する方法を紹介します。

要旨

Webアプリ・サイトの開発におけるObservabilityは、ユーザー体験(UX)の低下がいつどこで発生するかを検出し、開発者が改善にかかる時間を短縮するのに役立つ原因を把握することです。 構築をするには下記の3つのステップに区切ることができます。

  1. ログを集める
  2. ログの詳細を可視化する
  3. ログの詳細から、改善に必要な意味合いだしを行う

最後の意味合だしを行うステップでは、情報を下記の3つのレベルに分類します。

  1. エラー:空白ページの原因
  2. 警告:50ミリ秒を超えるハングアウト
  3. その他:デバッグなどの情報

この記事では、上記のステップを元に、AmplifyとQuickSightを活用したフロントエンドにおけるObservability向上の基盤を実装する方法を紹介しています。Observabilityの獲得により、フロントエンド開発者が短い時間でUXの向上を行うことがやりやすくなる、と結論づけています。

コード例はこちら
https://github.com/watilde/amplify-observability-example

フロントエンドにおけるObservabilityとは

ソフトウェア開発におけるObservabilityは、文脈に応じて定義が変わる用語です。 John Porcaroは、2020年にhumioのブログにて「(re)refine observability」という記事を公開し、Observabilityの定義として以下のステートメントを紹介しました。

Observabilityとは、システムが提供するデータからシステムの内部状態を理解することです。データを調べ、何が起こったのか、なぜ起こったのか、の質問に答えられる状態を指します。

https://www.humio.com/whats-new/blog/observability-redefined

実際のプロダクト開発、特にフロントエンドでは、良いUXを提供することが多くの場合ミッションとして設定されています。上記のステートメントを、フロントエンド開発における「これの達成によりより」まで続けるならば、下記のようになるでしょう。

これの達成により、データに基づいて何を改善すべきかを推測し、より良いエクスペリエンスを短時間で提供できるようになります。

https://watilde.medium.com/improve-ux-by-observability-in-front-end-with-amplify-and-quicksight-e7083ec1913b

ソフトウェアの現在の状態を理解し、短時間で解決策を考え出すにはデータの意味合い出しをサポートするような情報を視覚化する必要があります。Syslogのログレベルは、ログを分類するためのベストプラクティスとしてよく引き合いに出されます。

Syslog severity level — Wikipedia

JavaScriptの世界においては、情報を出力するためのコンソール関数があります。ログレベルは標準でerror, warn, infoの3つのレベルがあります。 それぞれ開発者の観点で、errorは発生したエラー情報を出力、warnは規約違反などの情報を出力、infoはデバッグ用の情報を表示するのに使われています。

ChromeのDevToolsで表示されるconsoleのログレベル

では、開発者視点ではなく利用者視点でどのようなログレベルが必要でしょうか。SyslogログレベルとJavaScriptのログレベルを組み合わせると、下記のように分類することができます。

  • error: ページが壊れるような緊急度の高い問題
  • warn: UXの低下を引き起こす問題
  • info: UXに影響を与えない情報

フロントエンドでこれらを観測する方法は次のとおりです。最初の項目「error」の場合、エラーはonerrorおよびonunhandledrejectionによってキャッチできます。 2番目の項目「warn」では、2021年5月からSEOを改善するメトリックとしても使われる予定であるWeb Vitalsを活用できます。3番目の項目「info」は主にデバッグ目的であり、UXにも影響しないため、今回は深堀りしません。

Observability向上を行うサービス

Observabilityが世に浸透にしてからしばらく経ちますが、QuickSightは、AWSを使用している際に情報の可視化を行うのによく使われているサービスです。 公式の説明文では、「スケーラブルでサーバーレス、組み込み可能な、機械学習を活用したクラウド向けのビジネスインテリジェンス (BI) 」として紹介されています。

Amazon QuickSight

Amazon QuickSight は、スケーラブルでサーバーレス、組み込み可能な、機械学習を活用したクラウド向けのビジネスインテリジェンス (BI) サービスです。QuickSight では、機械学習を利用したインサイトを含むインタラクティブな BI ダッシュボードを簡単に作成し、公開できます。

Amazon QuickSight
#1 QuickSight 101

Amplifyを使ってWebアプリを構築し、QuickSightと連携

AWSには、モバイルおよびウェブアプリケーションの開発を加速するための一連のツール(OSSライブラリ、ヘッドレスCMS、管理画面)とサービス(Webアプリ・サイトのホスティング)で構成されるAmplifyと呼ばれるサービスがあります。

AWS Amplify

Amplifyはストリームデータを送ることが可能なAmazon Kinesis Data Streams(以下Kinesis Data Streams)をサポートしており、複雑な設定なしでフロントエンドからQuickSightにログを届けられます。 6つのステップで実際にログを可視化する方法を見てみましょう。

1. 前提となるソフトウェアの設定

Amplifyの公式ドキュメントには、前提条件となるソフトウェアのインストール等を紹介するチュートリアルがあります: URLNader Dabitによる動画版もあるので、併せて見てみてください。

Installing & Configuring the AWS Amplify CLI

2. Reactアプリを作成し、Kinesis Data Streamsと繋げる

AmplifyはKinesis Data Streamsをサポートしています。 amplify add analysisを実行するだけで、Kinesis Data Streamsと繋ぐことができます。

$ npx create-react-app example
$ cd example
$ amplify init

# Put amplifyobservability as name

$ amplify add analytics

# Select Amazon Kinesis Stream
# Stream name is "amplifyobservabilityKinesis-dev"

$ amplify push

3. Kinesis Data Streamsを介して情報を送信

どのファイルがどのデバイスでエラーを起こすか、詳細を知るためにStackTrace.JSを使用してエラーメッセージを標準化し、UAParser.jsを使用してユーザーエージェント情報を標準化します。 次に、「monitorErrors.js」と「monitorWebVitals.js」というファイルを作成しましょう。

$ npm install stacktrace-js ua-parser-js
$ touch src/monitorErrors.js
$ touch src/monitorWebVitals.js

monitorWebVitals.jsでは、Kinesis Data Streamsとの接続情報を設定し、標準化されたエラーとユーザーエージェントを送信します。

// src/monitorErrors.js
import * as StackTrace from 'stacktrace-js';
import UAParser from 'ua-parser-js';
import { Analytics, AWSKinesisProvider } from 'aws-amplify';
import awsconfig from './aws-exports';

Analytics.configure({
  AWSKinesis: {
    region: awsconfig.aws_project_region,
    bufferSize: 1000,
    flushSize: 100,
    flushInterval: 5000, 
    resendLimit: 5
  }
});

Analytics.addPluggable(new AWSKinesisProvider());

const parser = new UAParser();

const callback = (stackframes) => {
  return stackframes.map((sf) => {
    return sf.toString();
  }).join('\n');
};

const errback = () => { return null; };

const monitorErrors = (msg, file, line, col, error) => {
  const stack = StackTrace.fromError(error).then(callback).catch(errback);
  stack.then((stringifiedStack) => {
    const data = {
      name: 'ERROR',
      msg: msg,
      url: file,
      line: line,
      column: col || null,
      stack: stringifiedStack,
      ...parser.getResult()
    };
    Analytics.record({
      data: data,
      streamName: 'amplifyobservabilityKinesis-dev'
    }, 'AWSKinesis');
  });
};

export default monitorErrors;

monitorWebVitals.jsでは、Kinesis Data Streamsとの接続情報を設定し、標準化されたWebVitals情報とユーザーエージェントを送信します。

// src/monitorWebVitals.js
import UAParser from 'ua-parser-js';
import { Analytics, AWSKinesisProvider } from 'aws-amplify';
import awsconfig from './aws-exports';

Analytics.configure({
  AWSKinesis: {
    region: awsconfig.aws_project_region,
    bufferSize: 1000,
    flushSize: 100,
    flushInterval: 5000, 
    resendLimit: 5
  } 
});

Analytics.addPluggable(new AWSKinesisProvider());

const parser = new UAParser();

const monitorWebVitals = (metric) => {
  const data = {
    name: metric.name,
    value: metric.value,
    url: window.location.href,
    ...parser.getResult()
  };
  Analytics.record({
    data: data,
    streamName: 'amplifyobservabilityKinesis-dev'
  }, 'AWSKinesis');
};

export default monitorWebVitals;

次に、作成したmonitorモジュールをsrc/index.jsにてインポートします。

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Amplify from 'aws-amplify';
import awsconfig from './aws-exports';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import monitorErrors from './monitorErrors';
import monitorWebVitals from './monitorWebVitals';

Amplify.configure(awsconfig);

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals(monitorWebVitals);

// To capture errors and report through Kinesis Stream
// Learn more: https://docs.amplify.aws/lib/analytics/streaming/q/platform/js#installation-and-configuration
window.onerror = monitorErrors;
window.onunhandledrejection = monitorErrors;

4. Webアプリを公開する

下記のコマンドを実行すると、作成したReactアプリを公開することができます。

$ amplify add hosting

# Select manual deployment for this time

$ amplify publish

CI/CDが終了すると、アプリを表示できるようになります。

実際のアプリケーションのスクリーンショット

5. データストリームの構築

次のステップは、Kinesis Data StreamsからQuickSightへのデータの流れを構築することです。 Amazon S3(以下S3)にデータを保存し、S3からQuickSightにデータを届けるには少し手間がかかります。 ゴールイメージは以下のアーキテクチャです。

データストリームのゴールイメージ

5.1 Kinesis Data Streamの名前を確認

AWSの管理コンソールでAmazon Kinesisを開き、サイドバーから「Data streams」を選択します。 次に、右側のパネルのData streamの名前が、実装したコードであるamplifyobservabilityKinesis-devと一致するかどうかを確認します。

Kinesis Data Streamの名前を確認

5.2 Kinesis Data Firehoseの作成

次に、サイドバーから「Data Firehose」をクリックして、新しいKinesis Data Firehoseの配信用ストリームを作成します。 これにより、Kinesis Data Streamを介してS3にデータが保存されるようになります。

Kinesis Data Firehoseを作成して、Kinesis Data StreamからS3にデータを配信

5.3 S3のバケットを確認

公開したWebアプリを少し操作して、エラーデータとWeb Vitalsデータを送信してみましょう。送信が完了したら、S3で配信されたデータを確認できます。

S3に保存されたデータ

5.4 Glue crawlerの設定

次のステップは、S3バケットからデータを収集するクローラーを構築することです。 AWS Glue(以下Glue)を開き、前のセクションで確認したS3のバケットからデータをクローリングするために「Add crawler」を押します。

Glue crawler

5.5 クロールされたデータを分析するためのAthenaの構築

前ステップにより、Amazon Athena(以下Athena)によるGlueを介したS3に保存されたデータを分析する準備が整いました。 新しいデータベースを作成し、それをGlueのデータに接続します。

Athenaによる分析

6. QuickSightでデータを可視化

最後にQuickSightでデータを可視化。 新しいボードを作成し、以下の画像のようにAthenaとQuickSightを接続します。 WebVitalsとErrorを別々のダッシュボードとして作成してみます。

Web Vitalsのダッシュボード
JavaScriptエラーのダッシュボード

おまけ: Amplify hostingの最新機能

Amplify Hostingは最近、Amazon CloudWatchと連携したモニタリング機能を追加しました。 この機能を利用すると、サービスが機能しているかHTTPステータスを元に確認できます。併せて活用できます。

Amplifyのモニタリング機能

詳細については、公式ドキュメントを参照してください。

ユーザーはUX向上を加速させる貢献者

OSSの世界では、「十分な目ん玉があれば、全てのバグは洗い出される」という言葉があり、著名なOSSがなぜ高い品質を保つことが出来ているかの説明によく引用されます。この言葉と同様に、モニタリングはユーザーと一体となってプロダクション環境でのスモークテストを可能とし、品質の向上を実現するでしょう。

言い換えると、リアルタイムにJavaScriptのエラーとWeb Vitalsの観察を行えるソフトウェアは、より多くのユーザーをUX向上を行う改善プロセスに招待することが可能となるのでは、と思っています。

謝辞

参考文献