ログレベルの使い分け:ERROR・WARN・INFO・DEBUGの判断基準と本番で残す量の設計

ログレベルの使い分け:ERROR・WARN・INFO・DEBUGの判断基準と本番で残す量の設計

ログレベルは、ログ1行ごとに「どれだけ重大か」を付ける区分で、出力するかどうかの足切りと、アラートを鳴らすかどうかの判断の両方に使われます。この記事では、ERROR・WARN・INFO・DEBUGをどの事象に割り当てるかの基準を、対応する人とタイミングから決める方法で示す構成です。後半では本番で残す量の決め方、Python・Go・Spring Boot・AWS Lambdaでのレベル設定のコード、構造化ログや相関IDと組み合わせた運用までを扱います。

まとめ:ログレベルは対応する人と時機で決め、本番はINFO以上を残す

ERRORは「誰かが調べて直す必要がある失敗」、WARNは「今は動いているが放置すると失敗につながる兆候」、INFOは「正常な状態の変化」、DEBUGは「調査のときだけ見る内部状態」に割り当てます。迷ったら、そのログを見た運用担当者が何をするかで決めてください。何もしないならERRORにしません。

本番の既定レベルはINFOにして、依存ライブラリはロガー名ごとにWARNまで絞ります。DEBUGは常時出さず、障害調査のときだけ対象のロガーに絞って一時的に上げる仕組みを用意します。アラートはERRORの件数と比率に張り、WARNは日次の確認に回すのが運用の負担を増やさない組み方です。

ログレベルの種類と定義|6段階が指す事象と言語ごとの数値の違い

FATALからTRACEまで6段階の定義と対応の緊急度の判断表

多くのロギングライブラリが持つレベルは、重大な順にFATAL(CRITICAL)、ERROR、WARN、INFO、DEBUG、TRACEの6段階です。名前は似ていても実装ごとに揃っているとは限らず、PythonにはTRACEが無く、Goのlog/slogにはFATALとTRACEのレベルがありません。チームで決めるべきなのは名前の暗記ではなく、各レベルに「誰がいつ対応するか」を対応させた表です。

レベル 指す事象 誰がいつ対応するか
FATAL / CRITICAL 処理を続けられず停止する 当番がすぐに対応
ERROR 要求や処理が失敗した 担当者が当日中に調査
WARN 失敗ではないが想定外 定期確認で対処を判断
INFO 正常な状態の変化 対応不要・追跡に使う
DEBUG 調査用の内部状態 開発・調査時だけ出す
TRACE 関数単位の細かな流れ 本番では原則出さない

この表を設計書かREADMEに置き、レビューで「このERRORは当日中に誰が見るのか」と問える状態にしておくと、レベルの付け方が人によってぶれにくくなります。

syslogとLog4jは小さいほど重大|規格ごとに逆になる数値の向き

レベルは内部的に数値で比較されますが、その向きは規格によって逆です。PythonのloggingはDEBUG=10からCRITICAL=50へ大きいほど重大で、Goのlog/slogもLevelDebug=-4、LevelInfo=0、LevelWarn=4、LevelError=8と同じ向きです。一方、syslogを定めたRFC 5424は0(Emergency)から7(Debug)、Log4j 2のレベル定義はFATAL=100からTRACE=600と、小さいほど重大になります。

規格・実装 範囲 数値の向き
Python logging DEBUG 10〜CRITICAL 50 大きいほど重大
Go log/slog Debug -4〜Error 8 大きいほど重大
Log4j 2 FATAL 100〜TRACE 600 小さいほど重大
syslog(RFC 5424) 0 Emergency〜7 Debug 小さいほど重大
OpenTelemetry TRACE 1〜FATAL 24 大きいほど重大

複数言語のログを1つの基盤に集めると、この違いが「level >= 4 で抽出したら逆の集合が出た」という事故になります。集約側では数値を直接比べず、後述のOpenTelemetryのSeverityNumberのような共通の尺度へ変換してから扱ってください。syslogにはWARNとINFOの間にNotice(5)があり、他の実装に移すときはINFOに寄せるのが一般的です。

