単一の書込担当を前提に、時間/サイズ・保持・出力先を決めます。ロガー全般はログ出力方法へ。ここでは切替条件と非本番の確認に絞ります。
| この記事の要点 |
|
結論: handler・出力先・書込担当を一緒に設計する
ログの基本設定はこちらを参照してください。loggerの階層・伝播・Django既定通知・秘匿はリンク先で確認し、ここではファイル切替に絞ります。handlers の設定に加え、loggerからの接続と保存先が必要です。時間基準は定期タイマーではなく、handlerのemitで期限を調べます。無出力の間は切り替わらず、次の対象レコードを新ファイルへ書きます。空白期間の全日分の空ファイルを作るものでもありません。
旧例の注意:次は設定の断片で完成したsettings.pyではありません。os/LOG_DIR/formatterとlogger接続が必要です。Dは深夜切替でも周期タイマーでもなく、backupCount=30は30日保証ではありません。
'handlers': {
'normal': {
'level': 'DEBUG',
'class': 'logging.handlers.TimedRotatingFileHandler',
'filename': os.path.join(LOG_DIR, 'django.log'),
'formatter': 'normal',
'when': 'D', # 単位 Dは日
'interval': 1, # 何日おきか指定
'backupCount': 30, # バックアップ世代数
},
},
Django の LOGGING 設定は Python 標準の logging.config.dictConfig 形式です。class 以外のキー(when など)は、そのままハンドラクラスのコンストラクタ引数として渡されます。
settings.py の全体例
単一プロセス用の新例:rotation_settings.pyを設定用モジュールとして使い、配備時に用意した絶対パスをbuild_loggingへ渡します。既存LOGGING全体を上書きせず、専用formatter/handler/logger名を衝突確認のうえ統合します。django/rootの既定通知はここでは置換しません。本番のパスを検証fixtureへ渡さないでください。
from pathlib import Path
def build_logging(log_dir):
# Provision this private directory at deployment, not at Django startup.
directory = Path(log_dir)
if not directory.is_absolute() or not directory.is_dir():
raise ValueError("An existing absolute private log directory is required")
return {
"version": 1,
"disable_existing_loggers": False,
"formatters": {"rotation": {"format": "%(levelname)s %(name)s %(message)s"}},
"handlers": {
"rotation": {
"class": "logging.handlers.TimedRotatingFileHandler",
"filename": str(directory / "rotation.log"),
"level": "INFO",
"formatter": "rotation",
"when": "midnight",
"interval": 1,
"backupCount": 7,
"utc": True,
"encoding": "utf-8",
}
},
"loggers": {
"rotation_demo": {
"handlers": ["rotation"],
"level": "DEBUG",
"propagate": False,
}
},
}
旧例の注意:次の旧設定は保持用です。os.makedirsをDjango起動時に実行したり、BASE_DIR配下が公開されないと仮定したりしません。django loggerのhandler置換で既定通知経路が変わり、伝播先によって重複もあり得ます。myapp=DEBUGでもnormal=INFOなのでDEBUGは出ません。
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
LOG_DIR = os.path.join(BASE_DIR, 'logs')
os.makedirs(LOG_DIR, exist_ok=True) # フォルダが無いと起動時にエラー
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'normal': {
'format': '%(asctime)s [%(levelname)s] %(name)s: %(message)s',
},
},
'handlers': {
'normal': {
'level': 'INFO',
'class': 'logging.handlers.TimedRotatingFileHandler',
'filename': os.path.join(LOG_DIR, 'django.log'),
'formatter': 'normal',
'when': 'midnight',
'backupCount': 30,
'encoding': 'utf-8',
},
},
'loggers': {
'django': {
'handlers': ['normal'],
'level': 'INFO',
},
'myapp': {
'handlers': ['normal'],
'level': 'DEBUG',
},
},
}
旧例のmyappは実際のモジュール名と一致させる必要があります。新例ではlogging.getLogger("rotation_demo.jobs").info("JOB_OK")のような固定イベントを関数内で出します。logger=DEBUGでもhandler=INFOなのでDEBUGは書かれません。settings.DEBUGとは別です。utf-8を明示しても、権限・容量・収集障害や秘密値の漏えいまで防ぐものではありません。
TimedRotatingFileHandler の主なパラメータ
| パラメータ | 意味 | 例 |
|---|---|---|
| when | 切り替えの単位。S(秒)/ M(分)/ H(時)/ D(日)/ midnight(深夜 0 時)/ W0〜W6(曜日、W0 が月曜) | 'midnight' |
| interval | 正の整数の間隔。S/M/H/Dは秒換算。weeklyは公式文書の「無視」と検証版の実装に差があるため下記参照 | 1 |
| backupCount | 時間基準の退避ファイル数(現在のファイルは別)。正なら切替時に命名規則に合う余剰を削除。0は切替を続けるが世代削除しない | 30 |
| utc | TrueはUTC、Falseは実行OSのローカル時刻。DjangoのTIME_ZONE/USE_TZとは独立 | False |
| encoding | ファイルの文字コード | 'utf-8' |
時間基準の退避名は django.log.2026-10-02 のような接尾辞付きで、通常は区間開始側の時刻を表します。suffixはwhenで変わります。ファイル名は各行がその暦日に属する保証ではなく、長い無出力期間・再起動・時刻補正も考慮します。保持数は日数ではありません。命名規則やintervalを変えると旧ファイルの削除対象が変わり得るため、設定変更前に既存ファイルを読取確認します。
D と midnight の違い:D/interval=1は86400秒間隔で、初回は既存ファイルのmtime、なければ現在時刻が基準です。再起動時には既存mtimeから再計算するので「常に起動から24時間」ではありません。midnight/interval=1は選んだ時刻系の次の深夜境界を狙いますが、実行は次のemitです。localではDSTの移行により暦日の間隔が23/25時間になり得ます。Dは暦日の同じ時刻への追従ではありません。
atTime=datetime.time(2, 30)はmidnightとW0〜W6の境界計算に使い、S/M/H/Dでは無視されます。utc=Falseのlocal/DSTはOSの規則に依存し、DjangoのTIME_ZONEを変えるだけでは切替基準は変わりません。UTCを使ってもformatterのasctimeは既定でlocalなので、表示時刻をUTCにしたい場合はformatter側も別途設定します。
weeklyの版差:Python 3.12公式文書にはW0〜W6でintervalは無視とありますが、検証したCPython 3.12.10ではinterval=2を次回計算に加算し、interval=1より7日後になります。W0は月曜、W6は日曜です。本例はinterval=1に固定し、複数週間隔やDST境界を使う場合は配備するPython版の実装と実時刻規則で再検証してください。
サイズでローテートする場合
同じrotation_settings.pyと並べるsize_settings.pyです。呼出しにはpathlib.Pathの既存絶対ディレクトリを渡します。時間用とサイズ用を同じファイルへ同時接続せず、設定をどちらか選びます。Timed専用のwhen/interval/utcをRotatingFileHandlerへ残すと構築に失敗するため、handler辞書を丸ごと置き換えます。
from copy import deepcopy
from rotation_settings import build_logging
def build_size_logging(log_dir):
config = deepcopy(build_logging(log_dir))
config["handlers"]["rotation"] = {
"class": "logging.handlers.RotatingFileHandler",
"filename": str(log_dir / "size.log"),
"level": "INFO",
"formatter": "rotation",
"maxBytes": 10 * 1024 * 1024,
"backupCount": 5,
"encoding": "utf-8",
}
return config
旧例の注意:次はhandler辞書の断片で、loggersへの接続とformatter/LOG_DIRが別途必要です。「10MBを超えたら」は厳密な容量上限ではありません。新例の構成と区別してください。
'size_rotate': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'filename': os.path.join(LOG_DIR, 'django.log'),
'formatter': 'normal',
'maxBytes': 10 * 1024 * 1024, # 10MB を超えたら切り替え
'backupCount': 5, # django.log.1 〜 .5 を保持
'encoding': 'utf-8',
},
サイズ基準は1ファイルの成長を抑える目安ですが、ディスク容量の厳密な上限保証ではありません。maxBytes=0ならサイズ切替しません。backupCount=0も世代退避せず現行ファイルが増え続けます(時間基準の0とは違う)。正の両値なら現行size.logと.1〜.5を保持し、.1が直近です。次のレコードを書き込む前にサイズを見ますが、巨大な1レコード・文字コードのバイト差・他の書込元・他ログ・圧縮/外部保持まで含む上限ではありません。容量監視・収集側の保持・必要ならquotaを別途設計します。
落とし穴
- 複数プロセスでの競合: gunicorn や uWSGI でワーカーを複数起動すると、各プロセスが別々にローテートを試み、ログが欠けたり別ファイルに書き続けたりする。stderrの外部収集、またはQueueHandler等から単一の書込担当へ集約する設計を検討する。OSのlogrotate +
logging.handlers.WatchedFileHandlerはUnix向けのdevice/inode変化による再openで、プロセス間書込を直列化しない。copytruncateにはコピーとtruncateの間の欠落リスクがあり、切替・所有者・圧縮タイミングも別途検証する。WindowsにはWatchedFileHandlerは不適切で、開いたファイルのrename制約もある。外部とPython両方で同じファイルをローテートしない - runserver の自動リロード: 開発サーバーは監視用と実行用の 2 プロセスで動くため、Windows ではローテート時に「別のプロセスが使用中」の PermissionError が出ることがある。
--noreloadは開発autoreloadを止めるだけで本番の複数worker問題の解決ではない。FileHandlerへの変更も容量制御や複数プロセス安全性を保証しない - ローテートはログ出力のタイミングで行われる: 深夜にログが 1 行も出なければ、次に出力された時点で切り替わる
- ログフォルダの権限: 公開webroot外の専用ディレクトリを配備時に準備し、実行ユーザーの通過/書込権限・既存ファイルの所有者・容量・永続volumeを確認する。親フォルダはhandlerが作らず、既定では構築時にopenするため起動失敗もある。delay=Trueはopenを遅らせるだけ。起動時mkdir、sudo起動、chmod 777で回避しない
確認方法
PC専用の新手順:次のtemporary_probe.pyを新例と並べ、専用Python/Django環境から実行します。毎回OS一時領域に一意の新規ディレクトリを作り、システム時計ではなくこのテストプロセス内のtime.timeだけを差し替えます。既存ログ・DB・本番設定・logrotateには触れません。保持試験ではこの実行が作ったログだけが削除され、残ったfixtureは表示パスに保持します。
"""PC-only fixture. Retention may delete ONLY files created by this invocation."""
import logging
import logging.config
import os
from pathlib import Path
import tempfile
from unittest.mock import patch
import django
from django.conf import settings
from rotation_settings import build_logging
def run():
directory = Path(tempfile.mkdtemp(prefix="cwiki-20957-owned-"))
config = build_logging(directory)
config["handlers"]["rotation"]["backupCount"] = 2
settings.configure(SECRET_KEY="temporary-fixture-only", INSTALLED_APPS=[], LOGGING=config)
django.setup()
logger = logging.getLogger("rotation_demo.probe")
handler = logging.getLogger("rotation_demo").handlers[0]
removed = []
original_remove = os.remove
def remove_owned(filename):
target = Path(filename).resolve()
assert target.parent == directory.resolve() and target.is_file()
removed.append(target.name)
original_remove(filename)
try:
logger.debug("FILTERED_MARKER")
logger.info("BEFORE_MARKER")
deadline = handler.rolloverAt
# Patch Python's clock in this test process; never change the OS clock.
with patch("logging.handlers.time.time", return_value=deadline + 3 * 86400):
assert list(directory.glob("rotation.log.*")) == []
logger.info("AFTER_IDLE_MARKER")
assert len(list(directory.glob("rotation.log.*"))) == 1
assert "AFTER_IDLE_MARKER" in (directory / "rotation.log").read_text("utf-8")
with patch("os.remove", side_effect=remove_owned):
for _ in range(3):
with patch("logging.handlers.time.time", return_value=handler.rolloverAt):
logger.info("RETENTION_FIXTURE_MARKER")
assert len(list(directory.glob("rotation.log.*"))) == 2
print("Django", django.get_version(), "owned fixture retained at", directory)
print("Retention removed only newly owned fixture files:", removed)
finally:
logging.shutdown()
return directory
if __name__ == "__main__":
run()
旧手順の注意:次の1分切替・アクセス・世代削除は本番に適用しません。待つだけでは切替せず、出力対象のログが必要です。whenを戻しても削除済みログは戻りません。
- 【旧手順・本番では実行禁止】動作確認用に一時的に
'when': 'M'(1 分ごと)にしてサーバーを起動する - 1 分以上空けて何度かアクセスし、
logsフォルダに日時付きのファイルが増えることを確認する - 【旧手順・本番では実行禁止】
backupCountを小さくして古いファイルを削除する試験は、新規作成した明示所有fixtureだけに限定する - 確認後、
whenを本来の値に戻す
関連
- PHPログ出力完全ガイド(error_log/Monolog/PSR-3/Laravel/logrotate)
- cron / crontab の書き方完全ガイド
- Django runserver の使い方と本番運用
根拠と検証範囲
2026年10月26日確認。PCのPython 3.12.10 / Django 5.2.18で新例と実handlerを検査。UTC/local・mtime・atTime・weekly・DST計算、無出力期間、しきい値、時間/サイズの0と保持を確認します。localのDSTはテスト内のZoneInfo変換で模擬し、OSの時計/TZは変更しません。本番権限、Windows複数プロセス、実Unixのlogrotate/WatchedFileHandler、収集基盤・メール・DBの統合は未検証です。
公式資料:Pythonファイルhandlerの仕様、検証版CPython 3.12.10の実装(weeklyの版差を含む)、複数プロセスの設計、DjangoのLOGGING。
子ページはありません
- 環境構築とプロジェクト/アプリの作成
- MVC(MVT)のそれぞれの使い方と説明
- データベースへの接続と操作
- Django Administration
- git管理
- エラー一覧
- バージョンの確認方法
- ログ出力方法
- SQLのログ出力方法
- ログのローテート設定
- settings.pyの定数にアクセスする方法
- 本番環境へのインストールとアプリのデプロイ(apache編)
- 本番環境へのインストールとアプリのデプロイ(nginx編)
- djangoアプリの本番の開始URLを変更する
- 静的(static)ファイルの置き場所と読み込み(画像、css、js )
- CSRFトークンをAjaxで使用する方法
- ajaxの使用例(POST編)
- ファイルのアップロードとファイルの名前
- クイックスタート/チュートリアル
- ログイン機能
- テンプレート側のログイン判定
- ビュー側のログイン判定
- 管理者ユーザーの作成/判定と管理画面
- モデルのjson化とレスポンス
- runserverでポートを指定する方法
- cronによるバッチ実行
- テンプレートで利用する共通のcontextを定義する方法
- プログラムが本番サーバーで反映されない場合の対処法
- APIの作成
- cron用コマンド・ファイルの作成
人気ページ
- 1 Eclipseで「サーバーに追加または除去できるリソースがありません。」の原因と対処法
- 2 tomcat の起動 / 停止ログと catalina.log・catalina.out の違い
- 3 JavaScript で base URL を取得する方法|window.location.origin
- 4 YouTube Data API v3 エラー一覧|403・400・404 の原因と対処
- 5 Laravel エラー一覧|500/Blade/DB 接続/ルーティングの代表エラー
- 6 3Dグラフィックスのソフト一覧|Blender・Metasequoiaと用途別の選び方
- 7 Spring Frameworkのアノテーション一覧
- 8 【Spring】@Valueアノテーションとは
- 9 CATALINA_HOME の確認方法 (Linux / Mac)
- 10 【Spring】@Autowiredアノテーションとは
最近更新/作成されたページ
- Visual Studio「デザイナーを表示できません」エラーの原因と対処 2026-10-28 12:06:00
- SSH 認証に失敗しました。再試行してください の対処 2026-10-28 12:06:00
- SSH2 秘密鍵の読み込みに失敗しました の原因と対処 2026-10-28 12:06:00
- TeraTerm のよくあるエラーと対処法完全ガイド — Connection refused / 2026-10-28 12:06:00
- Visual Studio エラー一覧|コンパイルエラー・警告の確認方法と対処 2026-10-28 11:39:57
- Whitelabel Error Page の原因と消し方|Spring Boot のエラーページ 2026-10-28 11:39:57
- Eclipseで「サーバーに追加または除去できるリソースがありません。」の原因と対処法 2026-10-28 11:39:57
- SQL WHERE 句完全ガイド 2026-10-28 11:39:57
- SQL JOIN 完全ガイド (INNER/LEFT/RIGHT/FULL OUTER) 2026-10-28 11:39:57
- tomcat の起動 / 停止ログと catalina.log・catalina.out の違い 2026-10-28 10:24:23
- Tomcat の起動確認方法 5 つ|localhost:8080・ポート・プロセス・ログ 2026-10-28 10:24:23
- Oracle 19c COMMITの使い方: 暗黙コミット・自動確定・応答不明時の確認 2026-10-28 09:59:09
- Oracle 19c DELETE文|削除対象の確認・COMMIT前の判断・ROLLBACKの条件 2026-10-28 09:59:09
- Oracle 19c INSERT文|INSERT ALL・SELECT・RETURNINGとMERGEの使い分け 2026-10-28 09:59:09
- Oracle 19cのトランザクション制御|COMMIT・ROLLBACK・分離レベルと接続設定 2026-10-28 09:59:09