# 機能設計書 108-テレメトリ

## 概要

本ドキュメントは、Next.jsの匿名テレメトリ収集機能の設計を記述する。機能利用状況やビルド情報を匿名で収集し、Next.jsの改善・機能優先度決定に活用する仕組みである。

### 本機能の処理概要

Next.jsの利用統計（ビルド完了、機能利用状況、エラー発生等）を匿名で収集し、テレメトリサーバー（`telemetry.nextjs.org`）に送信する機能である。すべてのデータはワンウェイハッシュで匿名化され、オプトアウトが可能である。

**業務上の目的・背景**：Next.jsチームが機能の利用状況やエラー傾向を把握し、フレームワークの開発ロードマップや機能の優先順位を決定するためのデータ収集である。収集データは完全に匿名であり、個人やプロジェクトを特定することはできない。

**機能の利用シーン**：`next dev`、`next build`、`next start`コマンドの実行時に自動的にテレメトリイベントが記録される。初回実行時にはテレメトリ収集の通知が表示される。`next telemetry`コマンドまたは`NEXT_TELEMETRY_DISABLED`環境変数で有効/無効を切り替えられる。

**主要な処理内容**：
1. テレメトリの有効/無効状態の管理（Confライブラリによる永続化）
2. 匿名ID、セッションID、プロジェクトIDの生成・管理
3. テレメトリイベントの記録（ビルド完了、機能利用、エラー等）
4. テレメトリサーバーへの非同期送信（リトライ付き）
5. 開発サーバー終了時の遅延フラッシュ（デタッチドプロセス）
6. ワンウェイハッシュによるデータ匿名化

**関連システム・外部連携**：テレメトリサーバー（`https://telemetry.nextjs.org/api/v1/record`）にHTTP POSTでイベントを送信する。

**権限による制御**：`NEXT_TELEMETRY_DISABLED`環境変数でグローバル無効化、`next telemetry disable/enable`コマンドでプロジェクト単位の制御が可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | ターミナル/コンソール | 主出力先 | テレメトリ通知メッセージ、デバッグログの出力先 |

## 機能種別

データ収集 / 外部連携

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| distDir | string | Yes | Next.jsのdistディレクトリパス | - |
| events | TelemetryEvent \| TelemetryEvent[] | Yes | 記録するテレメトリイベント | eventName: string, payload: object |

### 入力データソース

- `next.config.js`の設定（distDir）
- ビルド・開発プロセスからのイベントデータ
- 環境変数（`NEXT_TELEMETRY_DISABLED`、`NEXT_TELEMETRY_DEBUG`）
- Confストレージ（`~/.config/nextjs/config.json`）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| context.anonymousId | string | ランダム生成された匿名ID（永続化） |
| context.projectId | string | gitリモートURLのワンウェイハッシュ |
| context.sessionId | string | プロセス実行ごとのセッションID |
| meta | AnonymousMeta | OS、CPU、メモリ等のシステム情報 |
| events | Event[] | テレメトリイベント配列（eventName + fields） |

### 出力先

- `https://telemetry.nextjs.org/api/v1/record`（HTTP POST）
- デバッグモード時：`process.stderr`（JSON形式）

## 処理フロー

### 処理シーケンス

```
1. Telemetryクラスのインスタンス化
   ├─ Confストレージの初期化
   ├─ セッションIDの生成（randomBytes(32)）
   └─ 初回通知の表示判定
2. record()メソッドでイベント記録
   ├─ テレメトリ無効チェック
   ├─ デバッグモードの場合はstderrに出力
   └─ submitRecord()で送信
3. submitRecord()での送信処理
   ├─ anonymousId、projectId、sessionIdの取得
   ├─ getAnonymousMeta()でシステム情報取得
   └─ postNextTelemetryPayload()でHTTP POST
4. flushDetached()での遅延フラッシュ
   ├─ イベントをJSONファイルに書き出し
   └─ デタッチドプロセスで送信
```

### フローチャート

