Article by: Lukas Stracke
ブラウザのトレーシングは、うまく機能しているあいだは「存在を感じさせないもの」です。しかし、一度それがうまく動かなくなると、たちまち強烈な存在感を放ちます。トレーシングが正しく機能していれば、実環境におけるアプリケーションの挙動を明確に把握でき、具体的で行動につながるインサイトが得られます。一方で、トレーシングがうまく機能していない場合、そこにあるのはノイズだらけのデータ、欠落したトレース、そして何も語ってくれないスパン。つまり、何の手がかりも得られない状況です。私たちはここ数か月、この問題に少しずつ取り組み、着実に改善を重ねてきました。
ここでは、Sentry の JavaScript SDK に施した改善を順に見ていき、ブラウザトレーシングを「より正確にする」だけでなく、「より有用にする」ための取り組みをご紹介します。ページロードスパンを明示的に制御できるようにすることから、リダイレクトのより賢い取り扱い、リソーススパンにおけるより深いタイミングデータの追加まで。これらの更新は、推測を減らし、本当に重要なものを前面に出すことに主眼を置いています。
ページロードスパンを明示的に終了する
提供開始:10.13.0
私たちの SDK では、規定でページロードやナビゲーションのスパンは「アイドルスパン」として開始されます。このアイドルスパンは、子スパン(例:fetch リクエストのスパン)が継続的に開始・終了されるかぎりアクティブな状態を維持し、一定時間アクティビティが途絶えると自動的に終了します。
なぜこの方式なのでしょうか。それは、「ページが完全に読み込まれた」と言える普遍的な指標が存在しないからです。
初回レスポンスの受信後でしょうか。すべてのスクリプトやリソースのロード完了後でしょうか。それともアプリケーションのハイドレーションやブートストラップ完了後でしょうか。
たしかに、ブラウザには domContentLoaded や readyStateChange のようなシグナルも存在しますが、初期の体感的なページロードに関わる処理がその後に続くケースも少なくありません。現実として、フレームワークや Web アプリの多様性、そして一般的な JavaScript のカオスを踏まえると、この問いに万能な答えはありません。
私たちのアイドル化とデバウンスの仕組みは、ユーザーの 95% にとって十分に機能しており、概ねページロードの所要時間に納得いただけています。しかし一部のユーザーからは、「ページの読み込み完了をアプリケーション側から Sentry に明示的に通知したい」という要望も寄せられていました。そこで用意したのが、Sentry.reportPageLoaded() です。
enableReportPageLoaded: true と Sentry.reportPageLoaded() を併用すると、アイドル機構の大部分が取り除かれ、代わりにユーザーが完全に制御できるようになります。
コールバック外でのアクティブなスパン
提供開始:10.15.0
バージョン 8 で NodeJS SDK に OpenTelemetry を導入した際、私たちは旧来のトランザクション中心の API を新しい startSpan API 群に置き換えました。これは(Sentry がスパン中心のトレーシングの世界へ移行するうえで)さまざまな利点をもたらしましたが、ブラウザ中心のアプリケーションにはギャップも残しました。つまり、コールバックの外でスパンをアクティブな状態に保つことが(容易には)できなかったのです。
「アクティブなスパン」とは、任意の startSpan API を呼び出すだけで自動的に子スパンを追加できるスパンを指します。これに対し「非アクティブなスパン」は、現在のトレース内には残るものの、自動的に子スパンを割り当てることはできません。
コールバックの外でスパンをアクティブに保つこと自体は可能でしたが、かなりハック的であったため、公式には推奨していませんでした。しかし、このニーズについてユーザーから要望があり、各自で達成しようとしていることも分かりました。そこで、ついに公式 API を追加することになったのです。Sentry.setActiveSpanInBrowser(span)
なぜ長い名前なのか。この API はブラウザでのみサポートされるため、正しい環境で呼び出しているかを、ユーザーにもう一度よく確認してほしいからです。とはいえ、この API には明確な用途があります。たとえば、(上で示したように)独自のルーティング計測を実装しているユーザーにとって有用です。
リダイレクト vs. ナビゲーション
私たちの SDK での一般的な課題は、クライアントサイドのルーターによる自動リダイレクトとユーザー発のナビゲーションを区別することです。たとえば、React Router や Angular のルーターが未認証ユーザーをログインページへ送るのは「自動リダイレクト」です。これはたいてい、進行中の pageload やナビゲーションの最中に起きるため、新たな分離したルートスパンツリーを開始すべきではありません。対して、リンククリックなどのユーザー発ナビゲーションは、新しいナビゲーションのルートスパンを開始すべきです。
バージョン 9.37.0 と 10.3.0 の改良で、どの既定の JS History API の変更をリダイレクト/ナビゲーションと見なすかについて、時間とインタラクションに基づくヒューリスティクスを導入しました。今後、より多くのフレームワークのルーター計測にも広げる予定ですが、すでに区別できるものもあります。
結果として、リダイレクトを検出した場合、SDK は新規のナビゲーション・ルートスパンではなく、navigation.redirect の子スパンを開始します。

