第8章:良いログ / ダメログ 😇😱(検索できるログ)
この章のテーマはひとことで言うと **「未来の自分(or チーム)が、迷子にならないログ」**だよ〜👀🧭✨
0) まず最重要:ログは“書く”より“探す”が本番 🔎✨

障害対応や調査って、だいたいこうなるよね👇
- いま何が起きてる?(ざっくり把握)
- どこで?(入口?DB?外部API?)
- なぜ?(原因の仮説)
- 証拠は?(ログで裏取り)
このときログが 検索できないと、調査が「運ゲー」になる😱🎰 逆に検索できるログなら、“すぐ絞れる→すぐ辿れる” から超ラク😊✨
1) ダメログあるある 😱🪵(調査を殺すログ)
ダメ①:気持ちログ(情報が無い)
- 「処理失敗しました」
- 「エラーです」
- 「なんかおかしい」
👉 何が / どこで / どの入力で / どのユーザーで / どのリクエストで が無いと詰む💥
ダメ②:文章に全部埋め込む(検索しにくい)
User 12345 purchased item 98765 price 1200 ...
👉 数字が埋まってると、あとで 「userId=12345」みたいに機械的に絞れない😵💫
ダメ③:フォーマットが毎回違う(揺れログ)
userId/user_id/uidが混在requestId/reqId/ridが混在
👉 同じ意味なのに検索が分裂して地獄👿
ダメ④:1行に全部盛り(ノイズ)
- 成功でも失敗でも同じ熱量で長文ログ
- 毎回巨大なオブジェクトを
JSON.stringify()して出す
👉 必要なログがノイズに埋もれる🫠
ダメ⑤:秘密や個人情報をそのまま出す(危険)
- パスワード、トークン、Cookie、認証ヘッダ
- メール、住所、カード、個人特定ID など
👉 事故ったときの被害がヤバい⚠️ (しかもログ基盤に残り続ける…😱)
2) 良いログの合言葉:イベント + 文脈 + 結果 ✅✨
ログを「検索できる形」にするコツはこれ👇
✅ (A) イベント名を付ける(何が起きた?)
例:
order.create.startedorder.create.succeededorder.create.failedexternal.payment.requestdb.query.slow
👉 これだけで 検索の入口が爆速🚀
✅ (B) 文脈は“フィールド”で持つ(あとで絞る用)
例:
requestId(相関ID)trace.id/span.id(トレース連携)userId(※匿名化/ハッシュ推奨)orderIdhttp.method,http.routeduration_mserror.type,error.message
ECS でも trace.id はトップレベルで扱う前提になってるよ(ネストじゃない)📌 (Elastic)
OpenTelemetry 側もログとトレースを結びたいので、trace/span のIDをログに載せる運用を推してるよ🔗 (OpenTelemetry)
✅ (C) 結果を“判定できる形”で残す(成功/失敗/遅い)
- 成功なら:
status: "ok" - 失敗なら:
status: "error"+error.* - 遅いなら:
duration_ms(数値!)
👉 「遅いの何件?」みたいな集計もできる📈✨
3) “検索できるログ”の最小セット(まずはこれだけでOK)🧩✨
最初は欲張らずに、この 8点セット だけ固定しよ👇
event(イベント名)level(info/warn/error…)message(人間向け一言)requestId(ログをつなぐID)trace.id(後でトレースと合流)service(サービス名)env(prod/stg/dev など)version(ビルド/リリース識別)
OpenTelemetry JS は Traces/Metrics は安定寄りだけど、Logs はまだ “Development” 扱いなので、今は「ログ自体をキレイに作る」ほうが勝ちやすいよ😊 (OpenTelemetry)
4) 例で体感!ダメログ → 良いログ ✍️✨
ケース①:外部APIが失敗した
😱 ダメ
決済に失敗しました
😇 良い
- event:
payment.charge.failed - 追加フィールド:
requestId,orderId,provider,http.status_code,duration_ms,error.type
👉 これで「どの注文が」「どの決済会社で」「何msで」「何エラーか」が一瞬で分かる🔎✨
ケース②:遅い処理を見つけたい
😱 ダメ
遅いです
😇 良い
- event:
db.query.slow - 追加フィールド:
duration_ms(数値!)db.operationdb.table(※テーブル名はOK、SQL全文は慎重に)
👉 event=db.query.slow AND duration_ms>1000 とかで狩れる🗡️✨
5) TypeScriptの“良いログ”の形(軽く雰囲気)🌲✨
JSONログを作るなら、Node界隈だと Pino が定番で強いよ(JSON前提&速い)🌲 2026-01時点でも普通に現役で、最新版リリースも動いてるよ📦✨ (GitHub)
import pino from "pino";
export const logger = pino({
level: process.env.LOG_LEVEL ?? "info",
base: {
service: "example-api",
env: process.env.NODE_ENV ?? "dev",
version: process.env.APP_VERSION ?? "dev",
},
});
// 例:良いログ(イベント + 文脈 + 結果)
logger.info(
{
event: "order.create.succeeded",
requestId: "req-123",
"trace.id": "4bf92f3577b34da6a3ce929d0e0e4736",
"span.id": "00f067aa0ba902b7",
orderId: "ord-999",
duration_ms: 42,
status: "ok",
},
"order created"
);
trace.id/span.idをログに入れると、後でトレースと合流できて調査が超ラク🔗 (OpenTelemetry)
6) 秘密をログに出さない!“マスキング”の基本 🫣🔒✨
Pino には **redact(伏せ字)**があるよ👍
ドキュメントでも redact は公式に案内されてる📌 (GitHub)
import pino from "pino";
export const logger = pino({
redact: [
"req.headers.authorization",
"req.headers.cookie",
"password",
"token",
],
});
👉 認証系は 絶対にそのまま出さないのが基本だよ⚠️✨
7) エラーログの「最低限」テンプレ 🧯💥
エラーは “後で調べる” の主役! OpenTelemetry の例外系セマンティクスも、ログに例外属性を載せる方向だよ🧾 (OpenTelemetry)
おすすめ最低セット👇
event:*.failedstatus:"error"error.typeerror.messageerror.stack(長いので必要なときだけでもOK)requestIdtrace.id/span.id
8) ミニ演習:ダメログを良いログに変換しよう ✍️🌟
お題(ダメログ)
ログイン失敗決済に失敗しましたDBが遅い画像アップロード失敗外部APIエラー
ルール
- それぞれ event名 を付ける
- requestId を必ず入れる
- 「絞れるフィールド」を3つ以上追加する
解答例(ひとつだけ見せるね👀✨)
2. 決済に失敗しました →
event: "payment.charge.failed"requestIdorderIdproviderhttp.status_codeduration_mserror.type,error.message
(残りはあなたが作ってOK!AIに添削させると爆速🤖✨)
9) AIに頼るときの“良いお願いの仕方”🤖📝✨
コピペで使えるよ👇
「次のログ文言を、検索しやすい構造化ログに直して。 event名も付けて、requestId/trace.id/span.id を前提に、絞り込み用フィールドを最低3つ入れて。 個人情報や秘密(token/password/auth header)は絶対に出さないで。」
10) 章末チェック ✅💮(ここだけ押さえたら勝ち)
- ログは「書く」より「探す」が本番🔎
- イベント名がある(
xxx.yyy.succeededみたいに)🏷️ - 文脈は文章じゃなく フィールドで持ってる🧩
-
requestIdは必須(つなぐ)🔗 -
trace.id/span.idを入れると将来めっちゃ強い🔗 (OpenTelemetry) - 秘密は
redactなどで伏せる🔒 (GitHub)
次の第9章(構造化ログ入門🧱✨)では、いま作った「良いログの型」を、JSONとして安定運用する設計に落としていくよ〜😊📦✨