```mermaid
flowchart TD
    A[Telemetry初期化] --> B{通知済み?}
    B -->|No| C[テレメトリ通知表示]
    B -->|Yes| D[通知スキップ]
    C --> D
    D --> E[record呼び出し]
    E --> F{テレメトリ無効?}
    F -->|Yes| G[Promise.resolve]
    F -->|No| H{デバッグモード?}
    H -->|Yes| I[stderrにJSON出力]
    H -->|No| J[submitRecord]
    J --> K[projectId取得]
    K --> L[anonymousMeta取得]
    L --> M[postNextTelemetryPayload]
    M --> N[HTTP POST with retry]
    N --> O{成功?}
    O -->|Yes| P[完了]
    O -->|No| Q[エラー無視]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-108-1 | オプトアウト | `NEXT_TELEMETRY_DISABLED`環境変数でテレメトリを無効化 | 環境変数設定時 |
| BR-108-2 | 初回通知 | テレメトリ収集開始を1回のみ通知 | notifiedAtが未設定 |
| BR-108-3 | ワンウェイハッシュ | プロジェクトIDはgitリモートURLをSHA-256 + ソルトでハッシュ化 | プロジェクトID生成時 |
| BR-108-4 | ソルト付きハッシュ | ハッシュにはローカル生成のソルトを付与し、逆引きを不可能にする | すべてのハッシュ操作 |
| BR-108-5 | エラー無視 | テレメトリ送信の失敗はアプリケーション動作に影響しない | 送信エラー発生時 |
| BR-108-6 | CI/Docker環境 | CI環境やDocker内ではストレージをdistDirのcacheに配置 | isCI or isDocker |
| BR-108-7 | デタッチドフラッシュ | 開発サーバー終了時はデタッチドプロセスで非同期送信 | flushDetached呼び出し時 |
| BR-108-8 | リトライ | HTTP POST失敗時は1回リトライ（最小500ms待機） | 送信失敗時 |
| BR-108-9 | タイムアウト | 5秒のAbortSignalタイムアウト | fetch実行時 |

### 計算ロジック

- anonymousId: `randomBytes(32).toString('hex')`（永続化）
- sessionId: `randomBytes(32).toString('hex')`（プロセスごと）
- projectId: `SHA-256(salt + gitRemoteUrl)`
- salt: `randomBytes(16).toString('hex')`（永続化）

## データベース操作仕様

### 操作別データベース影響一覧

該当なし。Confライブラリによるファイルベースの設定永続化のみ。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | Conf初期化エラー | ファイルシステム権限エラー | confをnullに設定し、テレメトリを無効化 |
| - | HTTP送信エラー | ネットワークエラー/タイムアウト | 1回リトライ後にエラーを無視（swallow） |
| - | git remote取得エラー | gitリポジトリでない場合 | REPOSITORY_URL環境変数またはcwd()にフォールバック |

### リトライ仕様

- HTTP POST: 最大1回リトライ、最小500ms待機、ファクター1
- 5秒タイムアウト（AbortSignal.timeout）

## トランザクション仕様

該当なし。

## パフォーマンス要件

- テレメトリ送信はメインプロセスの実行をブロックしない（非同期Promise）
- 開発サーバー終了時はデタッチドプロセスで送信することでプロセス終了を遅延させない
- AbortControllerによるキャンセル機構でリソースリークを防止

## セキュリティ考慮事項

- すべてのデータはワンウェイハッシュで匿名化
- ソルトはローカルに生成・保存され、外部に送信されない
- gitリモートURLのハッシュからは元のURLを復元できない
- オプトアウト機能により、ユーザーはいつでもテレメトリを無効化できる

## 備考

- テレメトリイベントの種類：NEXT_BUILD_COMPLETED, NEXT_BUILD_OPTIMIZED, NEXT_BUILD_FEATURE_USAGE, NEXT_TYPE_CHECK_COMPLETED, NEXT_LINT_CHECK_COMPLETED, NEXT_ERROR_THROWN, NEXT_MCP_TOOL_USAGE等
- `NEXT_TELEMETRY_DEBUG`環境変数でテレメトリイベントの内容をstderrに出力可能

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

テレメトリイベントとメタデータの構造を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | storage.ts | `packages/next/src/telemetry/storage.ts` | TelemetryEvent型（32行目）、RecordObject型（34-39行目） |
| 1-2 | anonymous-meta.ts | `packages/next/src/telemetry/anonymous-meta.ts` | AnonymousMeta型（7-21行目）：OS、CPU、メモリ等のシステム情報構造 |
| 1-3 | events/build.ts | `packages/next/src/telemetry/events/build.ts` | 各種イベント型（EventBuildCompleted, EventBuildFeatureUsage等） |

**読解のコツ**: TelemetryEventは`{ eventName: string, payload: object }`のシンプルな構造。イベント種別ごとに特化したペイロード型がeventsディレクトリに定義されている。

#### Step 2: Telemetryクラスのメイン処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | storage.ts | `packages/next/src/telemetry/storage.ts` | Telemetryクラス（51-332行目） |

**主要処理フロー**:
1. **62-82行目**: コンストラクタ - Conf初期化、セッションID生成、通知表示
2. **84-111行目**: `notify()` - 初回テレメトリ通知
3. **113-133行目**: `anonymousId`/`salt`ゲッター - 永続化されたIDの取得・生成
4. **135-154行目**: `isDisabled`/`isEnabled`/`setEnabled` - 有効/無効管理
5. **156-167行目**: `oneWayHash` - ソルト付きSHA-256ハッシュ
6. **174-217行目**: `record()` - イベント記録とキュー管理
7. **226-276行目**: `flushDetached()` - デタッチドプロセスでの遅延送信
8. **278-331行目**: `submitRecord()` - テレメトリサーバーへの送信

#### Step 3: テレメトリ送信処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | post-telemetry-payload.ts | `packages/next/src/telemetry/post-telemetry-payload.ts` | HTTP POST送信処理（18-48行目） |

**主要処理フロー**:
- **19-21行目**: AbortSignal.timeout(5000)でタイムアウト設定
- **23-37行目**: `retry()`でfetch POST送信（1回リトライ、500ms最小待機）
- **39-46行目**: エラーの無視（swallow）

#### Step 4: デタッチドフラッシュを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | detached-flush.ts | `packages/next/src/telemetry/detached-flush.ts` | デタッチドプロセスでの送信処理 |

**主要処理フロー**:
- **13-23行目**: コマンドライン引数の解析
- **34-43行目**: JSONファイルからイベントを読み込み
- **45-47行目**: Telemetryインスタンスで送信・フラッシュ
- **50行目**: 送信完了後にイベントファイルを削除

#### Step 5: プロジェクトID生成を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | project-id.ts | `packages/next/src/telemetry/project-id.ts` | gitリモートURLまたはフォールバックによるプロジェクトID取得 |

**主要処理フロー**:
- **14-41行目**: `_getProjectIdByGit()` - `git config --local --get remote.origin.url`の実行
- **43-47行目**: `getRawProjectId()` - git > REPOSITORY_URL > cwdの優先順位

### プログラム呼び出し階層図

```
next dev / next build
    │
    └─ new Telemetry({ distDir })
           ├─ Conf初期化
           ├─ notify() [初回通知]
           │
           ├─ record(events)
           │      └─ submitRecord(events)
           │             ├─ getProjectId()
           │             │      └─ getRawProjectId()
           │             │             └─ git config --get remote.origin.url
           │             ├─ getAnonymousMeta()
           │             └─ postNextTelemetryPayload(payload, signal)
           │                    └─ fetch('https://telemetry.nextjs.org/api/v1/record')
           │
           └─ flushDetached('dev', dir)
                  ├─ fs.writeFileSync(eventsFile)
                  └─ child_process.spawn(detached-flush.ts)
                         ├─ JSON.parse(eventsFile)
                         ├─ telemetry.record(events)
                         ├─ telemetry.flush()
                         └─ fs.unlinkSync(eventsFile)