無視するスパン
スパンメトリクスの外挿の都合により、SDK が Sentry に送信する前に恣意的にスパンをドロップ/削除するいくつかの手段を削除する必要がありました。v9 以降、beforeSendSpan はスパンの変更のみを許可し、削除はできなくなりました。とはいえ、事前に定義したフィルター条件に応じて特定のスパンを無視したい、という要望は寄せられていました。たとえば、一部のユーザーにとっては、CSS や画像などのリソースの読み込み時間を表す resource.* スパンは価値が低いと見なされ、無視したいケースがあります。
私たちの SDK がスパン無視オプションに対応する方法は、一般に次の 2 通りです。
- 特定の SDK インテグレーションにオプションを追加し、計測内の特定アクション(リソースやミドルウェアのスパンなど)を無視できるようにする。
- SDK の一般オプションとして、スパンを「グローバル」に無視できるようにする。
最初は、ignoreResourceSpans(9.23.0 でリリース)や ignorePerformanceApiSpans(9.25.0 でリリース)といったインテグレーション単位のオプションをいくつか提供しました。しかし、無視対象としたいスパンの追加要望が増えたため、トップレベルの SDK オプションである ignoreSpans を導入しました(10.2.0 でリリース)。
このオプションにより、無視したいスパンに対するパターンを指定できます。ignoreErrors と同様の仕組みで、スパン名(文字列または正規表現)だけでのマッチ、または名前と op の任意の組み合わせでマッチさせることが可能です。
リソーススパンのタイミング情報
提供開始:10.12.0
先ほど resource.* スパンについて触れ、ユーザーによっては価値が低いと見なされることがあると述べました。理想的には、それらを可能な限り有用にすることで、その見方を変えてもらうことができます。
幸いにも、ブラウザは(ほぼ)すべてのリソース要求に対してリクエストのライフサイクル全体を公開してくれています。そこで、各リソーススパンについて利用可能なすべてのタイミング情報を収集するようにしました。たとえば、ドメインルックアップに要した正確な時間、リクエスト開始時刻、レスポンス受信時刻などです。さらに、リソーススパンに対して “Time to First Byte”(TTFB) も送信します。詳細なネットワークタイミング情報は、遅い CDN や、リクエストライフサイクルの中で特定のボトルネックになっている部分を突き止めるのに役立ちます。
バージョン 10.12.0 以降、利用可能なリクエストライフサイクルのタイミング情報は、リソーススパンのスパン属性として保存されます。

私たちの SDK には、Google の web-vitals ライブラリのベンダリング版が同梱されており、LCP・CLS・INP などの Web Vitals を算出・監視するために使用しています。ライブラリは定期的に最新バージョンへとバンプしています。
簡単そうに聞こえますが、実はもう少し複雑です。まず、バンドルサイズ最適化のために不要なコードの多くを取り除いています。また、利用可能なブラウザ API をより慎重に仮定しているため、すべてのブラウザ API 呼び出しを確認し、追加のガードを入れる必要があります。
さらに重要なのは、ライブラリ側で破壊的変更(breaking changes)が導入される可能性があり、それも考慮に入れなければならない点です。直近の 4.2.5 → 5.0.2 のアップデートでは、長らく非推奨とされ使用が控えられてきた FID(後継の INP は既に十分に確立)について、レポート自体が削除されました。私たちは SDK の 9.29.0 でライブラリを 5.0.2 へ更新しましたが、4.2.5 の FID コードは残したままにしました。これは、v9 の間に(ダッシュボード等で)FID に依存しているユーザーを壊さないためです。最終的に 10.0.0 でようやく FID を完全に削除しました。
スタンドアローンの Web Vital スパン
ここしばらく、LCP と CLS の Web Vitals の品質を大幅に向上させる取り組みを行ってきました。
これまでは、Web Vital の計測値は pageload スパンが終了した時点で収集されていましたが、実際には、LCP や CLS の値は pageload スパンがすでに終了した後にのみ安定する場合があり、その場合は最終値を拾えません。
そこで私たちは、CLS と LCP を pageload スパンから切り離し、それぞれを独立した(スタンドアローンの)スパンとして送信することにしました。これにより、pageload スパン終了時点の値をそのまま採用するのではなく、最終値が確定するのを待つことが可能になります。

当面は、2つの実験的オプションを設定するだけで、スタンドアローンのスパンを有効化できます。
両機能(スパン)は、現在のところオプトインのままです。理由は、SDK が現在要件としているバージョンよりも新しいセルフホスト版 Sentry が必要になるためです。これらは、次の SDK のメジャーバージョンでデフォルト有効にする予定です。
次に来るのは
まだ終わりではありません!私たちには多くのアイデアがあり、今後取り組みたい改善点をまとめたロードマップを最近公開しました。要点を手短に言うと、SSR トレースの大幅な改善、フロントエンドのトレース間のリンク強化、そしてサンプリングの一貫性の確保に取り組んでいます。
こちらは簡単なティザーです。


まじめな話に戻ります。
もしご興味があれば、ぜひロードマップをお読みいただき、Discord でぜひご意見をお聞かせください!
Original Page: Improving browser tracing step by step
IchizokuはSentryと提携し、日本でSentry製品の導入支援、テクニカルサポート、ベストプラクティスの共有を行なっています。Ichizokuが提供するSentryの日本語サイトについてはこちらをご覧ください。またご導入についての相談はこちらのフォームからお気軽にお問い合わせください。