ログフォーマットは「あとから読めるログ」を作るための設計
ログは「その瞬間に何が起きていたか」を後から再現するための記録です。
でも、ただ System.out.println("エラーしました") と出しているだけでは、
「いつ」「どの処理で」「どのユーザーで」「どのリクエストで」起きたのかが分からず、運用でほぼ役に立ちません。
そこで大事になるのが「ログフォーマット」です。
ログフォーマットとは、「ログ1行に、どんな情報を、どんな形で載せるか」を決めるルールのことです。
このルールをユーティリティとしてまとめておくと、アプリ全体のログが揃い、トラブルシュートが一気に楽になります。
まず押さえるべき「ログ1行に欲しい情報」
最低限ほしい情報のイメージ
業務システムで「後から読めるログ」にするために、1行に載せたい典型的な情報はだいたいこうです。
日時(いつ)
レベル(INFO / WARN / ERROR など)
スレッド名(どのスレッド)
クラス・メソッド(どの処理)
メッセージ(何が起きたか)
トレース ID やリクエスト ID(どのリクエスト)
ユーザー ID やテナント ID(誰の/どの顧客の)
これらを毎回手書きするのは現実的ではないので、
「ログフレームワークのパターン設定」と「アプリ側のフォーマットユーティリティ」を組み合わせて整えていきます。
ログフレームワーク側のフォーマット(パターンレイアウト)
典型的なパターン例(Logback / Log4j2)
Logback や Log4j2 では、設定ファイルでログのフォーマットを指定できます。
例えば、こんなパターンです。
%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger - %msg%n
これで、ログは次のような形になります。
2026-01-23 15:30:12.345 INFO [http-nio-8080-exec-1] com.example.UserService - ユーザ登録開始
ここでのポイントは、
日時・レベル・スレッド・ロガー名(クラス名)・メッセージが、毎行同じ形式で出ることです。
この「土台のフォーマット」は、基本的にログフレームワーク側の設定で統一します。
アプリ側のユーティリティは、「メッセージ部分をどう整えるか」「ID やコンテキストをどう埋め込むか」を担当させるときれいに分担できます。
アプリ側でやるべき「メッセージのフォーマット統一」
プレーン文字列だと情報がバラバラになる
例えば、こんなログが散らばっているとします。
log.info("ユーザ登録開始: id=" + userId);log.info("ユーザ登録完了 user=" + userId);log.error("エラー userId=" + id + " reason=" + e.getMessage());
一見それっぽく見えますが、
キー名が id だったり user だったり userId だったりバラバラで、
あとから検索するときに「どれがどれだっけ?」となりがちです。
ここをユーティリティで揃えてしまうと、ログが一気に“機械的に扱いやすく”なります。
シンプルな「キー=値」形式をユーティリティで揃える
例えば、こんなユーティリティを用意します。
public final class LogFormat {
private LogFormat() {}
public static String kv(String key, Object value) {
return key + "=" + String.valueOf(value);
}
public static String join(String... parts) {
return String.join(" ", parts);
}
}
Java使う側はこう書けます。
log.info(LogFormat.join(
"ユーザ登録開始",
LogFormat.kv("userId", userId),
LogFormat.kv("requestId", requestId)
));
Java出力はこうなります。
ユーザ登録開始 userId=123 requestId=abc-xyz
ここで深掘りしたいのは、「キー名と書式をユーティリティで揃えることで、ログが“検索しやすいデータ”になる」という点です。userId= で検索すれば、ユーザー関連のログが一気に拾えますし、requestId= で検索すれば、1 リクエストに紐づくログをまとめて追えます。
コンテキスト情報(リクエスト ID など)を自動で埋め込む
MDC(Mapped Diagnostic Context)を使う
SLF4J/Logback/Log4j2 には、MDC という「スレッドローカルなコンテキスト」をログに埋め込む仕組みがあります。
例えば、リクエストごとに requestId を MDC に入れておくと、ログフォーマット側で %X{requestId} と書くだけで、全ログに自動で付与できます。
アプリ側では、こんなユーティリティを用意します。
import org.slf4j.MDC;
public final class LogContext {
private static final String KEY_REQUEST_ID = "requestId";
private static final String KEY_USER_ID = "userId";
private LogContext() {}
public static void setRequestId(String requestId) {
MDC.put(KEY_REQUEST_ID, requestId);
}
public static void setUserId(String userId) {
MDC.put(KEY_USER_ID, userId);
}
public static void clear() {
MDC.clear();
}
}
JavaWeb フィルタやインターセプタでこう使います。
try {
LogContext.setRequestId(generateRequestId());
LogContext.setUserId(currentUserId());
// コントローラ処理へ
} finally {
LogContext.clear();
}
Javaログフォーマットをこうしておけば、
%d %-5level [%thread] %X{requestId} %X{userId} %logger - %msg%n
全てのログに requestId と userId が自動で付きます。
ここでの重要ポイントは、「ID の埋め込みを“メッセージ文字列”ではなく“コンテキスト”でやる」という設計です。
これにより、アプリ側のログメッセージはシンプルなまま、ログ全体としてはリクエスト単位・ユーザー単位で追いやすくなります。
JSON ログフォーマットとユーティリティ
構造化ログ(JSON)を前提にする場合
最近は、ログを JSON 形式で出力し、Elasticsearch や Loki などに食わせて検索・集計する構成も多いです。
この場合、「メッセージを人間向けに整形する」よりも、「フィールドを機械向けに揃える」ことが重要になります。
Logback などには JSON ログ用のアペンダやエンコーダがあり、level, timestamp, logger, thread, message, mdc などを自動で JSON にしてくれます。
アプリ側のユーティリティは、「message の中身をどうするか」よりも、
「必要な情報を MDC やフィールドとしてきちんとセットする」ことに重心が移ります。
それでも「メッセージの一貫性」は効いてくる
JSON ログでも、message フィールドは人間が読むことが多いです。
ここで、先ほどの LogFormat.kv のような「キー=値」スタイルを使っておくと、
ダッシュボード上でメッセージを見たときに、状況が直感的に分かりやすくなります。
例外ログのフォーマットとユーティリティ
例外は「メッセージ+スタックトレース」で出す
例外をログに出すときは、基本的にこう書きます。
log.error("ユーザ登録に失敗しました userId={}", userId, e);
Javaログフレームワークが、メッセージとスタックトレースをよしなに出してくれます。
ここでユーティリティを挟むとしたら、例えば「共通のエラーメッセージフォーマット」を作る形です。
public final class ErrorLogFormat {
private ErrorLogFormat() {}
public static String userActionFailed(String action, Object userId) {
return "ユーザ処理失敗 action=" + action + " userId=" + userId;
}
}
Java使う側はこうです。
log.error(ErrorLogFormat.userActionFailed("register", userId), e);
Javaここでのポイントは、「エラー時のメッセージ構造をユーティリティで揃える」ことです。
「何のアクションが」「どのユーザーで」「失敗したのか」が、どのログでも同じ形で出てくるようになります。
まとめ:ログフォーマットユーティリティで身につけるべき感覚
ログフォーマットは、「その場しのぎの println」ではなく、「後から検索・分析・追跡できる“データ”としてログを設計する」ことです。
押さえておきたい感覚はこうです。
ログフレームワーク側で「行全体のフォーマット」(日時・レベル・スレッド・ロガー)を決める。
アプリ側では、「メッセージ部分の書き方」をユーティリティで揃え、キー=値形式などで検索しやすくする。
MDC を使って、requestId や userId などのコンテキストを自動で全ログに埋め込む。
必要に応じて JSON ログを使い、「構造化されたログ」として扱う前提で設計する。
例外ログやエラーログのメッセージも、ユーティリティでパターン化しておくと、運用時に読みやすくなる。