```

### データフロー図

```
[入力]                       [処理]                          [出力]

ビルド/開発イベント ──────> Telemetry.record() ──────────> telemetry.nextjs.org
  ├─ eventName                   │                           (HTTP POST)
  └─ payload                     ├─ キュー追加
                                 ├─ anonymousId取得
システム情報 ─────────────> getAnonymousMeta() ──────────> meta情報
  ├─ OS, CPU, Memory             │
  └─ isCI, isDocker              └─ postNextTelemetryPayload()

Conf Storage ─────────────> anonymousId / salt ──────────> ワンウェイハッシュ
  (~/.config/nextjs)             │
                                 └─ projectId (SHA-256)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| storage.ts | `packages/next/src/telemetry/storage.ts` | ソース | Telemetryクラスのメイン実装 |
| anonymous-meta.ts | `packages/next/src/telemetry/anonymous-meta.ts` | ソース | 匿名システム情報の収集 |
| post-telemetry-payload.ts | `packages/next/src/telemetry/post-telemetry-payload.ts` | ソース | テレメトリサーバーへのHTTP POST送信 |
| detached-flush.ts | `packages/next/src/telemetry/detached-flush.ts` | ソース | デタッチドプロセスでの遅延送信 |
| project-id.ts | `packages/next/src/telemetry/project-id.ts` | ソース | プロジェクトID（git remote URL）の取得 |
| flush-telemetry.ts | `packages/next/src/telemetry/flush-telemetry.ts` | ソース | テレメトリフラッシュヘルパー |
| events/index.ts | `packages/next/src/telemetry/events/index.ts` | ソース | イベント定義のエクスポート |
| events/build.ts | `packages/next/src/telemetry/events/build.ts` | ソース | ビルド関連テレメトリイベント定義 |
| events/version.ts | `packages/next/src/telemetry/events/version.ts` | ソース | バージョン情報イベント定義 |
| events/plugins.ts | `packages/next/src/telemetry/events/plugins.ts` | ソース | プラグイン使用イベント定義 |
| ci-info.ts | `packages/next/src/server/ci-info.ts` | ソース | CI環境情報の検出 |
