はじめに
先日、開発の中でクラウド環境(Cloud Logging上)のログを見ている際に
「ログがごちゃごちゃしてて見にくいなあ...」 と感じてました。
ログなんて一旦文字出力しておけばいいだろくらいに思ってましたが、もっと早く知っておけばよかったと思えるくらいには体験が良くなったので、記事として残してみようと思います。
前提
使用言語: Typescript
クラウド環境: Google Cloud (Cloud Run + Cloud Logging)
(すでにクラウド環境(Cloud Run等)にデプロイ済みで、Cloud Loggingとの接続も完了していることを前提)
デバッグを簡単にするお助けアイテム
その1. 構造化ロギング
クラウド環境でログを格段に見やすく、またデバッグを楽にするために
「構造化ロギング」 を採用します。
構造化ロギングというのは簡単に説明すると
ログの情報を単なる文字列ではなく、JSON形式のような構造を持ったデータとして出力すること
で、ログの検索やフィルタリングが格段にやりやすくなります。
ログ出力する際に ユーザーがログインしました
と出力するのではなく、以下のようにJSON形式で出力するようなイメージです。
{
"message": "ユーザーがログインしました",
"severity": "INFO",
"userId": "user-123",
"timestamp": "2025-08-20T12:00:00Z",
"requestId": "_QBNoVwge8AxqlwBI-yWm"
}
その2. リクエストIDの採用
2つ目のお助けアイテムとして、「requestId」というパラメータを構造化ログのメタデータとして追加します。
「requestId」を追加することで、
HTTPリクエストごとに ユニークなID(リクエストID) を付与することで、そのリクエストに関連するすべてのログを一括で追跡できるようにできます。
導入するメリット
1. ログに一貫性が生まれる
特にルールを設けずに開発者それぞれがconsole.log
を書いていくと、
console.log('ユーザーがログインしました');
console.log(`User ID: ${userId} logged in.`);
console.log(`Login event occurred at ${timestamp}, user ID: ${userId}`);
のように、
ログのフォーマットはバラバラになりがちで、一貫性が失われてしまいます・・・
このような状態では、後からログを解析したり、特定の情報を探したりする際に非常に手間がかかります。
しかし、ログ出力を行うためのラッパー関数を合わせて作成すれば、関数を介してすべてのログを出力するように統一することで、開発者それぞれが独自のフォーマットでconsole.logを記述するのを防ぎ、アプリケーション全体のログに一貫性が生まれます。
さらに、このラッパー関数内で環境変数をチェックすることで、開発環境とクラウド環境でログの出力形式を自動的に切り替えることも可能になり、環境ごとに適切なログ出力の制御も可能になります!
// コード例
type LogLevel = 'info' | 'warn' | 'error';
interface StructuredLog {
message: string;
level: LogLevel;
timestamp: string;
requestId: string;
[key: string]: any;
}
const isProduction = process.env.NODE_ENV === 'production';
export const logger = (message: string, level: LogLevel = 'info', data: object = {}): void => {
const timestamp = new Date().toISOString();
if (isProduction) {
// クラウド環境(本番環境)では構造化ログを出力
const logPayload: StructuredLog = {
message,
level,
timestamp,
...data,
};
console.log(JSON.stringify(logPayload));
} else {
// 開発環境では読みやすいフォーマットで出力
const formattedData = Object.keys(data).length > 0 ? ` - ${JSON.stringify(data)}` : '';
console.log(`[${level.toUpperCase()}] ${message}${formattedData}`);
}
};
Cloud Logging上での表示
console.logのみ(上側)、構造化ロギングを採用したログ(下側)
2. requestIdでログを追跡できる
requestIdを構造化ログのメタデータに追加することで、
1つのリクエストに関連するすべてのログを一括で追跡できるようになります。
複数のマイクロサービスや非同期処理が絡む複雑なシステムでは、特定のユーザー操作(例:注文確定)が、どのサービスを、どの順番で通過したか、そしてどこでエラーが発生したかを把握するのが困難になりがちです。
ログにrequestIdを含めておけば、そのIDを手がかりに、関連するすべてのログを時系列順に並べて確認することができます。これにより、問題の根本原因を素早く特定し、解決策を見つけることができます。
このようにログが一覧で出力されていて、どのログがどのログと同じ処理の中で吐かれたものか見つけにくいですよね
そんな時に、ログに含まれる「requestId」をクリックして、「一致エントリを表示」を押すと、同じリクエスト内のログのみが表示され、かなり見やすくなります👀
※ 「requestId」は middlewareで「x-request-id」があればそれを、ない場合はnanoid()
でユニークになるように生成しています。
3. Cloud Loggingでのフィルタリングが容易になる
一つ前の「requestId」の採用もそうですが、Cloud Loggingでは構造化ロギングのメリットを最大限に活かせます。
こちらの記事でわかりやすく詳細が書かれているのでぜひ見ていただきたいのですが、
特定のメタデータをログに含めてあげることで、デバッグがかなりしやすくなります!
例えば、、、
「severity」 というメタデータを含めてあげて、そのvalueとして「WARNING」と設定すると、
画像のように、左端の「重要度」がWARNINGのアイコンとなり、
視覚的にエラーが発生していることが一目でわかります。
そのほかにも「ERROR」と表示させると、赤いマークになったりとエラーレベルによって変えることができます!!
▼ 参考資料
また構造化ロギングを採用していると、jsonのメタデータごとにフィルターを使えるようになり、デバッグの速度向上や、どんなエラーが多く発生しているのかなどの分析にも役立ちます…!
おわりに
今回の内容は、楽にデバッグするための1つに過ぎないとは思いますが、
これだけでもかなりデバッグが楽になりました!!
アプリケーションを運用をする上で、エラーは必ず発生してくるものなので
その際に最速で原因を突き止めるためにデバッグのしやすさは大切だと思うので、今後もこの視点を持って開発していきたいと思いました!