ログ設計:基礎から応用まで
1. はじめに
なぜログは重要なのか?
ログは現代のソフトウェアシステムにおいて欠かせない要素です。適切に設計されたログシステムは以下を可能にします。
- システムの稼働状態をリアルタイムで監視する
- バグや障害を素早く発見・修正する
- パフォーマンスのボトルネックを特定し最適化する
- ビジネス指標を継続的に把握する
しかし、ログ設計が不適切な場合、次のような問題が発生します。
- オーバーロギング:不要なログが大量に生成され、重要な情報が埋もれる
- 情報不足:障害発生時にデバッグに必要なデータが記録されていない
- 非一貫性:プラットフォームやサービスによってログ形式がバラバラで分析が困難
この記事の目的
本記事では、レストランの販売管理システム(注文受付・決済・在庫管理・デリバリー管理など)を題材にログ設計の方法を、実際のサンプルコードと具体例を交えながら解説します。
2. ログ設計の4つのコア原則
一貫性
JSON Schemaを使い、全レイヤー(BE / FE / Batch など)で同一のログ構造を強制します。
例えば、log_id の命名規則は <CATEGORY>_<NO>(_<LAYER>)? 形式(例:ORDER_001_FE、AUTH_002)とし、全チームで統一します。
可読性
ログメッセージはテンプレート化し、誰が読んでも意味がわかる形式にします。
- ❌ 悪い例:
"error occurred" - ✅ 良い例:
"Database connection failed to 'restaurant_db' at postgres:5432 - Connection timeout - Retry: 3/5"
有用性
ログは「記録すること」が目的ではなく「問題解決に使えること」が目的です。1つのログエントリで以下を答えられる必要があります。
- 何が起きたか(
message) - いつ起きたか(
timestamp) - どこで起きたか(
layer,application,hostname) - 誰に影響したか(
user_id) - どの処理に紐づくか(
trace_id)
セキュリティ
PII(個人識別情報)や金融などのデータは、そのままログに記録してはいけません。マスキングルールを定め、チーム全体で守ります。
| フィールド | マスキング方法 |
|---|---|
email |
ad***@restaurant.com |
credit_card |
****-****-****-1234 |
device_token / fcm_token
|
先頭8文字のみ表示 |
3. ログ実例集(販売管理システムより)
設計の詳細に入る前に、まず「ログがどのように見えるか」を確認しておきましょう。具体的なイメージを持っておくと、後続のセクションで解説する構造・レベル・テンプレートの必要性が理解しやすくなります。
ここでは、レストランの販売管理システムで実際に発生するシナリオから2パターンを紹介します。このログ構成で、5W1Hの特定が可能かどうかを確認してみましょう。
例①:新規注文作成(iOS) — ORDER_001_FE [INFO]
{
"timestamp": "2025-12-24T10:30:45.123Z",
"log_id": "ORDER_001_FE",
"level": "INFO",
"application": "restaurant-app",
"layer": "fe",
"category": "Order_Management",
"message": "New order #ORD-2025-001 created by customer_123 for $25.50 with 2 items - Payment: Credit Card",
"trace_id": "a1b2c3d4e5f6789012345678901234ab",
"user_id": "customer_123",
"context": { "order_id": "ORD-2025-001", "amount": 25.50, "items": 2, "payment_method": "Credit Card" },
"environment": "production"
}
例②:DB接続失敗 — DB_004 [ERROR]
{
"timestamp": "2025-12-24T11:00:05.000Z",
"log_id": "DB_004",
"level": "ERROR",
"application": "order-api",
"layer": "be",
"category": "Database",
"message": "Database connection failed to 'restaurant_db' at postgres:5432 - Connection timeout - Retry: 3/5",
"trace_id": "d4e5f6789012345678901234abcdef",
"context": { "database_name": "restaurant_db", "host": "postgres", "port": 5432, "retry_count": 3 },
"environment": "production"
}
4. ログの標準構造
実例を見てわかるように、どのログにも timestamp・log_id・level・trace_id などの共通フィールドが含まれています。これらを「なんとなく似た形」で実装するのではなく、JSON Schema で明示的に定義・強制することが重要です。
レストランの販売管理システムでは、注文・決済・在庫・デリバリーなど複数のサービスが連携して動作しています。各サービスがバラバラなフォーマットでログを出力すると、障害発生時に「どのサービスで何が起きたのか」をつなぎ合わせることが困難になります。統一した構造を定義しておくことで、ログの自動バリデーション・コード生成・横断検索がすべて同じ前提で動くようになります。
仮に必須とするフィールド
すべてのレイヤーで共通して記録すべき8つのフィールドです。
| フィールド | 型 | 説明 |
|---|---|---|
timestamp |
string (ISO 8601) | イベント発生時刻(UTC) |
log_id |
string | ログの一意識別子(例:ORDER_001_FE)、命名規則は CATEGORY_001(_LAYER)?
|
level |
enum |
INFO / WARN / ERROR
|
layer |
enum |
be / fe / batch など、処理が発生したレイヤーを示す |
application |
string | APIまたはアプリケーション名(例:order-api、restaurant-app) |
category |
enum |
Order_Management / Database / Authentication など |
message |
string | ログメッセージ(最大1000文字) |
trace_id |
string | サービス間のリクエストを紐付ける32文字のhex ID |
任意フィールド
user_id、session_id、ip_address、user_agent、environment、service_version、hostname、そして context(ユースケースごとに自由に拡張できるオブジェクト型)
5. ログレベルのルール
ログ構造を定義しても、「このイベントは INFO か WARN か ERROR か」という判断が人によってバラバラだと意味がありません。たとえば「注文キャンセル」を ERROR にしてしまうと、アラートが頻発して本当に重要な障害が埋もれてしまいます。逆に「決済失敗」を INFO にしてしまうと、売上に直結する問題を見逃すリスクがあります。
レストランの販売管理システムでは、注文・決済・在庫・認証など多岐にわたるイベントが発生します。それぞれに適切なレベルを割り当てるための共通ルールをチーム全体で合意・文書化しておくことが不可欠です。
原則
「レベルはシステムの状態を表すものであり、開発者の感覚ではない」
| レベル | 判断基準 | アラート |
|---|---|---|
| INFO | 通常の業務フローが正常に進行している | ❌ 不要 |
| WARN | 異常があるが、システムは処理を継続できる | ✅ 発生頻度に基づきアラートを通知 |
| ERROR | ユーザーへの直接影響、またはデータ不整合 | ✅ 即時対応が必要 |
よくある誤用例
| ❌ 誤り | ✅ 正しい | 理由 |
|---|---|---|
ERROR: 注文フォームの入力ミス |
WARN: 注文フォームの入力ミス |
ユーザー操作ミスはシステム障害ではない |
INFO: 決済失敗 |
ERROR: 決済失敗 |
ユーザーへの直接影響・売上損失 |
WARN: DB接続失敗 |
ERROR: DB接続失敗 |
システム全体が止まる可能性がある |
ERROR: キャッシュMISS |
WARN: キャッシュMISS |
DBへのフォールバックで処理継続できる |
6. メッセージテンプレートの設計
ログ構造とレベルが決まっても、message フィールドの中身が開発者ごとにバラバラでは、アラートルールや自動解析が機能しません。たとえばDBの接続失敗というログひとつをとっても、販売管理システム内の複数のサービス(注文サービス・在庫サービス・決済サービス)がそれぞれ違う書き方をしていると、「今どのサービスでDBエラーが何件発生しているか」を集計することができません。
メッセージテンプレートはこの問題を解決するための仕組みです。テンプレートを log_id に紐付けてファイルで一元管理することで、全チームが同じフォーマットでログを出力できるようになります。
なぜテンプレートが必要か?
たとえばDB接続失敗(DB_004)というイベントひとつでも、テンプレートなしだと次のようにバラバラになります。
- 開発者A:
"DB timeout" - 開発者B:
"database connection error" - 開発者C:
"Cannot connect to postgres"
テンプレートを使えば、どのサービスからでも統一された形式で出力されます。
"Database connection failed to 'restaurant_db' at postgres:5432 - Connection timeout - Retry: 3/5"
これにより、ログ集計・アラートルール・ダッシュボード作成がすべてこの一行のパターンを前提に設計できます。
テンプレート構造
各テンプレートは log_id・level・プレースホルダー付き message・required_fields の4つで構成されます。販売管理システムのDBクエリログを例にすると:
{
"log_id": "DB_001",
"level": "INFO",
"message": "SELECT query executed on '{table_name}' table - Duration: {execution_time_ms}ms - Rows: {affected_rows} - User: {user_id}",
"required_fields": ["table_name", "execution_time_ms", "affected_rows", "user_id"],
"example": "SELECT query executed on 'orders' table - Duration: 45ms - Rows: 150 - User: order-service"
}
実装例(Python)
def create_log_entry(template_id: str, data: dict, platform: str = None) -> dict:
template = get_template(template_id, platform)
# 必須フィールドの検証
for field in template["required_fields"]:
if field not in data:
raise ValueError(f"Required field missing: {field}")
# メッセージ生成
message = template["message"].format(**data)
return {
"timestamp": datetime.utcnow().isoformat() + "Z",
"log_id": template["log_id"],
"level": template["level"],
"message": message,
"trace_id": data.get("trace_id", generate_trace_id()),
"context": data
}
7. アラート戦略マトリクス
ログを収集するだけでは不十分です。問題が発生したときに誰が・いつ・どのように通知を受けるかを事前に設計しておかないと、深夜のDB障害やSQLインジェクション攻撃に気づくのが遅れ、売上損失やセキュリティ侵害につながります。
しかし、すべてのログにアラートを設定すると「アラート疲れ」が起きます。実際に販売管理システムのログを分類すると、全体の約3分の2はアラート不要です(監査・デバッグ・ダッシュボード用)。残りの3分の1に絞って適切なアクションを定義することが重要です。
アラート戦略マトリクスは、各ログIDに対して「どのアクションを取るか・どんな条件でトリガーするか」をチーム全体で合意した設計書です。PrometheusやDatadogなど特定のツールに依存せず、どの監視ツールを使う際もこのマトリクスを信頼できる唯一の情報源として参照します。
アクション分類
| アクション | 意味 | 通知チャンネル |
|---|---|---|
| 🔴 ALERT_IMMEDIATE | 1回発生で即時アラート | PagerDuty + Slack |
| 🟠 ALERT_THRESHOLD | 頻度が閾値を超えたらアラート | Slack + Email |
| 🟡 ALERT_TREND | 長期的な悪化傾向でアラート | Email + Dashboard |
| 🟢 DASHBOARD_ONLY | ダッシュボード表示のみ | Dashboard |
| ⚫ NO_ACTION | 監査・デバッグ用にログ保存のみ | — |
カテゴリ別アラート設定例
認証・セキュリティ
| Log ID | 説明 | レベル | アクション | トリガー条件 |
|---|---|---|---|---|
| AUTH_002 | ログイン失敗 | WARN | 🟠 ALERT_THRESHOLD | 5分で30回超 |
| SECURITY_002 | SQLインジェクション | ERROR | 🔴 ALERT_IMMEDIATE | 1回発生 |
データベース
| Log ID | 説明 | レベル | アクション | トリガー条件 |
|---|---|---|---|---|
| DB_001 | 通常クエリ | INFO | ⚫ NO_ACTION | — |
| DB_004 | DB接続失敗 | ERROR | 🔴 ALERT_IMMEDIATE | 1回発生 |
注文・決済
| Log ID | 説明 | レベル | アクション | トリガー条件 |
|---|---|---|---|---|
| ORDER_004 | 決済失敗 | ERROR | 🟠 ALERT_THRESHOLD | 10分でエラー率 > 5% |
| PAYMENT_001 | 決済成功 | INFO | 🟢 DASHBOARD_ONLY | — |
8. まとめ
優れたログ設計は単なる記録ではなく、障害検出と復旧、そしてビジネス観点での可視化を支えるインフラです。
本稿で示したように、共通スキーマ・テンプレート・明確なレベル運用・アラート戦略を組み合わせることで、ノイズを減らしつつ重要事象に速やかに対応できる体制が整います。
実運用では、ルールをチームで合意して文書化し、定期的に見直すことで、ログが単なるログ以上の価値を持ち続けられます。
Discussion