概要(loggingのフォーマットは「いつ・どこで・何が起きたか」を一行に凝縮する設計)
ログの価値は、後から原因に直行できること。フォーマットはそのための骨組みです。日付・レベル・発生箇所・メッセージを、一定の書式で出力します。重要なのは「何を含めるか」を決めること(最低限の文脈)、「どこに出すか」で書式を使い分けること(コンソールは短く、ファイルは詳細)、そして「可読性」と「機械可読性」のバランスです。
基本のフォーマット(まずは必要最小限を押さえる)
最短の設定と定番フィールド
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s:%(lineno)d %(message)s"
)
logging.info("アプリ起動")
Python- asctime: ログ時刻(datefmtで書式変更可)
- levelname: ログレベル(INFO/ERRORなど)
- name: ロガー名(getLogger(name)の値)
- lineno: 行番号(原因特定に有効)
- message: 実際のログ本文
日付書式の指定(ISO風に揃える)
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(module)s:%(lineno)d %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
logging.info("日付を読みやすく統一")
Python- datefmt: 出力する日時のフォーマット。運用時は「YYYY-MM-DD HH:MM:SS」が無難。
フォーマッタを使い分ける(出力先ごとに最適化)
コンソールは短く、ファイルは詳細に
import logging
logger = logging.getLogger("app")
logger.setLevel(logging.DEBUG)
# コンソール(短く)
ch = logging.StreamHandler()
ch.setLevel(logging.INFO)
ch.setFormatter(logging.Formatter("%(levelname)s %(message)s"))
# ファイル(詳細)
fh = logging.FileHandler("app.log", encoding="utf-8")
fh.setLevel(logging.DEBUG)
fh.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(name)s:%(lineno)d %(message)s"))
logger.addHandler(ch)
logger.addHandler(fh)
logger.info("画面は簡潔、ファイルは詳細")
Python短い書式は「見やすさ」、詳細書式は「原因特定」を優先します。出力先ごとにフォーマッタを分けるのが基本です。
追加フィールドで文脈を強化する
import logging
fmt = "%(asctime)s [%(levelname)s] %(name)s %(process)d/%(threadName)s %(message)s"
logging.basicConfig(level=logging.INFO, format=fmt)
logging.info("プロセスIDやスレッド名も出す")
Python- process/threadName: 並列時に「どこから出たか」を識別可能になります。
変数埋め込みと例外(読みやすく、壊れない形)
f文字列ではなくパラメータで埋め込む(遅延評価で軽量)
import logging
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")
user = "taro"
logging.info("ログイン開始 user=%s", user) # 推奨:必要時のみ整形が走る
Python- 理由: ログが出ないレベルでは整形コストがゼロ。f文字列は常に整形し、無駄が増えます。
例外はスタック込みで一撃(logger.exception)
import logging
logging.basicConfig(level=logging.ERROR, format="%(asctime)s [%(levelname)s] %(message)s")
try:
1 / 0
except Exception:
logging.exception("計算に失敗") # = logging.error("...", exc_info=True)
Python例外の原因箇所をフォーマットに含めるより、スタックトレースを記録する方が圧倒的に役立ちます。
構造化ログとJSON(機械可読性を上げる)
シンプルなJSON風書式(最初の一歩)
import logging
logging.basicConfig(
level=logging.INFO,
format='{"ts":"%(asctime)s","lvl":"%(levelname)s","mod":"%(module)s","line":%(lineno)d,"msg":"%(message)s"}',
datefmt="%Y-%m-%dT%H:%M:%S"
)
logging.info("JSON風の出力で機械処理しやすくする")
Python機械可読性が重要(集約や検索)なら、JSON風にするのが近道。専用ライブラリ(例:structlog)も検討できます。
コンテキスト値を追加する(extra/Adapter)
import logging
logger = logging.getLogger("api")
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(name)s req=%(req_id)s %(message)s"))
logger.addHandler(handler)
logger.setLevel(logging.INFO)
logger.info("開始", extra={"req_id": "R-123"})
logger.info("完了", extra={"req_id": "R-123"})
Python- extra: 動的なキー(req_idなど)をメッセージに差し込み、追跡性を高めます。
時刻とタイムゾーン(正確さが原因特定を近づける)
ローカル時刻かUTCかを決める(運用方針)
asctimeはデフォルトでローカル時刻。集約基盤や多地域運用ではUTCが安全です。UTCを強制したい場合は、フォーマッタをサブクラス化して自前で時刻を入れる方法が堅実です。
import logging, datetime
class UTCFormatter(logging.Formatter):
def formatTime(self, record, datefmt=None):
dt = datetime.datetime.utcfromtimestamp(record.created)
return dt.strftime(datefmt or "%Y-%m-%dT%H:%M:%SZ")
fmt = UTCFormatter("%(asctime)s [%(levelname)s] %(message)s")
handler = logging.StreamHandler()
handler.setFormatter(fmt)
logger = logging.getLogger("utc")
logger.addHandler(handler)
logger.setLevel(logging.INFO)
logger.info("UTCで出す")
Python実務の型(運用で役立つフォーマットひな形)
ひな形1:画面は短く、ファイルは詳細+ローテーション
import logging
from logging.handlers import RotatingFileHandler
logger = logging.getLogger("app")
logger.setLevel(logging.DEBUG)
# 画面:短く
ch = logging.StreamHandler()
ch.setLevel(logging.INFO)
ch.setFormatter(logging.Formatter("%(levelname)s %(message)s"))
# ファイル:詳細+サイズ管理
fh = RotatingFileHandler("app.log", maxBytes=5_000_000, backupCount=3, encoding="utf-8")
fh.setLevel(logging.DEBUG)
fh.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(name)s:%(lineno)d %(message)s"))
logger.addHandler(ch)
logger.addHandler(fh)
logger.info("起動")
logger.debug("詳細(ファイルにのみ)")
Pythonひな形2:APIクライアント向け(URL・遅延・ステータス)
import logging, time, requests
fmt = "%(asctime)s [%(levelname)s] url=%(url)s status=%(status)s latency=%(latency).3fs %(message)s"
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter(fmt))
logger = logging.getLogger("client")
logger.addHandler(handler)
logger.setLevel(logging.INFO)
def get(url):
t0 = time.perf_counter()
try:
r = requests.get(url, timeout=5)
latency = time.perf_counter() - t0
logger.info("GET完了", extra={"url": url, "status": r.status_code, "latency": latency})
return r
except Exception as e:
latency = time.perf_counter() - t0
logger.error("GET失敗: %s", e, extra={"url": url, "status": -1, "latency": latency})
return None
get("https://httpbin.org/get")
Pythonひな形3:テスト時は詳細、CIではINFOに切替
import logging, os
level = logging.DEBUG if os.getenv("APP_DEBUG") == "1" else logging.INFO
logging.basicConfig(level=level, format="%(asctime)s [%(levelname)s] %(name)s:%(lineno)d %(message)s")
logging.debug("ローカルでは詳細を出す")
logging.info("CI/本番ではINFO中心")
Pythonまとめ
フォーマットは「原因に直行するための情報設計」。最低限の文脈(時刻・レベル・発生箇所・本文)を揃え、出力先ごとに最適化する。変数埋め込みは遅延評価で軽くし、例外はスタック込みで確実に残す。必要に応じてJSON風に構造化し、extraでリクエストIDや遅延などのコンテキストを加える。時刻はローカルかUTCかを決め、運用に合わせて一貫させる。この型を押さえれば、初心者でも「見やすい・追える・使える」ログが短いコードで設計できます。
