# 通知設計書 16-ActiveSupport::Notifications.instrument

## 概要

本ドキュメントは、ActiveSupportの計装（Instrumentation）システムについて、設計情報を記載するものである。

### 本通知の処理概要

ActiveSupport::Notifications.instrumentは、Railsアプリケーション内のイベントを計測・通知するための汎用的な計装APIである。コードブロックの実行を計測し、その開始・終了タイミングやペイロードデータを購読者（subscriber）に通知する。パフォーマンス監視、ログ記録、メトリクス収集、デバッグなどの横断的関心事に使用される。

**業務上の目的・背景**：アプリケーションの動作を可視化し、パフォーマンスボトルネックの特定、監視ツールへのメトリクス送信、カスタムログ記録、イベント駆動型の処理を可能にする。Railsフレームワーク内部でも広く使用されており（sql.active_record、process.action_controller等）、統一された監視・計測インフラを提供する。

**通知の送信タイミング**：instrumentメソッドでラップされたコードブロックの実行開始時と終了時に、購読者へイベントが通知される。

**通知の受信者**：ActiveSupport::Notifications.subscribeで登録されたすべての購読者。

**通知内容の概要**：イベント名、開始時刻、終了時刻、トランザクションID、ペイロード（任意のハッシュデータ）が含まれる。例外発生時は例外情報もペイロードに追加される。

**期待されるアクション**：購読者がイベントを受信し、ログ記録、メトリクス送信、アラート発火等の処理を実行する。

## 通知種別

計装通知（Instrumentation Event）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（ブロック実行完了後に通知） |
| 優先度 | 高（即座に購読者へ通知） |
| リトライ | なし |

### 送信先決定ロジック

1. `ActiveSupport::Notifications.instrument(name, payload)`を呼び出す
2. `notifier.listening?(name)`で購読者存在チェック
3. 購読者が存在する場合、Instrumenter.instrumentでイベント計測・通知
4. 存在しない場合はブロックのみ実行（オーバーヘッド最小化）

## 通知テンプレート

### イベント構造

| 項目 | 内容 |
|-----|------|
| name | イベント名（例: "sql.active_record"） |
| start | 開始時刻（Time or Float） |
| finish | 終了時刻（Time or Float） |
| id | トランザクションID（一意識別子） |
| payload | カスタムデータ（Hash） |

### ペイロード例

```ruby
ActiveSupport::Notifications.instrument("process.action_controller", {
  controller: "UsersController",
  action: "index",
  params: { page: 1 },
  format: :html
}) do
  # 処理
end
```

### 例外発生時のペイロード追加

| キー | 説明 |
|-----|------|
| :exception | [例外クラス名, 例外メッセージ] |
| :exception_object | 例外オブジェクト |

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| name | イベント名 | 呼び出し元で指定 | Yes |
| payload | カスタムデータ | 呼び出し元で指定 | No（デフォルト{}） |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| API呼び出し | instrument | 購読者が存在する場合 | 明示的な呼び出し |
| フレームワーク | 各種フレームワーク処理 | 組み込み | sql.active_record等 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| 購読者なし | `notifier.listening?(name)`がfalseの場合、計測をスキップ |

## 処理フロー

### 計装フロー

```mermaid
flowchart TD
    A[instrument name, payload 呼び出し] --> B{listening? name}
    B -->|No| C[ブロック実行のみ]
    B -->|Yes| D[Instrumenter.instrument]
    D --> E[build_handle name, payload]
    E --> F[handle.start]
    F --> G[購読者へstart通知]
    G --> H{ブロック実行}
    H -->|成功| I[handle.finish]
    H -->|例外| J[payload に例外情報追加]
    J --> K[handle.finish]
    I --> L[購読者へfinish通知]
    K --> L
    L --> M[Event オブジェクト生成・配信]
    M --> N[終了]
    C --> N
```

## データベース参照・更新仕様

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| なし | - | instrument自体はDBを参照しない |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| なし | - | instrument自体はDBを更新しない |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| ブロック内例外 | 計測対象のコードで例外発生 | payload[:exception]に記録後、例外を再raise |
| 購読者例外 | 購読者のコールバック内で例外 | InstrumentationSubscriberError発生 |
| 複数購読者例外 | 複数の購読者で例外発生 | 全例外を集約してraise |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | なし |
| リトライ間隔 | N/A |
| リトライ対象エラー | N/A |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | 設定なし |
| 1日あたり上限 | 設定なし |

### 配信時間帯

制限なし（常時有効）

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

- ペイロードに機密情報（パスワード、トークン等）を含めない
- 本番環境では購読者のパフォーマンス影響を考慮する
- ログ出力時は適切なフィルタリングを行う

## 備考

