閉じる

Pythonのlogging入門: 基本の使い方とログレベル完全解説

アプリの振る舞いやエラーの原因をあとから調査するには、実行時の情報を記録する仕組みが欠かせません。

Pythonには標準ライブラリとしてloggingが用意されており、開発から本番運用まで同じAPIで活用できます。

本稿では基本の使い方からログレベルの考え方、よくあるハマりどころまで、初心者の方に向けて丁寧に解説します。

Pythonのloggingとは?printとの違い

loggingの役割とメリット

Pythonのloggingは、アプリケーションの実行中に起きた出来事(イベント)を、レベルや発生元、時刻とともに記録するための仕組みです。

ログはデバッグ専用ではなく、運用監視や不具合解析、監査にも耐える「記録」として扱います。

代表的なメリットは次の通りです。

主に文章で説明しますが、要点をまとめます。

まず、ログは重要度(ログレベル)でフィルタできるため、開発時は詳細、運用時は必要最小限に出力を調整できます。

また出力先(ハンドラ)を切り替えれば、コンソール、ファイル、ネットワーク先など柔軟に書き分けられます。

さらにフォーマットを統一すると、誰が見ても読みやすく、機械処理(検索や集計)にも向いた形で残せます。

「どこで、何が、いつ、どのくらい重要か」を機械可読に残せる点が、単なる標準出力との大きな違いです。

printとの違いと使い分け

printは画面に文字を出すだけの関数で、レベルや出力先の切り替え、統一フォーマットといった機能はありません。

学習初期には便利ですが、スクリプトが成長するにつれ管理が難しくなります。

継続的にメンテナンスされるコードではloggingが推奨です。

次の表は違いを簡単に比較したものです。

観点printlogging
目的手早い確認出力追跡可能な記録(解析・監視)
重要度(レベル)なしDEBUG/INFO/WARNING/ERROR/CRITICAL
出力先の切替標準出力のみコンソール/ファイル/外部連携など
フォーマット任意の文字列時刻・モジュール名などを自動付与
パフォーマンス文字列生成が都度実行遅延フォーマットで抑制可能
運用適性低い高い(標準ライブラリ)

ログ記録にprintを使い続けると、どの行が重要かわからず、後からの解析が困難になります

そのため、実運用を意識するなら早めにloggingへ移行しましょう。

ログレベルの基本(DEBUG/INFO/WARNING/ERROR/CRITICAL)

ログレベルは重要度を数値で段階付けしたものです。

順序はDEBUG < INFO < WARNING < ERROR < CRITICALです。

レベル数値概要
DEBUG10開発時の詳細情報(変数値や分岐の状況)
INFO20通常運用の情報(開始/終了、処理件数)
WARNING30注意喚起(想定外だが処理続行可能)
ERROR40エラー(機能の一部が失敗)
CRITICAL50致命的(アプリ継続不能、即対応)

レベルを低く設定するほど多くのログが出ます

運用では必要最小限、本番障害対応時だけ詳細を開ける運用が現実的です。

ログレベル完全解説(初心者向け)

各ログレベルの意味と使い所

DEBUG

最も詳細な情報です。

例えば「APIレスポンスのJSON」「SQLとバインド変数」「ループ内のカウンタ」といった、開発者が原因を詰めるための情報を出します。

普段の本番環境では基本的に無効にします。

INFO

正常系の進行状況を記録します。

アプリ起動/終了、バッチ処理の件数、外部サービスへの接続成功など、後から状況を時系列で追いやすくする情報を出します。

WARNING

想定外や非推奨の使い方など、注意が必要な出来事を表します。

処理は続行できるが放置すると問題化する可能性があるときに使います。

ERROR

機能の一部が失敗したときに使います。

例外を捕捉して対処したが完全復旧できていない、外部サービスが一時的に落ちている、などが該当します。

CRITICAL

サービス継続が難しい致命的な状況です。

