第14章:ログ・トレースの“残し方”を決める🧯🧾
この章はひとことで言うと、**「AIの挙動を“あとから説明できる”ようにする設計」**を固める回です🙂✨ AIは、同じ入力でも揺れたり、外部要因(モデル更新・混雑・タイムアウト)で結果が変わったりします。だからこそ ログ(出来事の記録)+トレース(処理の足跡) が超大事です🧭
1) まず結論:ログ設計は「3つの箱」で考える📦📦📦

✅ 箱A:Cloud Logging(まずここが本丸)🪵
- 検索できる・集計できる・アラートにできる
- Cloud Functions では logger SDK で「構造化ログ(JSON)」を出すのが基本です。(Firebase)
✅ 箱B:Cloud Trace(“流れ”を追う)🧵
- 1回のリクエストが、どの順で何msかかったか(スパン)を追える
- ログとトレースは Trace ID / Span ID で紐づけできます。(Google Cloud Documentation)
✅ 箱C:アプリ用の“履歴DB”(必要なら)🗂️
- ユーザーが「前の結果を見返す」用途
- ただし 個人情報が混ざりやすいので、ここは“最小限”が鉄則🚫🧠(Cloud Logging側とは役割が違う)
2) 「何を残す?」最小で強いログ項目セット💪🧾

AIワークフロー(Flow)で 最低限これだけ残すと、だいたい復旧できます👇
🧩 共通(全ログに付けるタグ)
requestId:1回の実行ID(自分でUUID発行でOK)flowName:どのFlowかflowVersion:プロンプトやロジックの版(例:v3)actor:呼び出し元(uidHashとか)release:デプロイ版(Gitの短いSHAでもOK)
⏱️ 観測(事故調査で強い)
latencyMs:全体の処理時間step:どの段階か(start/model_call/end/error)retryCount:再試行回数model:モデル名(例:Gemini/Imagenのどれ) ※Firebase AI Logic は Gemini/Imagen を扱えます。(Firebase)
🚫 絶対にログに入れない(最重要)🔒
- 生の本文(ユーザーの入力原文)
- メール/電話/住所/氏名などのPII
- トークン、Cookie、Authorizationヘッダ、App Checkトークン
- “うっかりコピペした秘密情報”全般
代わりに👇
inputChars(文字数)inputHash(ハッシュ)inputPreview(先頭20文字だけ、さらに伏せ字) みたいな“調査に必要な最低限”にします🙂
3) 手を動かす:Functions(TypeScript)で「構造化ログ」を標準化する🛠️✨
Cloud Functions の logger SDK は **構造化ログ(JSON)**を簡単に出せます。(Firebase) ここでは「Flow開始→モデル呼び出し→終了」を 同じ形で出すテンプレを作ります💡
3-1. ログ用ユーティリティ(コピペOK)📎

// functions/src/observability.ts
import * as logger from "firebase-functions/logger";
import { randomUUID, createHash } from "crypto";
export type LogCtx = {
requestId: string;
flowName: string;
flowVersion: string;
uidHash?: string; // 生uidは避ける
release?: string; // 例: Git SHA
};
export function newRequestId() {
return randomUUID();
}
export function hashUid(uid: string) {
return createHash("sha256").update(uid).digest("hex").slice(0, 16);
}
// ざっくり伏せ字(PII完全検知は難しいので、まずは“入れない設計”が基本)
export function safePreview(text: string, max = 20) {
const t = (text ?? "").replace(/\s+/g, " ").trim();
const head = t.slice(0, max);
// メールっぽいものは伏せる(簡易)
return head.replace(/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g, "***@***");
}
export function logInfo(event: string, ctx: LogCtx, fields: Record<string, unknown> = {}) {
logger.info(event, { ...ctx, ...fields }); // ←構造化ログ(jsonPayload)
}
export function logWarn(event: string, ctx: LogCtx, fields: Record<string, unknown> = {}) {
logger.warn(event, { ...ctx, ...fields });
}
export function logError(event: string, ctx: LogCtx, err: unknown, fields: Record<string, unknown> = {}) {
const message = err instanceof Error ? err.message : String(err);
logger.error(event, { ...ctx, errorMessage: message, ...fields });
}
3-2. Flow側で「start/end/error」を必ず出す📌

