Javaのログレベルの使い分けを解説する記事のアイキャッチ。Java道場風の空間で若手エンジニアがINFO、WARN、ERRORの基準を学ぶイラスト。

開発現場メモ

Javaのログレベルはどう使い分ける?INFO・WARN・ERRORの現場ルール

AIによる要約

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

新人SE
新人SE
catchした例外は全部ERROR、正常処理は全部INFOにしておけば安全ですか?
ポンコツSE
ポンコツSE
それでは監視通知とログ量が増え、本当に対応すべき障害が埋もれます。処理結果と運用アクションでレベルを選びます。

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
  • 処理名、結果、件数、所要時間
  • 外部連携先と応答ステータス
  • 再試行回数と最終結果
  • 例外オブジェクトと自作コードのスタックトレース

「何が起きたか」だけでなく、「どの処理で」「どの要求に対して」「結果はどうなったか」が分かる形にします。メッセージ形式は既存ログへ揃え、検索しやすい固定語を使います。

迷った時の判断フロー

  1. 処理は成功したか、失敗したか
  2. 失敗なら想定内の入力・業務エラーか、システム障害か
  3. 処理を継続できるか
  4. 運用担当が確認・対応する必要があるか
  5. 通常時にも必要か、調査時だけ必要か
  6. 同じ失敗を別の層ですでに記録していないか
  7. 機密情報が含まれないか

レベル名から選ぶのではなく、処理結果と運用アクションを答えてからレベルへ当てはめます。

現場レビューでよくある指摘

// レビューコメント例
入力チェックエラーをERRORで出しているため、
利用者の操作ミスで障害アラートが発生します。レベルと出力要否を見直してください。

// レビューコメント例
e.getMessage()だけでは発生行とCaused byを追えません。
ThrowableをログAPIへ渡してください。

// レビューコメント例
RepositoryとServiceの両方で同じ例外をERROR記録しています。
共通ハンドラで一度記録する方針へ揃えてください。

// レビューコメント例
Form全体をDEBUG出力しており、メールアドレスとトークンが含まれます。
必要な識別子だけをマスクして出力してください。

提出前のセルフチェック

  • 運用時の対応要否からレベルを選んだか
  • INFOへメソッドの全入口・出口を出していないか
  • WARNが出た時の確認行動を説明できるか
  • 想定内の入力エラーをERRORにしていないか
  • Throwableを渡してスタックトレースを残したか
  • 同じ例外を複数層で記録していないか
  • プレースホルダーを使っているか
  • 機密情報・個人情報を全レベルから除外したか
  • リクエストIDなど調査に必要な文脈があるか

読みやすく調査しやすいコードを学ぶ参考書

適切なログは、短いメソッド、伝わる命名、整理された例外処理と組み合わせることで効果を発揮します。新人が現場コードを読み解く土台として役立つ一冊です。

書籍「リーダブルコード」の表紙
書影:O'Reilly Japanより引用
PR 新人・若手エンジニア向け

リーダブルコード

著 / 角 征典 訳

コードレビューの「読みづらい」を減らす、最初の一冊。

命名、コメント、条件分岐、変数、メソッド分割を短い実例から学べます。Java専用書ではありませんが、現場で使える読みやすさの判断基準が身につきます。

  • レビュー指摘の理由と直し方を整理したい
  • 動くだけでなく、読みやすいコードを書きたい

当サイトはAmazonアソシエイト・プログラムの参加者です。価格・在庫・配送条件はAmazonでご確認ください。

この記事とあわせて読みたい

まとめ

INFO、WARN、ERRORは重要度の印象ではなく、処理結果と運用時の対応要否で選びます。正常な主要イベントはINFO、継続可能だが確認が必要ならWARN、処理が失敗し対応が必要ならERRORが基本です。

調査詳細はDEBUG・TRACEへ置き、例外はThrowableごと一度だけ記録します。プレースホルダーを使い、リクエストIDなど必要な文脈を残しつつ、パスワード・トークン・個人情報はどのレベルにも出さないようにしましょう。

-開発現場メモ
-, , ,