はじめに
Pythonでのデバッグや本番監視に print を使い続けていませんか?print はシンプルですが、ログレベルの制御、ファイルへの出力、タイムスタンプの付与、本番での無効化が難しいです。
Pythonの標準ライブラリ logging を使うと、これらすべてを宣言的に制御できます。本記事では基礎から実践パターンまでを解説します。
ログレベルの種類
logging には5段階のログレベルがあります。
| レベル | 数値 | 用途 |
|---|---|---|
DEBUG | 10 | 詳細なデバッグ情報(開発時のみ) |
INFO | 20 | 正常系の処理記録 |
WARNING | 30 | 予期しない状況だが処理は継続できる |
ERROR | 40 | 処理が失敗した |
CRITICAL | 50 | システムが続行不可能な致命的エラー |
デフォルトのルートロガーは WARNING 以上のみ出力します。
基本的な使い方
basicConfigによる設定
import logging
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s %(levelname)s %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
logging.debug("デバッグ情報")
logging.info("正常処理")
logging.warning("警告")
logging.error("エラー発生")
logging.critical("致命的エラー")
出力例:
2026-03-12 10:00:00 DEBUG デバッグ情報
2026-03-12 10:00:00 INFO 正常処理
2026-03-12 10:00:00 WARNING 警告
2026-03-12 10:00:00 ERROR エラー発生
2026-03-12 10:00:00 CRITICAL 致命的エラー
モジュールごとのロガー
本番コードでは logging.getLogger(__name__) でモジュール専用のロガーを作ります。どのモジュールからのログかが一目でわかります。
import logging
logger = logging.getLogger(__name__)
def process_data(data):
logger.info("処理開始: %d件", len(data))
try:
result = [x * 2 for x in data]
logger.debug("処理結果: %s", result)
return result
except Exception as e:
logger.error("処理失敗: %s", e, exc_info=True)
raise
exc_info=True を指定するとスタックトレースも記録されます。
フォーマットのカスタマイズ
%(...)s スタイルのフォーマット文字列でログの見た目を自由に変えられます。
import logging
formatter = logging.Formatter(
fmt="%(asctime)s [%(levelname)-8s] %(name)s:%(lineno)d - %(message)s",
datefmt="%Y-%m-%dT%H:%M:%S",
)
handler = logging.StreamHandler()
handler.setFormatter(formatter)
logger = logging.getLogger("myapp")
logger.setLevel(logging.DEBUG)
logger.addHandler(handler)
logger.info("サービス起動")
出力例:
2026-03-12T10:00:00 [INFO ] myapp:10 - サービス起動
主要なフォーマット変数:
| 変数 | 内容 |
|---|---|
%(asctime)s | タイムスタンプ |
%(levelname)s | ログレベル名 |
%(name)s | ロガー名 |
%(filename)s | ファイル名 |
%(lineno)d | 行番号 |
%(funcName)s | 関数名 |
%(message)s | ログメッセージ |
%(process)d | プロセスID |
%(thread)d | スレッドID |
ハンドラーの種類
ハンドラーはログの出力先を決めます。複数のハンドラーを同時に使えます。
StreamHandler(標準出力/標準エラー)
import logging
import sys
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(logging.INFO)
FileHandler(ファイル出力)
file_handler = logging.FileHandler("app.log", encoding="utf-8")
file_handler.setLevel(logging.DEBUG)
RotatingFileHandler(ログローテーション)
本番環境ではログが肥大化しないよう、ファイルサイズで自動ローテーションします。
from logging.handlers import RotatingFileHandler
rotating_handler = RotatingFileHandler(
"app.log",
maxBytes=10 * 1024 * 1024, # 10MB
backupCount=5, # 最大5世代保持
encoding="utf-8",
)
app.log が 10MB に達すると app.log.1 にリネームされ、新しい app.log が作られます。
TimedRotatingFileHandler(時刻ローテーション)
日次でローテーションする場合:
from logging.handlers import TimedRotatingFileHandler
timed_handler = TimedRotatingFileHandler(
"app.log",
when="midnight", # 毎日深夜にローテーション
interval=1,
backupCount=30, # 30日分保持
encoding="utf-8",
)
実践:アプリケーション全体の設定
複数モジュールにまたがるアプリでは、専用の設定関数を用意します。
import logging
import sys
from logging.handlers import RotatingFileHandler
def setup_logging(log_level: str = "INFO", log_file: str = "app.log") -> None:
"""アプリケーション全体のログ設定を初期化する"""
level = getattr(logging, log_level.upper(), logging.INFO)
formatter = logging.Formatter(
fmt="%(asctime)s [%(levelname)-8s] %(name)s - %(message)s",
datefmt="%Y-%m-%dT%H:%M:%S",
)
# コンソール: INFO以上
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(logging.INFO)
console_handler.setFormatter(formatter)
# ファイル: DEBUG以上(ローテーション付き)
file_handler = RotatingFileHandler(
log_file,
maxBytes=10 * 1024 * 1024,
backupCount=5,
encoding="utf-8",
)
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(formatter)
# ルートロガーに設定
root_logger = logging.getLogger()
root_logger.setLevel(level)
root_logger.addHandler(console_handler)
root_logger.addHandler(file_handler)
if __name__ == "__main__":
setup_logging(log_level="DEBUG")
logger = logging.getLogger(__name__)
logger.info("アプリケーション起動")
logger.debug("デバッグ情報(ファイルのみ)")
例外のログ記録
例外処理で logger.exception() を使うと、メッセージとスタックトレースを同時に記録します。
import logging
logger = logging.getLogger(__name__)
def divide(a, b):
try:
return a / b
except ZeroDivisionError:
logger.exception("ゼロ除算エラー: a=%s, b=%s", a, b)
return None
divide(10, 0)
出力例:
2026-03-12T10:00:00 [ERROR ] __main__ - ゼロ除算エラー: a=10, b=0
Traceback (most recent call last):
File "example.py", line 7, in divide
return a / b
ZeroDivisionError: division by zero
辞書設定(dictConfig)
大規模アプリでは logging.config.dictConfig を使い、設定を辞書(またはYAML/JSON)で管理します。
import logging
import logging.config
LOGGING_CONFIG = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"standard": {
"format": "%(asctime)s [%(levelname)s] %(name)s: %(message)s",
},
"detailed": {
"format": "%(asctime)s [%(levelname)-8s] %(name)s:%(lineno)d %(funcName)s() - %(message)s",
},
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"level": "INFO",
"formatter": "standard",
"stream": "ext://sys.stdout",
},
"file": {
"class": "logging.handlers.RotatingFileHandler",
"level": "DEBUG",
"formatter": "detailed",
"filename": "app.log",
"maxBytes": 10485760,
"backupCount": 5,
"encoding": "utf-8",
},
},
"loggers": {
"myapp": {
"handlers": ["console", "file"],
"level": "DEBUG",
"propagate": False,
},
},
"root": {
"handlers": ["console"],
"level": "WARNING",
},
}
logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("myapp")
logger.info("dictConfigで設定したロガー")
構造化ログ(structlog)
JSONログはログ収集基盤(Datadog, CloudWatch, ELK)との連携が容易です。外部ライブラリ structlog を使うと構造化ログを簡単に実装できます。
# pip install structlog
import structlog
structlog.configure(
processors=[
structlog.processors.TimeStamper(fmt="iso"),
structlog.stdlib.add_log_level,
structlog.processors.JSONRenderer(),
],
)
log = structlog.get_logger()
log.info("ユーザーログイン", user_id=42, ip="192.168.1.1")
出力例(JSON):
{
"timestamp": "2026-03-12T10:00:00Z",
"level": "info",
"event": "ユーザーログイン",
"user_id": 42,
"ip": "192.168.1.1"
}
structlog なしでJSONログを出す場合、logging.Formatter を継承したカスタムフォーマッターを書く方法もあります。
import json
import logging
class JsonFormatter(logging.Formatter):
def format(self, record):
log_data = {
"timestamp": self.formatTime(record, "%Y-%m-%dT%H:%M:%S"),
"level": record.levelname,
"logger": record.name,
"message": record.getMessage(),
}
if record.exc_info:
log_data["exception"] = self.formatException(record.exc_info)
return json.dumps(log_data, ensure_ascii=False)
handler = logging.StreamHandler()
handler.setFormatter(JsonFormatter())
logger = logging.getLogger("myapp")
logger.addHandler(handler)
logger.info("JSONフォーマットのログ")
printとloggingの比較
| 観点 | print | logging |
|---|---|---|
| ログレベル制御 | 不可 | 5段階で制御可 |
| ファイル出力 | リダイレクトのみ | FileHandler で簡単 |
| タイムスタンプ | 手動で追加 | フォーマッターで自動 |
| スタックトレース | 手動で traceback | exc_info=True で自動 |
| 本番での無効化 | 削除または条件分岐 | レベル設定で一括制御 |
| ログローテーション | 不可 | RotatingFileHandler で対応 |
| 構造化ログ | 不可 | dictConfig + structlog で対応 |
関連記事
- Pythonデコレータの仕組みと実践パターン -
@timerや@retryデコレータとloggingを組み合わせた実装例 - Python asyncio入門 - 非同期コード内でのloggingの注意点
- Pythonでプログレスバーを自作する - CLIツールにおけるログ出力と進捗表示の使い分け
- Pythonでprintの上書きをする方法 - printを活用する場面とloggingとの使い分け