設定ファイルが読めない、必須リソースへ接続不能、といった場合に使います。

開発(DEV)と本番(PROD)のレベル設定の目安

環境推奨初期レベル目的
開発(DEV)DEBUG または INFO問題切り分けを優先、詳細に可視化
ステージング(STG)INFO本番近い条件での動作確認
本番(PROD)WARNING または INFOノイズを抑えつつ必要な情報を確保

本番でWARNING以上にしても、障害調査時のみ一時的にDEBUGへ引き上げる運用がよく使われます。

レベルごとの出力例

以下は、各レベルで出力してみた例です。

Python
# logging_levels_example.py
# 各ログレベルの出力を確認するサンプル

import logging

# INFO以上を出す設定
logging.basicConfig(level=logging.INFO)

logging.debug("これはDEBUGです")
logging.info("これはINFOです")
logging.warning("これはWARNINGです")
logging.error("これはERRORです")
logging.critical("これはCRITICALです")
実行結果
INFO:root:これはINFOです
WARNING:root:これはWARNINGです
ERROR:root:これはERRORです
CRITICAL:root:これはCRITICALです

DEBUGが表示されていないことから、レベルの閾値より低いメッセージは出力されないことがわかります。

基本の使い方(basicConfig編)

最短の初期化(logging.basicConfig)

logging.basicConfigは、最小限の設定でログ出力を初期化する関数です。

最初の1回の呼び出しが有効で、以降の呼び出しは無視されます(Python 3.8以上はforce=Trueで再設定可能)。

Python
# basic_minimum.py
import logging

# INFO以上を出力
logging.basicConfig(level=logging.INFO)

logging.info("Hello logging")
logging.warning("注意喚起です")
実行結果
INFO:root:Hello logging
WARNING:root:注意喚起です

コンソールに出力する(stream)

デフォルトでは標準エラー出力( stderr )に出ます。

標準出力( stdout )に出したい場合はstreamを指定します。

Python
# console_stream.py
import logging
import sys

# 既に設定済みでも強制的に上書きしたい場合は force=True (3.8+)
logging.basicConfig(
    level=logging.INFO,
    stream=sys.stdout,  # 標準出力へ
    format="%(levelname)s:%(message)s",
    force=True
)

logging.info("標準出力に出ます")
実行結果
INFO:標準出力に出ます

ファイルに出力する(filename/encoding)

ファイルへ書く場合はfilenameを指定します。

日本語を含むならencoding='utf-8'を必ず指定しましょう。

Python
# file_output.py
import logging

logging.basicConfig(
    level=logging.INFO,
    filename="app.log",       # ログファイル名
    filemode="w",             # 上書き(追記なら "a")
    encoding="utf-8",         # 文字化け対策
    format="%(asctime)s %(levelname)s %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S"
)

logging.info("アプリ開始")
logging.warning("ディスク残量が少ないかもしれません")
logging.error("処理に失敗しました")
実行結果app.log
# app.log の内容(例)
2025-01-01 12:00:00 INFO アプリ開始
2025-01-01 12:00:01 WARNING ディスク残量が少ないかもしれません
2025-01-01 12:00:02 ERROR 処理に失敗しました

フォーマットの基本(format/datefmt)

formatでメッセージの並び、datefmtで時刻の書式を指定します。

よく使うプレースホルダはasctime, levelname, name, message, linenoです。

Python
# format_example.py
import logging

logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s.%(msecs)03d %(levelname)s [%(name)s:%(lineno)d] %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
    force=True
)

logger = logging.getLogger(__name__)

logger.info("アプリ起動")
logger.debug("詳細情報。linenoやモジュール名を確認できます")
実行結果
# 実行結果(例)
2025-01-01 12:00:00.123 INFO [__main__:11] アプリ起動
2025-01-01 12:00:00.124 DEBUG [__main__:12] 詳細情報。linenoやモジュール名を確認できます

変数を埋め込む(%sフォーマット)

