# ログ運用仕様

Parky 全ランタイム（Astro SSG / Vite React SPA / Cloudflare Workers / Flutter）に
わたるログ出力の**共通スキーマと運用ルール**。

各ランタイムは異なるロガー実装を使ってよい。ただし**出力する JSON イベントは本文書のスキーマに従う**。
これにより将来どのログ集約基盤に送っても串刺し検索ができる。

---

## 1. 共通スキーマ

すべてのロガーは次の JSON を 1 イベント = 1 行で stdout に出す:

```json
{
  "level":   "debug|info|warn|error",
  "time":    "2026-04-17T10:00:00.123Z",
  "service": "admin|home|api|mobile",
  "env":     "dev|staging|prod",
  "msg":     "人が読める短い説明",
  "scope":   { "userId": "...", "requestId": "..." },
  "error":   { "name": "...", "message": "...", "stack": "..." },
  "extra":   { "任意のコンテキスト": "..." }
}
```

### 必須フィールド
| キー | 意味 |
| --- | --- |
| `level`   | ログレベル（下記 § 2） |
| `time`    | ISO 8601 UTC |
| `service` | どのランタイムから出たか |
| `env`     | 環境（`import.meta.env.MODE` 等から） |
| `msg`     | 1 行の説明文（日本語OK） |

### 任意フィールド
| キー | 意味 |
| --- | --- |
| `scope` | リクエスト/セッション全体を通したコンテキスト（user_id / trace_id 等） |
| `error` | `Error` オブジェクトのシリアライズ（`error` レベル時は推奨） |
| `extra` | その場限りの追加情報 |

**禁止**: PII（メール・氏名・電話）をそのまま出力しない。必要なら user_id のみ。

---

## 2. ログレベル

| level | 用途 | 本番での扱い |
| --- | --- | --- |
| `debug` | 開発時の詳細追跡 | 出力しない |
| `info`  | 重要な状態遷移（セッション開始、ジョブ完了） | 出力 |
| `warn`  | 想定内の異常（retry、フォールバック発動） | 出力 |
| `error` | 想定外のエラー（Sentry へも自動転送） | 出力 + Sentry |

---

## 3. 業務監査ログは分離する

**ロガー基盤と業務監査は別物**:

| 種類 | 保存先 | 目的 |
| --- | --- | --- |
| アプリログ（本ドキュメント） | stdout → 集約基盤 | 運用・障害対応 |
| ユーザー行動ログ | `user_activity_logs` テーブル | 分析・バッジ付与 |
| 管理者操作ログ | `admin_activity_logs` テーブル | 監査・変更追跡 |

**ロガーでアプリログを出す際に、業務監査テーブルにも書いてしまわないこと。**
業務監査は専用 API（`logAdminActivity()` 等）経由で明示的に行う。

---

## 4. ランタイム別の実装指針

### 4.1 admin / home（TypeScript ブラウザ + Astro SSR）

- **`@parky/logger` パッケージ**を使う（`web/packages/logger/`）
- ブラウザでは console に出力 + `error` は Sentry に自動転送
- Astro SSR（Node）も同じパッケージで動作
- 使用例:
  ```ts
  import { createLogger } from '@parky/logger';
  const log = createLogger({ service: 'admin', env: import.meta.env.MODE });
  log.info('ユーザー更新完了', { extra: { user_id } });
  log.error(err, { scope: { action: 'user.update_status' } });
  ```

### 4.2 Cloudflare Workers BFF（Hono）

- `api/src/lib/logger.ts` に薄いラッパーを置いて `console.log(JSON.stringify({...}))` で stdout 出力
- スキーマは本ドキュメントに準拠
- `wrangler.toml` で `[observability] enabled = true` を入れてあるので、Cloudflare Dashboard の Workers Observability から構造化ログとして検索可能
- Logpush を繋いで外部基盤（Datadog / Logtail 等）に流す設計も可能（未配線）

### 4.3 Flutter（Dart）

- `logger` パッケージ（Dart）を使用
- 本番クラッシュは Firebase Crashlytics に送る
- JSON 出力は dev では不要、本番でログ集約基盤にパイプするタイミングで検討

---

## 5. 転送先（バックエンド）

現時点:
- **Sentry**: `@sentry/react` 導入済み（DSN 未設定時は素通り）
- **stdout**: ブラウザコンソール / Cloudflare Pages 実行ログ / Cloudflare Workers Observability

将来（別タスク）:
- ログ集約: Datadog / Logtail / Grafana Loki 等を 1 つ選定し Cloudflare Logpush で流す
- `trace_id` を跨がせて OpenTelemetry 対応

---

## 6. 疎結合の原則

`@parky/logger` のコア実装は**転送先（Sentry / CloudWatch 等）を知らない**。
転送は **Transport 関数**として差し替え可能:

```ts
const log = createLogger({
  service: 'admin',
  env: 'prod',
  transports: [consoleTransport(), sentryTransport()],  // 後付けで追加
});
```

これにより:
- コアは依存ゼロで変更不要
- 転送先の切替はアプリ起動時の設定のみ
- テスト時は transports を空にできる

---

## 7. 命名規則（参考）

`msg` は短く動作・結果を書く:

```
good:  "駐車セッション作成失敗"
good:  "role.update_permissions 成功"
bad:   "エラーが発生しました"
bad:   "The user (id=abc) tried to update ..." ← 詳細は extra/scope へ
```

`scope.action` はドット区切りの `resource.verb` を推奨:
`user.update_status` / `role.update_permissions` / `vehicle.soft_delete`
