【モンキーパッチはもう不要】Tracing Channels による、より優れたオブザーバビリティ

Article by:

 

ほとんどすべての本番アプリケーションは、さまざまなツールやライブラリを利用しています。たとえば、データベースやキャッシュと通信するためのライブラリ、あるいは Nest.js や Nitro のようなフレームワークです。本番環境で何が起きているのかを把握するために、アプリケーション開発者は Sentry のような APM(Application Performance Monitoring)ツールを利用します。

しかし、そこには本質的な問題があります。APM ツールが必要とするパフォーマンスデータは、多くの場合、ライブラリ自身からネイティブに提供されていません。そのデータ取得は、Sentry や OpenTelemetry のような APM ツール側へ委ねられており、それらが代わりにライブラリの重要な機能へインストルメンテーションを行っています。

 

インストルメンテーションとは?

アプリケーションを観測可能にするための最も基本的な要件は、その各コンポーネントや利用ライブラリをインストルメントできることです。インストルメンテーションとは、プログラム内部の動作を監視・分析し、診断データを生成するためのコードを追加するプロセスを指します。Sentry SDK や OpenTelemetry のインストルメンテーションは、まさにこの処理を内部で行っています。

たとえば、一般的な HTTP クライアントライブラリを考えてみましょう。アプリケーション開発者は、リクエストの開始と完了のタイミング、さらに URL、ステータスコード、ヘッダーといったメタデータを知りたいと考えます。

現在、ライブラリごとにこの対応方法は統一されていません。emitter.on(‘request’, …) のような独自フックを提供するものもあれば、リクエストを横取りするためのベンダー固有ミドルウェアを提供するものもあります。こうした場合、Sentry や OpenTelemetry は、オブザーバビリティデータを送信するプラグインを実装できます。

これは機能しますが、その負担はライブラリやフレームワーク側(たとえば Nuxt)にかかります。つまり、インストルメンテーション用 API を意識的に設計し、どこでそれを公開するべきかを判断しなければなりません。フックやインターセプタによって、適切な場所へオブザーバビリティコードを差し込める一方で、APM のメンテナーは、その API が将来にわたって安定して維持されることをライブラリ作者に依存しています。

さらに、共通規約も存在しません。ライブラリごとにフックの形もメタデータも異なるため、APM メンテナーはライブラリごとに大きく異なるプラグインを実装・保守し続ける必要があります。

 

サーバーサイド JavaScript はどのようにインストルメントされているのか

JavaScript における従来のインストルメンテーション手法は「モンキーパッチ」です。これは、ライブラリのコードを実行時に書き換え、本来の処理に加えてオブザーバビリティデータも送信するようにする方法です。この手法が可能なのは、モジュールが変更可能で同期ロードされる CommonJS(CJS)環境に限られます。

しかし、エコシステムは変化しています。サーバーサイド JavaScript が ES Modules(ESM)へ移行するにつれ、この手法は成り立たなくなっています。ES Modules は変更不可能であり、非同期にロードされるため、従来のように実行時に import をパッチすることができません。詳細については、ESM Observability Instrumentation Guide でより詳しく解説されています。

現在の回避策として使われているのが、Module Customization Hooks と –import フラグを組み合わせる方法です。代表的なフックとして import-in-the-middle/hook.mjs があります。これは機能しますが、壊れやすく、複雑で、いかにも「回避策」という印象を与えるものです。

CJS におけるモンキーパッチも、ESM における Module Customization Hooks も、本質的には同じ問題を抱えています。それは、インストルメンテーションを「外側から」適用しているという点です。ライブラリ自身は受動的な存在のままです。

では、ライブラリ自身がオブザーバビリティへ積極的に参加し、自らテレメトリデータを送信するようになったらどうでしょうか?

それを可能にするのが、Tracing Channels のような diagnostics API です。

 

ライブラリは自らテレメトリを送信するべき

APM ツールが内部へ入り込んでデータを取得するのを待つのではなく、ライブラリ自身が、ランタイムに組み込まれた仕組みを使って内部動作を積極的に公開できるようになるべきです。そのために適した仕組みが Diagnostics Channels であり、さらに言えば Tracing Channels です。これらの機能は、Node.js Diagnostics Working Group によって開発が進められています。

Node.js の diagnostics_channel API を作成し、このワーキンググループを立ち上げた Stephen Belanger に、大きな感謝を送りたいと思います。彼はこの分野を前進させる上で非常に重要な役割を果たしてきました。提案へのフィードバックを行い、ときには権威ある立場から意見を示すことで、ライブラリメンテナーたちを巻き込む後押しにもなっています。

 

Diagnostics Channels

Diagnostics Channels はNode.js に直接組み込まれた、高性能な同期イベントシステムです。また、Bun、Deno、Cloudflare Workers(Node.js compatibility flag 経由)でもサポートされており、ランタイムを横断する共通基盤になりつつあります。

主な用途は、単発イベントの通知です。たとえば、「接続が開始された」といったイベントです(node-redis は実際にこれを利用しています)。ただし、これには制限があります。Diagnostics Channels 単体では、完全なライフサイクルを表現できません。処理時間を計測するには、開始イベントと終了イベントを手動で関連付ける必要があります。

 

