アプリの振る舞いやエラーの原因をあとから調査するには、実行時の情報を記録する仕組みが欠かせません。
Pythonには標準ライブラリとしてlogging
が用意されており、開発から本番運用まで同じAPIで活用できます。
本稿では基本の使い方からログレベルの考え方、よくあるハマりどころまで、初心者の方に向けて丁寧に解説します。
Pythonのloggingとは?printとの違い
loggingの役割とメリット
Pythonのlogging
は、アプリケーションの実行中に起きた出来事(イベント)を、レベルや発生元、時刻とともに記録するための仕組みです。
ログはデバッグ専用ではなく、運用監視や不具合解析、監査にも耐える「記録」として扱います。
代表的なメリットは次の通りです。
主に文章で説明しますが、要点をまとめます。
まず、ログは重要度(ログレベル)でフィルタできるため、開発時は詳細、運用時は必要最小限に出力を調整できます。
また出力先(ハンドラ)を切り替えれば、コンソール、ファイル、ネットワーク先など柔軟に書き分けられます。
さらにフォーマットを統一すると、誰が見ても読みやすく、機械処理(検索や集計)にも向いた形で残せます。
「どこで、何が、いつ、どのくらい重要か」を機械可読に残せる点が、単なる標準出力との大きな違いです。
printとの違いと使い分け
print
は画面に文字を出すだけの関数で、レベルや出力先の切り替え、統一フォーマットといった機能はありません。
学習初期には便利ですが、スクリプトが成長するにつれ管理が難しくなります。
継続的にメンテナンスされるコードではlogging
が推奨です。
次の表は違いを簡単に比較したものです。
観点 | logging | |
---|---|---|
目的 | 手早い確認出力 | 追跡可能な記録(解析・監視) |
重要度(レベル) | なし | DEBUG/INFO/WARNING/ERROR/CRITICAL |
出力先の切替 | 標準出力のみ | コンソール/ファイル/外部連携など |
フォーマット | 任意の文字列 | 時刻・モジュール名などを自動付与 |
パフォーマンス | 文字列生成が都度実行 | 遅延フォーマットで抑制可能 |
運用適性 | 低い | 高い(標準ライブラリ) |
ログ記録にprint
を使い続けると、どの行が重要かわからず、後からの解析が困難になります。
そのため、実運用を意識するなら早めにlogging
へ移行しましょう。
ログレベルの基本(DEBUG/INFO/WARNING/ERROR/CRITICAL)
ログレベルは重要度を数値で段階付けしたものです。
順序はDEBUG < INFO < WARNING < ERROR < CRITICALです。
レベル | 数値 | 概要 |
---|---|---|
DEBUG | 10 | 開発時の詳細情報(変数値や分岐の状況) |
INFO | 20 | 通常運用の情報(開始/終了、処理件数) |
WARNING | 30 | 注意喚起(想定外だが処理続行可能) |
ERROR | 40 | エラー(機能の一部が失敗) |
CRITICAL | 50 | 致命的(アプリ継続不能、即対応) |
レベルを低く設定するほど多くのログが出ます。
運用では必要最小限、本番障害対応時だけ詳細を開ける運用が現実的です。
ログレベル完全解説(初心者向け)
各ログレベルの意味と使い所
DEBUG
最も詳細な情報です。
例えば「APIレスポンスのJSON」「SQLとバインド変数」「ループ内のカウンタ」といった、開発者が原因を詰めるための情報を出します。
普段の本番環境では基本的に無効にします。
INFO
正常系の進行状況を記録します。
アプリ起動/終了、バッチ処理の件数、外部サービスへの接続成功など、後から状況を時系列で追いやすくする情報を出します。
WARNING
想定外や非推奨の使い方など、注意が必要な出来事を表します。
処理は続行できるが放置すると問題化する可能性があるときに使います。
ERROR
機能の一部が失敗したときに使います。
例外を捕捉して対処したが完全復旧できていない、外部サービスが一時的に落ちている、などが該当します。
CRITICAL
サービス継続が難しい致命的な状況です。
設定ファイルが読めない、必須リソースへ接続不能、といった場合に使います。
開発(DEV)と本番(PROD)のレベル設定の目安
環境 | 推奨初期レベル | 目的 |
---|---|---|
開発(DEV) | DEBUG または INFO | 問題切り分けを優先、詳細に可視化 |
ステージング(STG) | INFO | 本番近い条件での動作確認 |
本番(PROD) | WARNING または INFO | ノイズを抑えつつ必要な情報を確保 |
本番でWARNING以上にしても、障害調査時のみ一時的にDEBUGへ引き上げる運用がよく使われます。
レベルごとの出力例
以下は、各レベルで出力してみた例です。
# 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
で再設定可能)。
# 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
を指定します。
# 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'
を必ず指定しましょう。
# 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 の内容(例)
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です。
# 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)
のように、引数で埋め込むのが定石です。
レベルで抑止された場合に文字列連結が発生せず、パフォーマンス上有利です。
# 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__)
を使うと、どのモジュールが書いたログかが自動で分かるようになります。
エントリポイント(実行ファイル)で一度だけ共通設定を行い、各モジュールではロガーを取得して使うのが定石です。
# module_a.py
import logging
logger = logging.getLogger(__name__) # "module_a" という名前のロガー
def do_work():
logger.info("do_work を開始")
logger.debug("内部処理の詳細")
return "OK"
# 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.db
はapp
の子になり、設定(レベルやハンドラ)が継承されます。
初心者が知ると便利な設定と注意点
重複ログを防ぐ(ハンドラの多重追加)
同じロガーにハンドラを何度も追加すると、同じメッセージが重複出力されます。
エントリポイントで一度だけ基本設定(またはハンドラ追加)を行い、ライブラリやモジュール側ではgetLogger(__name__)
してlogger.debug/info(...)
を呼ぶだけにします。
どうしてもロガーに直接ハンドラを付ける必要がある場合は、既に追加済みかを確認してから付与します。
必要に応じてpropagate=False
で親ロガーへの伝播を止めます。
# 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で残す方針がおすすめです。
# 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の子孫に位置づけられ、レベルやハンドラ設定を継承します。
# 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
を使う方法も同等です。
# 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にフォールバックする例です。
# 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を使うという原則を意識して、堅牢なログ基盤を作っていきましょう。