GROWI の OpenTelemetry 統合 (apps/app/src/features/opentelemetry/) を メンテナンスするための大局的な仕様。SDK ライフサイクル、Resource Attribute、Custom Metric、HTTP Anonymization の 4 レイヤがそれぞれ「何を担い、何を担わないか」を明文化し、新規メトリクスや anonymization handler の追加、SDK のバージョンアップ、設定キーの追加・改名といった将来のメンテナンス時に、本 spec を 1 か所の参照点として運用できる状態を目標とする。
本 spec は新規実装 spec ではなく、既に実装・稼働している features/opentelemetry/ の 現状の責務境界をスナップショットとして固定化する 性格を持つ。個別機能の追加・変更は原則として本 spec の Boundary Commitments の範囲内で行われ、境界をまたぐ変更が必要なときは Revalidation Triggers として再評価される。
node-sdk.ts, node-sdk-configuration.ts, node-sdk-resource.ts)。logger.ts)。semconv.ts)。custom-resource-attributes/)。custom-metrics/、合計 4 モジュール: application / user-counts / page-counts / system)。anonymization/、4 個の handler + utility)。otel:* 設定キー 4 種の利用ポリシー。growiInfoService / configManager / loggerFactory などの上流サービスの設計や API 変更。growi.users.total / growi.users.active / growi.pages.total / growi.configs / system.* / process.*)の名称変更や再構成。http.target 以外)。growiInfoService.getGrowiInfo({ includeAttachmentInfo, includeUserCountInfo, includePageCountInfo }) の API シグネチャ・返り値型が維持されることに依存する。破壊的変更があった場合は Revalidation Triggers として custom-metrics/ および custom-resource-attributes/application-resource-attributes.ts を再評価する。configManager.getConfig('otel:*') の 4 キーが現状の意味で参照可能であることに依存する。Objective: GROWI 運用者として、OpenTelemetry SDK を環境変数 1 つで有効/無効を切り替えられ、無効時にはランタイムオーバーヘッドや誤った OTLP 接続試行が発生しないことを保証したい。
otel:enabled 設定が false のとき、NodeSDK インスタンスを生成せず Resource Attribute 取得や Custom Metric の登録も行わない。otel:enabled 設定値と OTEL_SDK_DISABLED 環境変数の値が矛盾している場合、OTEL_SDK_DISABLED を上書きして整合性を取り、その旨を warn ログとして出力する。start() 呼び出しと Custom Metric 登録」の 3 段階で行う。各段階は otel:enabled を再確認した上で実行する。initInstrumentation() を二重に呼ばれても、SDK インスタンスを重複生成しない(再初期化は警告を出してスキップする)。Objective: 受信側インフラ管理者として、GROWI が emit する Resource Attribute がテレメトリ発生元エンティティの identity 情報のみであり、測定値や設定値が紛れ込まないことを保証したい。
service.name, service.version, service.instance.id(取得できた場合), os.type, os.platform, os.arch, growi.service.type, growi.deployment.type。service.instance.id / growi.service.type / growi.deployment.type)に行う。Objective: 運用者として、GROWI インスタンスの設定情報(site URL、wiki type、外部認証種別、添付ストレージ種別)を 1 つの info-gauge メトリクスから一覧できることを保証したい。
growi.configs という ObservableGauge を Prometheus info パターン(値は常に 1、情報はラベルに格納)で emit する。growi.configs に以下のラベルを付与する: site_url, site_url_hashed, wiki_type, external_auth_types, attachment_type。otel:isAppSiteUrlHashed が true, the GROWI server shall site_url を [hashed] リテラルにし、site_url_hashed に SHA-256 ハッシュ値を入れる。false のときは site_url に生 URL を入れ site_url_hashed は undefined(emit されない)。external_auth_types / attachment_type の値が growiInfoService から取得できない場合, the GROWI server shall 当該ラベルを空文字 '' でフォールバックする。Objective: 運用者として、GROWI 上の主要エンティティ(ユーザー、ページ)の総数とアクティビティ指標を継続的に観測したい。
growi.users.total メトリクスを総ユーザー数で観測する(単位 users)。growi.users.active メトリクスをアクティブユーザー数で観測する(単位 users)。growi.pages.total メトリクスを総ページ数で観測する(単位 pages)。growiInfoService からのカウント値取得が失敗した場合, the GROWI server shall 0 で観測するか、当該収集サイクルでの観測をスキップし、diag ロガーに error を記録する。Objective: コンテナ環境(Docker / Kubernetes)で GROWI を運用する管理者として、「コンテナに割り当てられたメモリ上限(cgroup limit)」「ホスト物理メモリ総量」「プロセス RSS」「V8 ヒープの使用/確保/外部メモリ」を別々のメトリクスとして観測できることを保証したい。
system.memory.limit を process.constrainedMemory() の戻り値(>0 のとき)で観測する。値が 0 または falsy のときは当該メトリクスのみ観測をスキップする。system.host.memory.total を os.totalmem() の戻り値で常に観測する。process.memory.usage を process.memoryUsage().rss で観測する。process.runtime.v8.heap.used / process.runtime.v8.heap.total / process.runtime.v8.heap.external を v8.getHeapStatistics() および process.memoryUsage().external から観測する。By(bytes)で emit する。Objective: プライバシ保護担当者として、otel:anonymizeInBestEffort が true のとき、ユーザーが入力した検索キーワード・ページパス・ユーザー名がトレース span の http.target に平文で残らないことを保証したい。
otel:anonymizeInBestEffort が true のとき、HTTP instrumentation の startIncomingSpanHook で anonymizationModules を順次評価し、canHandle(url) が true を返した module の handle() 結果を span attribute としてマージする。/_api/search, /_search) の q クエリパラメータを [ANONYMIZED] に置換する。/_api/v3/page-listing/{ancestors-children,children,item}) および page API (/_api/v3/pages/list, /_api/v3/pages/subordinated-list, /_api/v3/page/check-page-existence, /_api/v3/page/get-page-paths-with-descendant-count) の path / paths パラメータを匿名化する。[USERNAME_HASHED:...] / [HASHED:...] プレースホルダで置換する。permalink(ObjectId)と users top page(/user)はそのまま残す。otel:anonymizeInBestEffort が false, the GROWI server shall startIncomingSpanHook を渡さず、HTTP instrumentation の標準動作のみを行う。Objective: 開発者として、OpenTelemetry 内部の diag ログが GROWI の通常のアプリケーションログ(pino)と同一フォーマット・同一出力先で観測できることを保証したい。
NODE_ENV === 'development' かつ otel:enabled が true, the GROWI server shall DiagLogger を pino logger にアダプトする実装 (DiagLoggerPinoAdapter) をグローバルに登録する。diag.error/warn/info/debug/verbose で受け取ったメッセージが JSON 文字列の場合に parse して構造化 data に変換し、pino の引数規約(data 第 1 引数・message 第 2 引数)に整合する形で渡す。initLogger() を呼ばない(OpenTelemetry の diag 既定動作に委ねる)。Objective: 運用者として、OTLP メトリクス/トレースエクスポートが OpenTelemetry SDK 標準の環境変数(OTEL_EXPORTER_OTLP_ENDPOINT 等)で制御でき、内部で勝手な default endpoint が固定されないことを保証したい。
OTLPTraceExporter および OTLPMetricExporter をコンストラクタ引数なしで生成し、エンドポイントなど exporter 設定は OpenTelemetry SDK 標準の環境変数で解決させる。PeriodicExportingMetricReader の exportIntervalMillis を 300000(5 分)で初期化する。@opentelemetry/instrumentation-pino および @opentelemetry/instrumentation-fs を明示的に無効化する(pino: log signal を使用しないため、fs: トレース量が膨大すぎるため)。Objective: 開発者として、@opentelemetry/semantic-conventions の incubating attribute をランタイムコードから直接 import せず、本モジュール内のローカルコピーを参照することで、上流の minor リリースでの破壊的変更からアプリケーションを保護したい。
service.instance.id, http.target)を semconv.ts 内に文字列定数として定義し、ランタイムコードはこれを import する。@opentelemetry/semantic-conventions/incubating からの import をランタイムコードに含めない。Objective: 機能を追加・変更するエンジニアとして、新規 Custom Metric や新規 Anonymization Handler を本 spec の境界に従って実装し、レイヤ責務の汚染を回避したい。
custom-metrics/ 配下にファイルを追加し、addXxxMetrics(): void をエクスポートし、custom-metrics/index.ts の setupCustomMetrics() から呼び出す。anonymization/handlers/ 配下に AnonymizationModule 実装ファイルを追加し、handlers/index.ts の anonymizationModules 配列に登録する。growi.configs ラベル経由で、観測値を growi.* / system.* / process.* メトリクス経由でそれぞれ emit する責務分離を維持する。