この記事の対象読者
- 問い合わせ調査や運用調査を担当し始めた人
- Cloud Loggingでログを見る機会があるけれど、検索の仕方にまだ慣れていない人
-
console.log("エラー")から少しだけ先に進みたい人
本題に入る前に
この話は、問い合わせ対応や運用調査でログを見る機会がある人、またはこれからログ設計に関わるWebエンジニアを想定しています。CSやサポートから「このユーザーで何が起きたか確認してほしい」と相談される立場の人にも役立つ内容です。
最近、業務アプリの運用を考える中で、「機能を作ること」と同じくらい「あとから何が起きたか分かること」が大事だと感じる場面が増えています。画面上ではただ「失敗しました」と見えていても、裏側では外部APIが遅かったのか、入力値が不正だったのか、権限で弾かれたのか、いろいろな可能性があります。
問い合わせを受けたときに、「このユーザーだけ」「この時間帯だけ」「この操作だけ」で見たいこと、ありますよね。ログが文章だけだと、目で追うしかなくなってつらいです。最初から検索しやすい形にしておくと、あとで自分を助けてくれます。
問い合わせを受ける側からすると、最初に欲しいのは完璧な原因ではなく、調査の入口です。「誰の操作か」「いつ起きたか」「どの処理か」が分かるだけでも、見るべき範囲はかなり狭くなります。この記事では、その入口を作るためのログ設計を扱います。
ログはあとから探すための作業メモ
Webサービスを運用していると、ユーザーや社内の担当者から「うまく動きません」「画面に反映されません」といった問い合わせが来ることがあります。
このとき、エンジニアはまず「本当にシステムで何が起きたのか」を確認します。相手の説明だけでは、通信エラーなのか、入力ミスなのか、外部サービス側の失敗なのか、まだ分かりません。
そこで見るのがログです。ログは、システムが裏側で残している作業メモのようなものです。飲食店でたとえるなら、注文が入った時刻、担当者、調理状況、エラーになった理由を厨房側で記録しておくイメージです。
ただし、メモが「なんか失敗した」だけだと後から探せません。「何時に」「誰の」「どの処理が」「どう失敗したか」が分かる形で残っていると、調査が一気に進めやすくなります。
Cloud Loggingは、そのログを集めて検索するためのGoogle Cloudのサービスです。そして構造化ログは、ログをただの文章ではなく、検索しやすい項目つきのデータとして残す考え方です。
ほかにもログを保存するサービスやライブラリはありますが、この記事ではGoogle Cloudを使う現場でよく出てくるCloud Loggingを例にします。大事なのはサービス名を覚えることより、「後から探すために、ログに項目を持たせる」という考え方です。
まずはJSONで1行出す
今回は架空の予約同期APIを例にします。
function writeLog(payload) {
console.log(JSON.stringify(payload));
}
async function syncReservation({ userId, organizationId, requestId }) {
const startedAt = Date.now();
try {
await new Promise((resolve) => setTimeout(resolve, 100));
writeLog({
severity: "INFO",
message: "reservation_sync_succeeded",
operation: "reservation_sync",
status: "success",
userId,
organizationId,
requestId,
durationMs: Date.now() - startedAt,
});
} catch {
writeLog({
severity: "ERROR",
message: "reservation_sync_failed",
operation: "reservation_sync",
status: "failure",
errorCode: "UNKNOWN_ERROR",
userId,
organizationId,
requestId,
durationMs: Date.now() - startedAt,
});
}
}
syncReservation({
userId: "user_001",
organizationId: "org_001",
requestId: "req_001",
});
ローカルではこう実行できます。
node reservation-sync.js
出力例です。
{"severity":"INFO","message":"reservation_sync_succeeded","operation":"reservation_sync","status":"success","userId":"user_001","organizationId":"org_001","requestId":"req_001","durationMs":101}
入れておくと探しやすい項目
最初から完璧なログ設計を目指さなくて大丈夫です。まずはこのあたりがあるだけで、問い合わせ調査がかなり進めやすくなります。
| 項目 | 使いどころ |
|---|---|
operation |
どの処理かを絞る |
status |
成功か失敗かを見る |
userId |
問い合わせ対象のユーザーで探す |
organizationId |
組織やテナント単位で探す |
requestId |
1回のリクエストを追う |
errorCode |
同じ種類の失敗を探す |
durationMs |
遅い処理に気づく |
Cloud Loggingでの検索例
Google Cloudでは、JSONで取り込まれたログを jsonPayload のフィールドとして検索できます。
予約同期の失敗だけ見たい場合です。
jsonPayload.operation="reservation_sync"
jsonPayload.status="failure"
特定ユーザーのログを見たい場合です。
jsonPayload.userId="user_001"
タイムアウト系のエラーだけ探したい場合です。
jsonPayload.errorCode="EXTERNAL_API_TIMEOUT"
ログがただの文章だけだと、毎回がんばって文字列検索することになります。JSONでキーを決めておくと、あとから落ち着いて絞り込めます。
注意したいこと
ログに何でも入れるのは避けます。
たとえば、認証トークン、APIキー、個人情報、メール本文、自由入力の本文などは、ログに出す前に本当に必要か確認したほうが安全です。
問い合わせ調査では「誰の、どの処理で、どんな種類の失敗が起きたか」まで分かれば十分なことも多いです。まずはIDとエラー種別を残すところから始めるのがよいと思います。
まとめ
構造化ログは、特別な監視基盤を作る前に始められる小さな改善です。
ログを見る未来の自分やチームメンバーが、userId や operation で検索できるようにしておく。それだけでも、問い合わせ調査の最初の一歩がかなり楽になります。