loggingではlogger.info("x=%s", value)のように、引数で埋め込むのが定石です。

レベルで抑止された場合に文字列連結が発生せず、パフォーマンス上有利です。

Python
# interpolate_variables.py
import logging

logging.basicConfig(level=logging.INFO, format="%(levelname)s:%(message)s", force=True)
logger = logging.getLogger(__name__)

user = "alice"
count = 3

# おすすめ: 引数で渡す(遅延フォーマット)
logger.info("ユーザ=%s 件数=%d", user, count)

# 非推奨ではないが注意: f文字列は先に文字列化される
logger.debug(f"ユーザ={user} 件数={count}")  # DEBUGが無効でも文字列化コストがかかる
実行結果
INFO:ユーザ=alice 件数=3

モジュールごとのロガー(getLogger(__name__))

モジュール単位でgetLogger(__name__)を使うと、どのモジュールが書いたログかが自動で分かるようになります。

エントリポイント(実行ファイル)で一度だけ共通設定を行い、各モジュールではロガーを取得して使うのが定石です。

Python
# module_a.py
import logging

logger = logging.getLogger(__name__)  # "module_a" という名前のロガー

def do_work():
    logger.info("do_work を開始")
    logger.debug("内部処理の詳細")
    return "OK"
Python
# main.py
import logging
import module_a

# アプリ全体の初期化はエントリポイントで1回だけ
logging.basicConfig(
    level=logging.DEBUG,
    format="%(levelname)s:%(name)s:%(message)s",
    force=True
)

logger = logging.getLogger(__name__)

def main():
    logger.info("アプリ開始")
    result = module_a.do_work()
    logger.info("結果=%s", result)

if __name__ == "__main__":
    main()
実行結果
INFO:__main__:アプリ開始
INFO:module_a:do_work を開始
DEBUG:module_a:内部処理の詳細
INFO:__main__:結果=OK

名前付きロガーは階層構造です。

例えばapp.dbappの子になり、設定(レベルやハンドラ)が継承されます。

初心者が知ると便利な設定と注意点

重複ログを防ぐ(ハンドラの多重追加)

同じロガーにハンドラを何度も追加すると、同じメッセージが重複出力されます

エントリポイントで一度だけ基本設定(またはハンドラ追加)を行い、ライブラリやモジュール側ではgetLogger(__name__)してlogger.debug/info(...)を呼ぶだけにします。

どうしてもロガーに直接ハンドラを付ける必要がある場合は、既に追加済みかを確認してから付与します。

必要に応じてpropagate=Falseで親ロガーへの伝播を止めます。

Python
# avoid_duplicate_handlers.py
import logging
import sys

logger = logging.getLogger("myapp")

# すでにハンドラがあるかを確認
if not logger.handlers:
    handler = logging.StreamHandler(sys.stdout)
    handler.setFormatter(logging.Formatter("%(levelname)s:%(name)s:%(message)s"))
    logger.addHandler(handler)

logger.setLevel(logging.INFO)
logger.propagate = False  # 親(root)へ流さない

logger.info("このメッセージは1回だけ出ます")
実行結果
INFO:myapp:このメッセージは1回だけ出ます

基本方針: 設定は1か所(エントリポイント)、他はロガー取得のみを守ると、ほとんどの重複問題を避けられます。

日本語の文字化け対策(encoding指定)

ファイル出力ではencoding="utf-8"を必ず指定します。

Windows環境などで文字化けが起きやすいためです。

コンソールの文字化けはターミナル側のエンコーディング設定に依存しますが、解析対象はファイルにUTF-8で残す方針がおすすめです。

Python
# japanese_encoding.py
import logging

logging.basicConfig(
    filename="nihongo.log",
    level=logging.INFO,
    encoding="utf-8",
    format="%(asctime)s %(levelname)s %(message)s",
    datefmt="%H:%M:%S",
    force=True
)