Tracing Channels

Tracing Channels はまさにその制限を解決するための仕組みです。Tracing Channel は、関連する Diagnostics Channels を束ねたものであり、1つの操作ライフサイクル全体に対応するサブチャネル(start、end、error、asyncStart)を自動生成します。

さらに重要なのは、TracingChannel が非同期境界をまたいでコンテキストを自動伝播する点です。これにより、APM ツールは、あるデータベースクエリがどの HTTP リクエストによって引き起こされたのかを、手動で管理することなく関連付けられます。

これらを組み合わせることで、ライブラリやフレームワークの作者は、特定のログ・トレーシングベンダーへ依存することなく、内部処理を標準化された方法で公開できるようになります。ライブラリ側は構造化イベントを送信し、それをどう扱うかはオブザーバビリティツール側が決定します。

 

ライブラリで Tracing Channels を実装する方法

Tracing Channels は、利用されていない場合、ほぼゼロコストです。購読者が存在しなければ、データ送信のコストはほとんど発生しません。そのため、ライブラリ作者は、オブザーバビリティを必要としないユーザーへ負荷を与える心配なく Tracing Channels を追加できます。

これによって、モンキーパッチは不要になります。また、ESM 環境で –import フラグを使ったプリロード設定も不要になります。

 

命名と一貫性:チャネルこそが契約

Tracing Channels は、必ずそれを送信するライブラリ単位でスコープ化するべきです。名前空間には npm パッケージ名を使用します。パッケージ名はグローバルに一意であるため、チャネル名の衝突を防げます。

たとえば、mysql2 は mysql2:query を提供しており、そこから tracing:mysql2:query:start などのチャネルが送信されます。また、unstorage ライブラリは unstorage.get を提供し、tracing:unstorage.get:start などを送信します。untracing パッケージは、こうした命名規則をエコシステム全体で標準化する取り組みを進めています。

同じくらい重要なのが、一貫したデータ構造を常に送信することです。Sentry をはじめとする APM ツールは、ペイロード構造が分かってはじめて、自動インストルメンテーションを提供できます。

実装パターン自体はシンプルです。ライブラリ側で、対象処理を tracePromise 呼び出しでラップします。

そして利用側では、Sentry のような SDK がそれらのイベントを購読します。

ライブラリとオブザーバビリティツールは、互いの存在を知る必要がありません。チャネルこそが、その契約になるのです。

 

エコシステムはすでに動き始めています

2026年2月初旬、Sentry の AndreiJanSigridOTel Unplugged EU に参加し、「より良い JavaScript ESM サポートへの準備」というテーマを提起しました。これは、OpenTelemetry エコシステムにおける最優先事項の1つとして投票で選ばれました。

OTel Unplugged EUの参加者がJS ESMの可観測性に関する優先事項について議論

つまり、これは単なる理論上の提案ではありません。すでに多くの著名ライブラリが Diagnostics Channel や Tracing Channel のサポートを実装、あるいは PR をマージしています。

フレームワークや HTTP 周りでは、undici(Node.js に組み込まれている HTTP クライアント)が Node 20.12 から Diagnostics Channels をサポートしています。また、fastify(Doc)、nitro(PR)、h3(PR) もネイティブ対応を進めています。

データベース領域では、unstorage(PR) や mysql2(Doc) がすでに Tracing Channels を利用しており、pg / pg-pool でも現在対応が進行中です。Redis クライアントもそれに続いており、ioredis(PR)や node-redis(PR)では Tracing Channels のサポートがすでに進められています。

もちろん、こうした動きは、実際に手を動かす人たちなしには実現しません。複数ライブラリへの Tracing Channel 実装を推進してきた Sentry のエンジニア、Abdelrahman Awad@logaretm)には特に大きな感謝を送りたいです。

そして、h3 や nitro において協力的だった Pooya Parsa@pi0)にも特別な感謝を。彼のオープンな姿勢は、このアプローチを形にし、エコシステム全体へその可能性を示す上で非常に重要な役割を果たしました。

 

これから目指すもの

私たちはまだ、「鶏が先か卵が先か」という段階にいます。ライブラリ側は、APM ツールが実際に利用してくれなければチャネルを追加する動機が弱く、APM ツール側も、ライブラリがチャネルを提供しなければ積極的に対応する理由を持てません。

最終的な目標は「ユニバーサルな JavaScript オブザーバビリティ」です。Node.js、Bun、Deno が同じ診断パターンを共有し、CJS におけるモンキーパッチも、ESM における –import フラグも、壊れやすい回避策も不要な世界です。

ライブラリ自身がオブザーバビリティの主体となり、自分たちのユーザーにとって本当に重要だと考えるデータを、自ら送信するようになるのです。

 

 

Original Page: No more monkey-patching: Better observability with tracing channels

 




IchizokuはSentryと提携し、日本でSentry製品の導入支援、テクニカルサポート、ベストプラクティスの共有を行なっています。Ichizokuが提供するSentryの日本語サイトについてはこちらをご覧ください。またご導入についての相談は「お問い合わせ」からお気軽にお問い合わせください。

 

シェアする

Recent Posts

;