Q1logging.basicConfig(level=logging.INFO)で設定した場合、出力されないログレベルはどれですか?
logging — print の代わりに記録する
5段階のログレベルとbasicConfigでの書式設定、%(levelname)sなどの書式コード、FileHandlerでのファイル出力でprintデバッグを卒業する方法を実例で学べます。
loggingはアプリの動きを段階的に記録するための標準モジュールです。printでデバッグ出力を書くと、本番では消したいのに消し忘れる、重要度の区別が付かない、ファイルにも出したくなるけど書き換えが大変といった問題が出ます。loggingを使うと、重要度(DEBUG / INFO / WARNING / ERROR / CRITICAL)で出力を絞れて、Formatterで書式を統一でき、Handlerで出力先(標準出力 / ファイル / リモート)を切り替えられます。
logger.info(...)を呼ぶと、Logger がレベルでフィルタ → Handler が出力先を決める → Formatter が書式を組み立てるという流れで処理される。3 つの責務が分かれているので、書式や出力先を 1 箇所で変えるだけで全体の挙動が変わる。ログレベル — 5 段階の重要度
logging は5 段階のレベルを持ち、Logger に設定したレベル以上のメッセージだけが出力されます。開発中はDEBUG、本番はINFOやWARNINGに設定する運用が一般的で、コード側を変えずに設定 1 箇所で出力量を切り替えられるのがprintとの最大の違いです。
WARNING / ERROR / DEBUG のレベル使い分け
ロガーが設定済みの状態で、INFO 以外のレベルを呼び分けて出力を観察します。設定レベルが INFO なので、DEBUG は何も出力されないことも合わせて確認します。これが logging の「設定 1 箇所で出力量を切り替えられる」仕組みの基礎です。
ファイルへログを出力する
実プロジェクトでは、ログを画面に出すだけでなくファイルに残しておきたい場面が大半です。後からgrepで原因調査をしたり、運用監視ツールに取り込んだりするためです。basicConfigにfilename="app.log"を渡せば、ルートロガーの出力先がファイルに切り替わります — printで同じことをするにはすべてのprintを書き換える必要があるのに対し、loggingなら設定 1 箇所で出力先を変えられます。
loggingなら設定 1 箇所で切り替えられる。filemodeは"a"(既定、追記)か"w"(上書き)を指定します。本番運用では追記にして履歴を残しますが、開発中は上書きで毎回まっさらから始めるほうが調査しやすいことが多いです。画面とファイルの両方に出したい場合は、basicConfigではなく後述のStreamHandler + FileHandlerを併用する形になります。
StreamHandler + FileHandlerを別々に組み立ててaddHandlerする。import logging
import os
# 親フォルダを先に作っておく (FileHandler は自動で作らない)
os.makedirs("logs", exist_ok=True)
# basicConfig に filename を渡すと、出力先がファイルに切り替わる
logging.basicConfig(
level=logging.INFO,
format="[%(levelname)s] %(message)s",
filename="logs/app.log", # ファイル出力
filemode="w", # "a" (追記) or "w" (上書き)
force=True,
)
logger = logging.getLogger("app")
logger.info("起動しました")
logger.warning("設定が古い")
logger.error("DB 接続失敗")
# ファイルから読み出して内容を確認
with open("logs/app.log") as f:
print(f.read(), end="")
ハンドラを別ファイルで定義する
アプリが大きくなると、ロガーの設定(書式、レベル、出力先)を専用のモジュールに切り出して、各モジュールからimportして使う構成にします。ロガー設定の重複を避け、書式や出力先を 1 箇所で変更できるようになります。StreamHandlerを直接組み立て、Formatterを割り当てて、logger.addHandler(...)で登録する手動構成のパターンを別ファイルに置く形です。
main.py / orders.py / users.py) はsetup_logger(__name__)を呼ぶだけで、設定済みのロガーを取得できる。書式や出力先を変えたいときはlog_setup.pyの中だけを編集すればよい。# log_setup.py — ロガー設定を切り出した専用モジュール
import logging
def setup_logger(name):
logger = logging.getLogger(name)
logger.setLevel(logging.INFO)
if not logger.handlers: # 重複登録を防ぐ
handler = logging.StreamHandler()
handler.setFormatter(
logging.Formatter("[%(name)s] [%(levelname)s] %(message)s")
)
logger.addHandler(handler)
logger.propagate = False # ルートに伝播させない
return logger
# main.py — log_setup を import して使う側
from log_setup import setup_logger
logger = setup_logger("orders")
logger.info("注文を受け付けました")
より詳細な書式 — 時刻・モジュール名・行番号
Formatterの書式文字列には%(...)s形式の置換コードを並べることで、運用に必要な情報を 1 行に集約できます。よく使う 6 つの置換コードと出力例を表にまとめます。ロガー名はparent.child.grandchildのようなドット区切りで階層化でき、getLogger(__name__)を各モジュールで呼ぶだけで「どのモジュールから出たログか」が自動で記録されます。
| 置換コード | 出力する内容 | 出力例 |
|---|---|---|
| %(asctime)s | タイムスタンプ | 2024-12-01 10:30:45 |
| %(name)s | ロガー名 (ドット区切りで階層化可) | app.orders |
| %(levelname)s | ログレベル | INFO / WARNING / ERROR |
| %(funcName)s | 呼び出し元の関数名 | process_order |
| %(lineno)d | 呼び出し行番号 | 42 |
| %(message)s | メッセージ本体 | 注文を処理 |
import logging
# 本番運用向けの詳細書式
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(name)s] [%(levelname)s] %(funcName)s:%(lineno)d - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
force=True,
)
logger = logging.getLogger("app.orders")
def process_order(order_id):
logger.info(f"注文 {order_id} を処理")
process_order(1234)
# 出力例:
# 2024-12-01 10:30:45 [app.orders] [INFO] process_order:13 - 注文 1234 を処理
yaml で設定を切り出す
ロガー設定が複雑になってくると、Python コードの中に書式・ハンドラ・レベルをハードコードする代わりにyamlの設定ファイルに切り出す形にします。logging.config.dictConfig(...)はdict形式の設定を受け取るので、yaml をパースして渡すだけでロガー全体を構成できます。コードを変えずに本番・ステージング・開発で別の設定ファイルを読み込ませる、といった運用が一気にしやすくなります。
# logging.yml — ロガー設定の単一ソース
version: 1
disable_existing_loggers: false
formatters:
default:
format: "[%(name)s] [%(levelname)s] %(message)s"
handlers:
console:
class: logging.StreamHandler
formatter: default
level: INFO
loggers:
app:
level: INFO
handlers: [console]
propagate: false
| キー | 役割 | 備考 |
|---|---|---|
| version | dictConfig のスキーマバージョン | 現状は 1 のみ。必須項目 |
| disable_existing_loggers | 既存ロガーを無効にするか | false 推奨 (true だと前のセクションで作ったロガーが止まる) |
| formatters | Formatter (書式) の名前付き一覧 | default など任意の名前で定義し、handlers から参照する |
| formatters.<name>.format | 書式文字列 (%(...)s 置換コード) | コード内の Formatter 引数と同じ書式 |
| handlers | Handler (出力先) の名前付き一覧 | console / file / mail などを定義 |
| handlers.<name>.class | Handler クラスの完全修飾名 | logging.StreamHandler / logging.FileHandler / logging.handlers.RotatingFileHandler など |
| handlers.<name>.formatter | 適用する Formatter の名前 | formatters セクションで定義したキーを書く |
| handlers.<name>.level | Handler 単位のレベル | Logger より細かく出力を絞れる |
| loggers | Logger の名前付き一覧 | loggers.app は logging.getLogger("app") で取得 |
| loggers.<name>.handlers | このロガーに付ける Handler 名のリスト | [console, file] のように複数指定可 |
| loggers.<name>.propagate | 親ロガーへの伝播を許可するか | false で止めると root ロガーへ二重出力されない |
ログのローテーション
ログを書き続けるとファイルが無限に肥大化するため、本番運用では古いファイルを別名にして削除する ローテーションが必須です。logging.handlersにはサイズベースと時刻ベースの 2 種類があり、用途に応じて使い分けます。
RotatingFileHandler — サイズベース
RotatingFileHandler(filename, maxBytes, backupCount)はmaxBytesを超えそうになると新しいファイルに切り替える Handler です。古いファイルはapp.log.1 / app.log.2の連番でリネームされ、backupCountを超えると最古から削除されます。maxBytes=10_000_000(10 MB) + backupCount=5なら最大 60 MB を上限にログが回り続けます。
maxBytesを超えそうになると、app.log → app.log.1にリネームし、新しいapp.logを作って書き込みを続ける。すでにapp.log.Nが存在していればapp.log.N → app.log.N+1にずらされ、backupCountを超えた最古のファイルは削除される。import logging
from logging.handlers import RotatingFileHandler
# サイズベース: maxBytes 超過でローテーション
size_handler = RotatingFileHandler(
"app.log",
maxBytes=10_000_000, # 10 MB を超えそうになると切り替え
backupCount=5, # app.log.1 〜 app.log.5 を保持 (合計最大 60MB)
)
size_handler.setFormatter(logging.Formatter("[%(levelname)s] %(message)s"))
TimedRotatingFileHandler — 時刻ベース
TimedRotatingFileHandler(filename, when, interval, backupCount)はwhenで指定した時刻の節目でローテーションする Handler です。when="midnight"(毎日 0 時)、"H"(時)、"M"(分)、"S"(秒)、"D"(日) など。バックアップはapp.log.2024-12-01_00-00-00のようにタイムスタンプ付きで、ファイル名から「いつのログか」が直接読み取れます。
app.logを開く。backupCountを超えた古いタイムスタンプのファイルは自動削除される。import logging
from logging.handlers import TimedRotatingFileHandler
# 時刻ベース: 毎日 0 時にローテーション
day_handler = TimedRotatingFileHandler(
"app.log",
when="midnight", # "S" 秒 / "M" 分 / "H" 時 / "D" 日 / "midnight" など
interval=1, # 何単位ごとか (when="H", interval=6 なら 6 時間ごと)
backupCount=30, # 過去 30 日分を保持
)
day_handler.setFormatter(
logging.Formatter("%(asctime)s [%(levelname)s] %(message)s")
)
理解度チェック
まずは1問ずつ答えてみましょう。
Q2logging.basicConfigにfilename="app.log"を渡したとき、ログ出力先はどうなりますか?
Q3ロガー設定(書式・レベル・ハンドラ)を別モジュールに切り出すことの主なメリットはどれですか?