// functions/src/index.ts(例)
import { onCallGenkit } from "firebase-functions/v2/https";
import { newRequestId, hashUid, logInfo, logError, safePreview } from "./observability";
// 例:onCallGenkitで公開しているFlowを“呼ぶ側”のラッパとして考えてOK
export const ngCheck = onCallGenkit(
{ region: "asia-northeast1" },
async (req) => {
const requestId = newRequestId();
const ctx = {
requestId,
flowName: "ngCheck",
flowVersion: "v1",
uidHash: req.auth?.uid ? hashUid(req.auth.uid) : undefined,
release: process.env.GIT_SHA,
};
const t0 = Date.now();
const input = String(req.data?.text ?? "");
logInfo("flow_start", ctx, {
inputChars: input.length,
inputPreview: safePreview(input),
});
try {
// ここで実際のAI処理(Genkit Flow内のLLM呼び出し等)
// const result = await runNgCheckFlow(input);
const result = { verdict: "OK", reason: "sample" };
logInfo("flow_end", ctx, {
latencyMs: Date.now() - t0,
verdict: result.verdict,
});
return result;
} catch (e) {
logError("flow_error", ctx, e, { latencyMs: Date.now() - t0 });
throw e;
}
}
);
ポイント👇
- “成功ログ”と“失敗ログ”が同じrequestIdで追える🧵
- 入力は「文字数+プレビュー(伏せ)」まで
- これだけで問い合わせ対応がめちゃ楽になります🙂✨
4) トレースも欲しい:GenkitのテレメトリをGoogle Cloudへ🧭

Genkit には テレメトリ(ログ/メトリクス/トレース)をGoogle Cloudへ出す仕組みがあります。(genkit.dev) 本番で「どのステップが遅い?」を詰めるなら、これが効きます🔥
最小イメージ(初期化時にON)👇
import { enableGoogleCloudTelemetry } from "@genkit-ai/google-cloud";
enableGoogleCloudTelemetry({
// 必要なら projectId 等
});
そして Cloud Logging 側は、トレースとログを紐づけできるので、ログからトレースへ飛べる導線を作れます。(Google Cloud Documentation)
5) “見る”のも大事:ログの確認ルートを2本にする👀🧪

ルートA:Cloud Logging UI(検索・集計に強い)🔎
- 構造化ログだと
jsonPayload.flowName="ngCheck"みたいに フィールド検索ができます。(Google Cloud Documentation)
ルートB:CLIでサッと見る(作業中に強い)⚡
firebase functions:logでログ表示できます。(Firebase)
例👇
firebase functions:log --only ngCheck
6) 事故らないための「ログ運用ルール」ミニテンプレ📋🧠
🔥 ルール1:ログレベルの基準
info:開始/終了/分岐(OK/NG/要レビュー)warn:リトライ発生、外部APIが遅い、入力が怪しいerror:例外、タイムアウト、プロバイダ障害
🔥 ルール2:保持期間(Retention)は“分ける”
Cloud Logging はログバケット等で保持期間を変えられます。(Google Cloud)
- ふだんのinfo:短め
- error/warn:長め みたいに分けるとコストも事故も減ります💸
🔥 ルール3:監査ログは別物(セキュリティの基礎)
管理操作・権限変更は Cloud Audit Logs(監査ログ)側で考えるのが基本です。(Google Cloud Documentation)
7) Firebase AI Logicも絡める:乱用・コスト監視のログ設計🎛️💸

AI Logic は ユーザー単位のレート制限(デフォルト 100 RPM/ユーザー)があり、プロダクションではクォータ確認や監視が重要です。(Firebase) なのでログにも👇を入れると運用が楽です。
aiAllowed: true/false(Remote Config等で止めたか)rateLimited: true/falseplanTier: free/pro(課金差があるなら)estimatedCostBucket: low/med/high(ざっくりでもOK)
さらに、コスト/使用量の監視ガイドもあります。(Firebase)
8) 開発AIで“ログ調査”を爆速にする🚀(Gemini CLI / Console)
💻 Gemini CLIで「ログクエリ作って」→そのまま貼る
Gemini CLI はターミナルで調査・デバッグを支援できます。(Google Cloud Documentation) おすすめプロンプト例👇
- 「Cloud Loggingで
flowName=ngCheckのflow_errorだけ抜くクエリを書いて」 - 「
requestIdをキーに、開始→終了まで追えるクエリにして」 - 「jsonPayloadのフィールドを前提に、集計(エラー率/平均latency)案を出して」
🧩 Gemini in Firebaseで「このエラー何?」をコンソールで聞く
Firebase コンソール側の支援も使えます。(Firebase)
ただし、AIにログ本文を丸投げしない(PII混入の可能性)で、まずは requestId や errorMessage 程度に絞るのが安全です🙂🛡️
ミニ課題🧩📝
次を満たす「ログ設計メモ」を作って、コードにも反映してみてください✨
requestId / flowName / flowVersion / uidHash / latencyMsを必ず出す- 入力本文は残さず、
inputChars / inputPreview(伏せ) / inputHashにする flow_start / flow_end / flow_errorの3種類を必ず出す- Cloud Logging UI で
requestId検索して、1回の実行が追えるのを確認👀
チェック✅
- 問い合わせが来たとき「いつ・誰が・どのFlowで・何秒かかって・どう失敗した」が requestIdで1発で追える?
- ログに“秘密”や“本文”が入らない設計になってる?🔒
- 成功時のログもある?(失敗だけだと原因の比較ができない)🙂
必要なら、次の第15章(Evaluate)に繋げやすいように、**「評価用データセットID」「期待ラベル(OK/NG)」「判定結果」**までログ項目に入れる版も作れます📊🔥