AIによる要約
ログレベルはメッセージの重要そうな雰囲気ではなく、運用時にどう扱うかで決めます。INFOは正常な業務・処理の主要イベント、WARNは処理を継続できるが確認が必要な状態、ERRORは要求やジョブが失敗し対応が必要な状態です。DEBUG・TRACEは調査用の詳細に使います。例外は最終的に扱う境界で一度だけ記録し、logger.error("...", e)のようにThrowableを渡します。パスワードや個人情報はどのレベルにも出しません。


Javaの現場ではSLF4JとLogbackなどを使い、TRACE、DEBUG、INFO、WARN、ERRORのログを出します。APIは簡単ですが、「何をどのレベルで出すか」は教科書だけでは決めにくい部分です。
ログは開発者だけでなく、監視、運用、問い合わせ調査、監査にも使われます。ERRORを増やしすぎると不要なアラートが鳴り、INFOを細かく出しすぎると必要な履歴を探せません。現場で説明できる判断軸を持ちましょう。
この記事のポイント
- レベルは運用時の対応要否から決める
- INFOは正常系の主要イベントだけに絞る
- WARNは継続可能だが放置したくない状態に使う
- ERRORは要求・ジョブが失敗し対応が必要な状態に使う
- 例外スタックトレースは同じ失敗につき原則1回記録する
ログレベルの基本
| レベル | 現場での目安 | 例 |
|---|---|---|
| TRACE | 通常は出さない最詳細の処理追跡 | ループ各回、変換途中 |
| DEBUG | 開発・調査時に必要な詳細 | 検索条件、分岐結果 |
| INFO | 正常運用で追いたい主要イベント | ジョブ開始・終了、受付完了 |
| WARN | 継続できるが確認・改善が必要 | リトライ成功、代替値使用 |
| ERROR | 処理が失敗し対応が必要 | 注文登録失敗、ジョブ中断 |
LogbackではTRACE < DEBUG < INFO < WARN < ERRORの順です。実効レベルがINFOなら、INFO、WARN、ERRORは出力され、DEBUGとTRACEは抑止されます。
ただし、この順序だけでは使い分けられません。同じ例外でも、リトライで回復したならWARN、利用者の要求が失敗したならERROR、想定内の入力エラーならINFOまたはログ不要になることがあります。
INFOは正常系の主要イベントに使う
log.info(
"注文受付が完了しました orderId={}, requestId={}",
orderId,
requestId);
INFOは、本番で通常表示されてもよく、問い合わせ時に業務の進行を追うために必要な情報です。
- バッチやジョブの開始・終了・処理件数
- 外部連携ファイルの受付・送信完了
- 重要な業務状態の変更
- アプリケーションの起動・停止
一方、すべてのメソッド入口と出口をINFOで出すと、1リクエストで数十行になります。メソッド追跡はDEBUGへ寄せ、INFOは運用上意味のある単位へ絞ります。
// 出しすぎの例
log.info("createメソッド開始");
log.info("FormからDTOへ変換");
log.info("Repository呼び出し開始");
log.info("Repository呼び出し終了");
log.info("createメソッド終了");
開始・終了の両方が必要な長時間バッチは別ですが、短いServiceメソッドへ機械的に入れる必要はありません。
WARNは処理を継続できるが確認が必要な状態
try {
return pricingClient.findPrice(productId);
} catch (SocketTimeoutException e) {
log.warn(
"価格APIがタイムアウトしたためキャッシュ値を使用します productId={}",
productId,
e);
return priceCache.get(productId);
}
この例は代替値で処理を継続できますが、外部API障害が続けば確認が必要です。WARNは「今すぐ処理失敗ではないが、頻発すると問題」という状態に向いています。
- リトライ後に成功した
- 外部機能を使えず代替経路で継続した
- 廃止予定の設定やAPIが使われた
- 想定値を使えず既定値へフォールバックした
- 処理件数や待ち時間が警戒値へ近づいた
「少し不安だからWARN」ではなく、WARNが一定回数出たら誰が何を確認するかを考えます。運用アクションが何もないならDEBUGやINFOが適切な場合があります。
ERRORは処理失敗と対応要否で決める
try {
orderService.place(command);
} catch (RuntimeException e) {
log.error(
"注文登録に失敗しました requestId={}, customerId={}",
requestId,
mask(customerId),
e);
throw e;
}
ERRORは、要求が完了できず、運用担当や開発者が調査すべき状態です。監視がERROR件数をアラート条件にしている案件もあるため、気軽に使うと不要な通知を増やします。
上のコードはシステム境界で記録する例です。さらに上位の共通例外ハンドラで記録するなら、ServiceやRepositoryでは重ねてERRORを出しません。
入力エラーをERRORにしない
利用者が必須項目を未入力、存在しないURLへアクセス、検索結果が0件といった想定内の事象は、通常システム障害ではありません。
if (bindingResult.hasErrors()) {
log.debug(
"入力エラーのため登録画面へ戻します fieldErrorCount={}",
bindingResult.getFieldErrorCount());
return "orders/input";
}
業務上の不正操作を監査ログへ残す要件はあり得ますが、その場合もアプリケーション障害のERRORとは目的を分けます。通常ログ、アクセスログ、監査ログを混同しないことが大切です。
DEBUGは調査に必要な判断材料に使う
log.debug(
"注文検索を実行します status={}, from={}, to={}, page={}",
condition.status(),
condition.from(),
condition.to(),
pageable.getPageNumber());
DEBUGには、分岐理由、検索条件、外部呼び出しの概要など、障害調査で必要だが通常運用では量が多い情報を置きます。
DEBUGだから機密情報を出してよいわけではありません。本番で一時的にDEBUGへ変更することがあるため、パスワード、トークン、個人情報は最初から記録しない設計にします。
TRACEは限定的な詳細追跡に使う
TRACEは、ライブラリ内部や複雑な変換の各段階など、DEBUGより細かい追跡へ使います。業務アプリの通常実装で必ず使うものではありません。
for (int i = 0; i < rows.size(); i++) {
log.trace(
"CSV行を解析します lineNumber={}, columnCount={}",
i + 1,
rows.get(i).size());
}
大量ループのTRACEを本番で有効にするとログ量が急増します。出力対象パッケージや有効期間を限定し、調査後に戻せる手順を用意します。
文字列連結よりプレースホルダーを使う
// 避けたい例
log.debug("order=" + order + ", customer=" + customer);
// 推奨
log.debug("orderId={}, customerId={}", orderId, customerId);
SLF4Jの{}プレースホルダーを使うと、レベルが無効な場合に不要な文字列組み立てを抑えられます。また、Entity全体のtoStringを出さず、調査に必要なIDや状態だけを選べます。
メソッド呼び出し自体が重い場合は、プレースホルダーでも引数評価は行われます。重いデバッグ情報を作るときはlogger.isDebugEnabled()やSLF4Jの利用バージョンに合う遅延評価APIを検討します。
例外はメッセージだけでなくThrowableを渡す
// NG: 例外型とスタックトレースを失う
log.error("注文保存に失敗しました: {}", e.getMessage());
// OK: Throwableを最後の引数として渡す
log.error("注文保存に失敗しました orderId={}", orderId, e);
e.getMessage()だけでは、発生行やCaused byを追えません。Throwableを渡し、スタックトレースを記録します。
ただし同じ例外をRepository、Service、Controller、共通ハンドラのすべてで出す必要はありません。文脈を付けて上位へ投げ、最終的に処理する境界で一度記録します。
ログへ出してはいけない情報
- パスワード、秘密の質問、認証コード
- アクセストークン、Cookie、APIキー
- カード番号、口座情報
- マイナンバーなどの強い個人識別情報
- リクエスト・レスポンス本文の無加工出力
- 個人情報を含むEntity全体のtoString
レベルを下げても情報漏えい対策にはならない
DEBUGは本番で有効化される可能性があり、ログファイルにも長期間残り得ます。「ERRORではなくDEBUGだから出してよい」と考えず、出力項目を設計してください。
ログに入れると調査しやすい情報
- リクエストID・トレースID
- 業務処理を特定できるマスク済みID
- 処理名、結果、件数、所要時間
- 外部連携先と応答ステータス
- 再試行回数と最終結果
- 例外オブジェクトと自作コードのスタックトレース
「何が起きたか」だけでなく、「どの処理で」「どの要求に対して」「結果はどうなったか」が分かる形にします。メッセージ形式は既存ログへ揃え、検索しやすい固定語を使います。
迷った時の判断フロー
- 処理は成功したか、失敗したか
- 失敗なら想定内の入力・業務エラーか、システム障害か
- 処理を継続できるか
- 運用担当が確認・対応する必要があるか
- 通常時にも必要か、調査時だけ必要か
- 同じ失敗を別の層ですでに記録していないか
- 機密情報が含まれないか
レベル名から選ぶのではなく、処理結果と運用アクションを答えてからレベルへ当てはめます。
現場レビューでよくある指摘
// レビューコメント例 入力チェックエラーをERRORで出しているため、 利用者の操作ミスで障害アラートが発生します。レベルと出力要否を見直してください。 // レビューコメント例 e.getMessage()だけでは発生行とCaused byを追えません。 ThrowableをログAPIへ渡してください。 // レビューコメント例 RepositoryとServiceの両方で同じ例外をERROR記録しています。 共通ハンドラで一度記録する方針へ揃えてください。 // レビューコメント例 Form全体をDEBUG出力しており、メールアドレスとトークンが含まれます。 必要な識別子だけをマスクして出力してください。
提出前のセルフチェック
- 運用時の対応要否からレベルを選んだか
- INFOへメソッドの全入口・出口を出していないか
- WARNが出た時の確認行動を説明できるか
- 想定内の入力エラーをERRORにしていないか
- Throwableを渡してスタックトレースを残したか
- 同じ例外を複数層で記録していないか
- プレースホルダーを使っているか
- 機密情報・個人情報を全レベルから除外したか
- リクエストIDなど調査に必要な文脈があるか
読みやすく調査しやすいコードを学ぶ参考書
適切なログは、短いメソッド、伝わる命名、整理された例外処理と組み合わせることで効果を発揮します。新人が現場コードを読み解く土台として役立つ一冊です。
リーダブルコード
コードレビューの「読みづらい」を減らす、最初の一冊。
命名、コメント、条件分岐、変数、メソッド分割を短い実例から学べます。Java専用書ではありませんが、現場で使える読みやすさの判断基準が身につきます。
- レビュー指摘の理由と直し方を整理したい
- 動くだけでなく、読みやすいコードを書きたい
当サイトはAmazonアソシエイト・プログラムの参加者です。価格・在庫・配送条件はAmazonでご確認ください。
この記事とあわせて読みたい
まとめ
INFO、WARN、ERRORは重要度の印象ではなく、処理結果と運用時の対応要否で選びます。正常な主要イベントはINFO、継続可能だが確認が必要ならWARN、処理が失敗し対応が必要ならERRORが基本です。
調査詳細はDEBUG・TRACEへ置き、例外はThrowableごと一度だけ記録します。プレースホルダーを使い、リクエストIDなど必要な文脈を残しつつ、パスワード・トークン・個人情報はどのレベルにも出さないようにしましょう。