ログレベルの使い分け基準|ERRORとWARNの境界と誤用しやすい場面

ERRORとWARNの境界|運用担当者が手を動かす必要があるかで分ける

ERRORとWARNの線は、システムが自力で回復したかどうかで引きます。外部APIへの呼び出しが1回目にタイムアウトし、リトライで成功したならWARNです。リトライを使い切って注文処理が失敗したならERRORになります。同じ「タイムアウト」でも、利用者や後続処理に失敗が届いたかどうかでレベルが変わります。

WARNに入れるのは、設定の既定値が使われた、廃止予定のAPIが呼ばれた、接続プールの使用率が8割を超えた、といった「今は動くが後で効いてくる」事象です。WARNが1日に数千件出るなら、それは誰も読んでいない証拠です。原因を直すか、INFOへ下げるかを決めてください。

4xxや入力エラーをERRORにしない|呼び出し側に原因がある事象の扱い

よくある誤用は、利用者の入力ミスや認証切れをERRORで出すことです。HTTPの400や404、パスワードの入力誤りはサーバーとしては想定どおりの応答であり、運用担当者が直すものはありません。これをERRORにすると、攻撃的なスキャンや古いブックマークからのアクセスだけでエラー件数が跳ね上がり、本物の障害がその中に埋もれます。

実務では、4xxはアクセスログに状態コードとして残し、アプリケーションログではINFOかDEBUGにとどめます。ERRORにするのは5xxを返した場合と、4xxでも短時間に急増して不正アクセスが疑われる場合の集計結果です。後者は1行ごとのレベルではなく、件数を数えるアラート側で拾います。

INFOに残す事象の選び方|起動・状態遷移・外部連携の結果に絞る

INFOは本番で常に出るレベルなので、量を決めるのはここです。残すのは、プロセスの起動と停止、読み込んだ設定の版、ジョブの開始と終了(件数と所要時間つき)、決済や外部APIとの連携結果、利用者の状態が変わった操作などです。「関数に入った」「ループの何周目」はINFOではなくDEBUGに置きます。

目安として、正常時に1リクエストあたりINFOが1〜3行に収まっているかを確認してください。10行を超えているなら、途中経過をINFOで出している箇所があります。リクエストの終わりに1行でまとめ、処理時間・結果・件数をフィールドとして持たせる書き方にすると、量を抑えたまま調査に必要な情報が残ります。

本番で残すログの量を設計する|環境別の既定レベルとライブラリの絞り方

開発はDEBUG・本番はINFO|環境別の既定レベルとDEBUGを常時出さない理由

既定レベルは、開発環境でDEBUG、ステージングと本番でINFOにするのが出発点です。Pythonのルートロガーは何も設定しなければWARNINGで、GoのslogのハンドラはHandlerOptions.Levelを省略するとInfoになります。ライブラリごとの既定値に任せず、環境変数で明示的に渡すと、環境の取り違えを防げます。

本番でDEBUGを常時出さない理由は2つあります。1つはログ基盤の費用で、CloudWatch Logsをはじめ多くの基盤は取り込んだデータ量に比例して課金されるため、DEBUGを開けたままにすると出力量が1桁増えることも珍しくありません。もう1つは情報の混入で、DEBUGにはリクエスト本文やSQLのパラメータが出やすく、個人情報やトークンを残す経路になります。保管期間や転送の設計はログ集約の仕組みとエージェント選定で扱っています。

ライブラリのログをロガー名ごとに絞る|依存パッケージのレベル指定

アプリのレベルをINFOにしても、HTTPクライアントやORM、クラウドSDKが自分のロガーで大量のINFOを出すことがあります。ロガーは名前の階層で管理されるので、依存パッケージのロガー名を指定してWARNへ下げる方法が有効です。Pythonならurllib3やbotocore、Spring Bootならorg.hibernate.SQLのような名前単位で個別に設定できます。

逆に、自社コードの特定パッケージだけDEBUGにする使い方もできます。全体をDEBUGにするより出力量がはるかに少なく済むため、障害調査ではまずロガー名を絞ってレベルを上げる、という手順をチームで共有しておいてください。