- instrument呼び出し時に購読者がいない場合、オーバーヘッドは最小限
- Eventオブジェクトはduration、cpu_time、allocations等の詳細メトリクスを提供
- monotonic_subscribeでモノトニック時間を使用した高精度計測が可能
- スレッドセーフな実装（IsolatedExecutionState使用）

---

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

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

### 推奨読解順序

#### Step 1: Notificationsモジュールを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | notifications.rb | `activesupport/lib/active_support/notifications.rb` | モジュール全体構造、instrumentメソッド |

**主要処理フロー**:
1. **行208-214**: `instrument(name, payload = {})` - エントリーポイント
2. **行209**: `notifier.listening?(name)` - 購読者存在チェック
3. **行210**: `instrumenter.instrument(name, payload)` - 実際の計測処理
4. **行212**: 購読者なしの場合はブロックのみ実行

#### Step 2: Instrumenterクラスを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | instrumenter.rb | `activesupport/lib/active_support/notifications/instrumenter.rb` | Instrumenterクラス |

**主要処理フロー**:
- **行54-66**: `instrument(name, payload = {})` - 計測実行
- **行55-56**: `build_handle`でHandleオブジェクト生成、`handle.start`
- **行58-62**: ブロック実行、例外時はpayloadに例外情報追加
- **行64**: `handle.finish`

#### Step 3: Eventクラスを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | instrumenter.rb | `activesupport/lib/active_support/notifications/instrumenter.rb` | Eventクラス |

**主要処理フロー**:
- **行106-122**: Eventクラスの初期化
- **行146-151**: `start!`でCPU時間、アロケーション記録開始
- **行154-159**: `finish!`で終了時刻、メトリクス記録
- **行198-200**: `duration`で実行時間をミリ秒で取得

#### Step 4: Fanoutクラスを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | fanout.rb | `activesupport/lib/active_support/notifications/fanout.rb` | Fanoutクラス（デフォルトnotifier） |

**主要処理フロー**:
- **行69-84**: `subscribe(pattern, callable, monotonic:, &block)` - 購読登録
- **行286-296**: `build_handle(name, id, payload)` - Handleオブジェクト生成
- **行332-334**: `listening?(name)` - 購読者存在チェック

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

```
ActiveSupport::Notifications.instrument(name, payload, &block)
    │
    ├─ notifier.listening?(name) (行209)
    │      └─ Fanout#listening? (fanout.rb 行332-334)
    │
    └─ instrumenter.instrument(name, payload, &block) (行210)
           │
           ├─ Instrumenter#instrument (instrumenter.rb 行54-66)
           │      │
           │      ├─ build_handle(name, payload) (行55)
           │      │      └─ @notifier.build_handle(name, @id, payload)
           │      │             └─ Fanout#build_handle (fanout.rb 行286-296)
           │      │                    └─ Handle.new(...)
           │      │
           │      ├─ handle.start (行56)
           │      │      └─ 購読者グループへstart通知
           │      │
           │      ├─ yield payload (行58)
           │      │      └─ ユーザーコード実行
           │      │
           │      ├─ rescue Exception => e (行59-62)
           │      │      └─ payload[:exception] = [e.class.name, e.message]
           │      │
           │      └─ handle.finish (行64)
           │             └─ 購読者グループへfinish通知
           │                    └─ subscriber.call(event) or subscriber.call(name, start, finish, id, payload)
           │
           └─ registry[notifier] (行269-271)
                  └─ Instrumenter.new(notifier)
```

### データフロー図

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

name ────────────────────▶ listening? ───▶ 購読者存在チェック

payload ─────────────────▶ build_handle ───▶ Handle/Event

                          │
                          ▼
                   handle.start
                          │
                          ▼
               ┌─────────────────────┐
               │   yield payload     │
               │   (ユーザーコード)   │
               └─────────────────────┘
                          │
                          ▼
                   handle.finish
                          │
                          ▼
               ┌─────────────────────┐
               │   Subscribers       │
               │   (LogSubscriber等) │
               └─────────────────────┘
                          │
                          ▼
               Event {
                 name: "...",
                 duration: 10.5,
                 payload: {...},
                 allocations: 100
               }
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| notifications.rb | `activesupport/lib/active_support/notifications.rb` | ソース | Notificationsモジュール |
| instrumenter.rb | `activesupport/lib/active_support/notifications/instrumenter.rb` | ソース | Instrumenter、Eventクラス |
| fanout.rb | `activesupport/lib/active_support/notifications/fanout.rb` | ソース | Fanout（デフォルトnotifier） |
| log_subscriber.rb | `activesupport/lib/active_support/log_subscriber.rb` | ソース | ログ出力購読者基底クラス |