logging.info("日本語が正しく記録されます")
logging.info("絵文字もOKです 👍")
実行結果
# nihongo.log の内容(例)
12:00:00 INFO 日本語が正しく記録されます
12:00:01 INFO 絵文字もOKです 👍

root loggerとloggerの違い

logging.getLogger()で名前を省略するとrootロガーが返ります。

basicConfigは主にrootロガーを初期化します。

名前付きロガー(例:getLogger("myapp"))は階層的にrootの子孫に位置づけられ、レベルやハンドラ設定を継承します。

Python
# root_vs_named.py
import logging

logging.basicConfig(level=logging.INFO, format="%(name)s:%(message)s", force=True)

root = logging.getLogger()          # root
named = logging.getLogger("myapp")  # 子ロガー

root.info("rootからのメッセージ")
named.info("myappからのメッセージ")
実行結果
root:rootからのメッセージ
myapp:myappからのメッセージ

特定モジュールの詳細だけ出したい場合は、logging.getLogger("myapp").setLevel(logging.DEBUG)のように部分的にレベルを下げると便利です。

例外を記録する(logging.exception)

logging.exceptionは、例外ハンドラ( except 節)の中で呼ぶとスタックトレース付きでエラーを記録します。

exc_info=Trueを使う方法も同等です。

Python
# log_exception.py
import logging

logging.basicConfig(level=logging.ERROR, format="%(levelname)s:%(message)s", force=True)

def div(a, b):
    return a / b

try:
    div(1, 0)
except ZeroDivisionError:
    logging.exception("0除算が発生しました")
実行結果
# 実行結果(例)
ERROR:0除算が発生しました
Traceback (most recent call last):
  File "log_exception.py", line 9, in <module>
    div(1, 0)
  File "log_exception.py", line 6, in div
    return a / b
ZeroDivisionError: division by zero

例外の起点と経路が明確になるため、解析効率が大幅に向上します。

かんたんにログレベルを切り替える(環境変数など)

運用中にコードを変えずにレベルを切り替えるには、環境変数を使います。

LOG_LEVELを読み取り、なければINFOにフォールバックする例です。

Python
# level_from_env.py
import logging
import os

level_name = os.getenv("LOG_LEVEL", "INFO").upper()
# 無効な値なら INFO にする
level = getattr(logging, level_name, logging.INFO)

logging.basicConfig(level=level, format="%(levelname)s:%(message)s", force=True)
logger = logging.getLogger(__name__)

logger.debug("DEBUGメッセージ")
logger.info("INFOメッセージ")
logger.warning("WARNINGメッセージ")
実行結果
# 実行結果(環境変数を設定しない場合 → INFO)
INFO:INFOメッセージ
WARNING:WARNINGメッセージ
実行結果
# 実行結果(環境変数 LOG_LEVEL=DEBUG の場合)
DEBUG:DEBUGメッセージ
INFO:INFOメッセージ
WARNING:WARNINGメッセージ

レベルを環境変数で制御する設計にしておくと、本番トラブル時も即座に詳細ログへ切り替えられます

まとめ

本稿では、Python標準ライブラリloggingの基本から実務で役立つ設定まで解説しました。

ログは「実行時の事実」を残すための資産であり、レベル、出力先、フォーマットを適切に設計することで、開発効率と運用品質が大きく向上します。

特に最初の1回だけbasicConfigで初期化する設計モジュールではgetLogger(__name__)を使う文字化け対策にencoding="utf-8"例外はlogging.exceptionで記録レベルは環境変数で切替といったポイントを押さえれば、初心者の方でも安心してログ運用を始められます。

最後にもう一度、printではなくloggingを使うという原則を意識して、堅牢なログ基盤を作っていきましょう。

この記事を書いた人
エーテリア編集部
エーテリア編集部

人気のPythonを初めて学ぶ方向けに、文法の基本から小さな自動化まで、実際に手を動かして理解できる記事を書いています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!