ログレベルを設定する手順|Python・Go・Spring Boot・Lambdaの実装例

Pythonのloggingで環境変数LOG_LEVELからレベルを切り替える設定

Pythonではlogging.basicConfigにレベル名の文字列を渡せます。環境変数から読み、依存ライブラリを個別に絞る最小構成は次のとおりです。

import logging
import os

# LOG_LEVEL が無ければ INFO。不正な値なら起動時に ValueError で止まる
level = os.environ.get("LOG_LEVEL", "INFO").upper()
logging.basicConfig(
    level=level,
    format="%(asctime)s %(levelname)s %(name)s %(message)s",
)
# 依存ライブラリの冗長なログはロガー名で個別に絞る
logging.getLogger("urllib3").setLevel(logging.WARNING)
logging.getLogger("botocore").setLevel(logging.WARNING)

log = logging.getLogger("app.order")


def submit(order_id: str, items: list) -> None:
    # 引数を % 形式で渡すと、DEBUG が無効なときは文字列の組み立て自体が行われない
    log.debug("submit start order_id=%s items=%s", order_id, items)
    if log.isEnabledFor(logging.DEBUG):
        # 重い集計は有効なときだけ実行する
        log.debug("detail=%s", [i for i in items if i])
    log.info("order submitted order_id=%s count=%d", order_id, len(items))

f文字列でlog.debug(f"...{items}")と書くと、DEBUGが無効でも文字列化が先に走ります。Logging HOWTOが示すとおり引数として渡し、重い処理はisEnabledForで囲みます。ファイルへ出す場合のローテーション設定はPythonのRotatingFileHandlerの設定と複数プロセス対策を参照してください。

Goのlog/slogでLevelVarを使い再起動せずにレベルを変える実装

Goのslogには、実行中にハンドラのレベルを変えるためのslog.LevelVarがあります。公式ドキュメントでは複数のゴルーチンから安全に使えると説明されており、ゼロ値はInfoです。管理用のエンドポイントから書き換える例を示します(ルーティングのメソッド指定はGo 1.22以降の書き方です)。

package main

import (
	"io"
	"log/slog"
	"net/http"
	"os"
)

// ゼロ値は Info。Set で全ロガーのレベルが一度に変わる
var level = new(slog.LevelVar)

func main() {
	logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: level}))
	slog.SetDefault(logger)

	// 管理用ポートはループバックだけで待ち受ける
	// 例: curl -X PUT -d DEBUG http://127.0.0.1:6060/loglevel
	mux := http.NewServeMux()
	mux.HandleFunc("PUT /loglevel", func(w http.ResponseWriter, r *http.Request) {
		b, _ := io.ReadAll(io.LimitReader(r.Body, 16))
		if err := level.UnmarshalText(b); err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}
		slog.Warn("log level changed", "level", level.Level().String())
	})
	go http.ListenAndServe("127.0.0.1:6060", mux)

	slog.Debug("not printed until level is DEBUG")
	slog.Info("server started", "port", 8080)
	select {}
}

レベルの変更そのものはWARNで残しています。変更はINFOより目立たせて記録し、誰がいつ上げたかを後から追えるようにするためです。JSONで出す理由とフィールド設計は構造化ログとslog・structlogの実装で解説しています。

Spring BootのActuatorで特定のロガーだけを一時的にDEBUGにする手順

Spring Bootではapplication.propertiesのlogging.level.*で既定値を決め、実行中の変更はActuatorのloggersエンドポイントで行います。2026年10月時点の4.1系ドキュメントでは、configuredLevelを送るとレベルが変わり、空のオブジェクトを送ると設定が消えて既定値へ戻ります。

# application.properties(本番の既定値)
logging.level.root=info
logging.level.org.hibernate.SQL=warn
management.endpoints.web.exposure.include=health,loggers

# 障害調査の間だけ com.example.order を DEBUG にする
curl -i -X POST 'http://localhost:8080/actuator/loggers/com.example.order' \
  -H 'Content-Type: application/json' -d '{"configuredLevel":"debug"}'

# 調査が終わったら設定を消して既定値へ戻す
curl -i -X POST 'http://localhost:8080/actuator/loggers/com.example.order' \
  -H 'Content-Type: application/json' -d '{}'

loggersを公開すると、外部からレベルを書き換えられる入口が増えます。管理用ポートを分けるか、Spring Securityで認証を掛けてから公開してください。Spring BootのLoggingの章には、ロガーをグループにまとめて一括で変えるlogging.groupの設定もあります。

AWS LambdaのJSONログでアプリとシステムのレベルを分けて絞る設定

Lambdaでは関数の設定でログレベルを絞れます。公式のLog-level filteringによると、この機能はログ形式がJSONのときだけ使え、アプリケーションログとシステムログ(STARTやREPORT)に別々のレベルを指定します。

aws lambda update-function-configuration --function-name order-api \
  --logging-config LogFormat=JSON,ApplicationLogLevel=INFO,SystemLogLevel=WARN

注意点は2つです。レベルを持たないアプリのログはINFO扱いになるため、printで出した行はApplicationLogLevelをWARN以上にすると消えます。またシステムログの既定はINFOで、WARNにするとREPORT行のメモリ使用量や実行時間が残らなくなります。REPORTを別の手段で集めていない場合はINFOのままにしてください。CloudWatch Logsでの見方と費用の削り方はAWS Lambdaのログ運用ガイドにまとめています。

構造化ログ・相関IDと組み合わせるログレベル運用とアラート設計

levelを固定キーで出す構造化ログとSeverityNumberへの対応づけ

レベルを本文の文字列に埋めず、JSONの固定キーとして出すと、基盤側で「levelがERRORの件数」を確実に数えられます。キー名はlevelかseverityのどちらかに全サービスで揃えます。値の表記ゆれ(WARNとWARNING、ERRとERROR)も、この段階で正規化しておいてください。

言語をまたいで比較する尺度として使えるのが、OpenTelemetryのLogs Data Modelです。安定版の仕様で、SeverityNumberを1〜4がTRACE、5〜8がDEBUG、9〜12がINFO、13〜16がWARN、17〜20がERROR、21〜24がFATALと定めており、大小比較にはこの数値を使うよう求めています。Collectorで変換する設定はOpenTelemetryのログ実装と送出経路の選び分けで扱っています。

相関IDで1リクエスト分を束ねる|レベルを上げずに調査する方法

障害の調査で欲しいのは、多くの場合「失敗した1件のリクエストで何が起きたか」です。リクエストの入口で相関ID(トレースを使っているならtrace_id)を払い出し、すべてのログ行にフィールドとして付けておけば、ERRORの1行からそのIDで検索して、同じリクエストのINFOをすべて並べられます。

この仕組みがあると、DEBUGを開ける頻度が下がります。INFOに「どの外部APIを何ミリ秒で呼んだか」が残っていれば、失敗したリクエストの経路はINFOだけで再現できるからです。DEBUGに頼る調査は再現待ちになりやすく、相関IDとINFOで追える範囲を広げるほうが障害の解決は早くなります。

アラートはERRORの件数と比率に張る|WARNを通知にしない判断

アラートは、ERRORが1件出るたびに通知する設定にしないでください。外部サービスの一時的な失敗でも夜中に呼び出しが起きると、通知を読まなくなる状態に直行します。5分間のERROR件数がしきい値を超えた、またはリクエスト全体に占めるERRORの比率が1%を超えた、のように件数か比率で張ります。FATALは件数に関係なく即時通知の対象です。

WARNは通知に使わず、日次か週次で件数の多い順に並べて確認します。通知の設計で起きがちな失敗と対策はアラート疲れの原因と監視の実装での対策で詳しく解説しています。

ログレベル設計を作り込むべき場面と既定値のままでよい場面の判断

作り込むべき場面|複数サービス連携と障害対応の当番がある体制

レベルの定義表・ロガー名ごとの既定値・実行中の変更手段・アラート条件まで作り込むべきなのは、3つ以上のサービスがAPIでつながり、障害時に当番が呼ばれる体制のシステムです。この規模になると、レベルの付け方がサービスごとに違うだけで、調査の初動に毎回30分単位の時間を取られます。決済や在庫のように失敗が売上に直結する処理を持つ場合も、作り込みの対象に入ります。

既存システムでレベルがばらばらになっていて、どこから直すか決めきれない場合は、ERRORの出どころ上位10件から定義表に照らして付け替えるのが近道です。ログ設計の見直しを含む運用の引き受けは、一創の保守運用・内製化支援でも対応しています。

既定値で足りる場面|単一アプリで障害時にすぐDEBUGへ切り替えられる

社内向けの単一アプリで、利用者が数十人、障害時は開発者が直接ログを読む、という体制なら作り込みは過剰です。本番をINFO、依存ライブラリをWARNにし、ERRORとWARNの境界だけを本記事の基準で揃えれば十分に回ります。定義表やレベル変更用のエンドポイントを先に作っても、使われないまま保守対象だけが増えます。

切り替えの判断材料は、ログを読む人数です。書いた本人以外がログを読み始めた時点で、定義表を作る段階に入ったと考えてください。

ログレベルの設定・使い分け・本番運用の判断に関するよくある質問

ログレベルの使い分けや本番での設定について、よく寄せられる質問をまとめました。

本番環境のログレベルは何にすればよいですか?

アプリケーション本体はINFO、依存ライブラリはWARNから始めるのが一般的です。INFOで1リクエストあたりの行数が多すぎる場合は、途中経過をDEBUGへ移してINFOを残します。本番全体をWARNにすると、正常時の記録が残らず、障害の前後で何が変わったかを比べられなくなります。DEBUGは常時出さず、調査中に対象のロガーだけ上げてください。

FATALとCRITICALの違いは何ですか?

どちらも「処理を続けられない最も重大な事象」を指し、実装によって名前が違うだけです。PythonはCRITICAL(数値50)を使い、FATALは同じ値の別名として残っています。Log4jやOpenTelemetryはFATALという名前です。syslogではCriticalの上にAlertとEmergencyがあり、より細かく分かれています。

アプリを再起動せずにログレベルを変更できますか?

できます。GoのslogはLevelVarを書き換えるだけで反映され、Spring BootはActuatorのloggersエンドポイントへPOSTすると変更が反映される仕組みです。AWS Lambdaは関数の設定を更新すれば次の呼び出しから効きます。どの方法でも、変更した事実をログに残し、調査が終わったら元に戻す手順まで決めておいてください。

DEBUGとTRACEはどう使い分けますか?

DEBUGは変数の値や分岐の結果など調査に使う内部状態、TRACEは関数の出入りやループの1周ごとのような流れの記録です。TRACEは出力量が桁違いに多く、本番ではほぼ使いません。PythonやGoの標準ライブラリにはTRACEが無いため、必要なら独自のレベルを定義するより、DEBUGをロガー名で細かく分けるほうが扱いやすくなります。

ログに出してはいけない情報はレベルで制御できますか?

できません。レベルは出力の足切りであって、内容の検査ではないためです。パスワード、トークン、カード番号、個人情報は、どのレベルでも出さない前提で、ログを出す前の段階でマスキングします。DEBUGだから本番に出ないという前提は、調査で一時的にDEBUGを開けた瞬間に崩れるからです。PHPでの具体的なマスキング設定はLaravelのログ設定とマスキングが参考になります。

関連記事

お気に入りに入れた記事の一覧

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.27 テックブログ Brotli 脆弱性:CVE-2026-111497の影響確認とNode.js・Python・.NETの更新手順
  2. 2026.10.28 テックブログ リンツ不正アクセス:314万件の対象確認とパスワード強制リセット後の対処
  3. 2026.10.27 テックブログ Joshin(ジョーシン)不正アクセス:最大600万件の対象とサブスク初期パスワードの対処
  4. 2025.02.27 コラム トラッキングビーコン(Webビーコン)とは?仕組み・用途・確認と対策を徹底解説
  5. 2026.10.23 テックブログ Renta!はいつ再開?不正アクセスでメールアドレス等が流出したおそれ・再開時のパスワード再設定と運営側の対応

RELATED POSTS 関連記事

目次