Python の TimedRotatingFileHandler でログ出力するコード例です。
『ログ出力』と『ログファイルの時間経過によるローテーション』は、TimedRotatingFileHandler()
を使うとできました。
コード例です(10 秒ごとにログファイルをローテートします)。
import logging, logging.handlers, pathlib, time
logger = logging.getLogger(__name__)
def main():
root = logging.getLogger()
root.setLevel(logging.DEBUG)
log_txt = pathlib.Path(r'F:\apps\data\log.txt')
th = logging.handlers.TimedRotatingFileHandler(
log_txt, encoding='utf-8',
when='S', interval=10, backupCount=5,
)
root.addHandler(th)
for text in ['ああ', 'いい', 'うう', 'ええ', 'おお']:
time.sleep(5)
logger.info(text)
return
if __name__ == '__main__':
main()
実行結果です。
log.txt
作成日時: 2022年4月7日、2:44:26
ええ
おお
log.txt.2022-04-07_02-44-16
作成日時: 2022年4月7日、2:44:16
いい
うう
log.txt.2022-04-07_02-44-06
作成日時: 2022年4月7日、2:44:06
ああ
以上です。
ログファイルを自動で分割しながら、テキストファイルに出力することができました。
古いログも、自動でリネームして、ログファイルをローテーションして、残してくれました。
ログ分割のタイミング設定 when には、秒、分、時、日、曜日などの選択肢がありました。
ログのバックアップの数 backupCount を指定したら、その数だけ、分割後の古いログを残してくれました。
時間の経過で、古いログが『流れて消えていく仕組み』を作ることができました。
また、ログのバックアップの数を、デフォルトの backupCount=0
にしたら、分割後の古いログがすべて残りました。
ファイルの数は無制限に増えていきましたが、すべてのログを保存することができました。
以下、解説付きのコード例です。
コード例
時間経過でログファイルをローテーションしながら記録する Python コード例です。
TimedRotatingFileHandler() を使用します。
OS は Windows 10 Pro (64 bit) で、Python 3.8.6 (64 bit) を使用しました。
"""TimedRotatingFileHandler でログ出力する Python コード例です。"""
import logging, logging.handlers, pathlib
import time, datetime # デバッグ用
# (1/5) 名前付きロガーを取得します。
# 名前付きロガー取得時のロギングレベルは NOTSET (0) です。
logger = logging.getLogger(__name__)
# logger.setLevel(logging.NOTSET)
def main():
"""メイン関数です。"""
# (2/5) ルートロガーを取得します。
# ルートロガー取得時のロギングレベルは WARNING (30) です。
root = logging.getLogger()
root.setLevel(logging.DEBUG)
# 最初は NOTSET / DEBUG / INFO あたりに設定しておきます。
# (お好みで) ルートロガーにストリームハンドラを追加します。
# どのハンドラも生成時のロギングレベルは NOTSET (0) です。
sh = logging.StreamHandler()
# sh.setLevel(logging.NOTSET)
sh.setFormatter(logging.Formatter('%(name)s %(levelname)s: %(message)s'))
root.addHandler(sh)
# (3/5) ルートロガーにタイムドローテーティングファイルハンドラを追加します。
# どのハンドラも生成時のロギングレベルは NOTSET (0) です。
log_txt = pathlib.Path(r'F:\apps\data\log.txt')
th = logging.handlers.TimedRotatingFileHandler(
log_txt, encoding='utf-8',
when='S', interval=10, backupCount=5,
)
# th.setLevel(logging.NOTSET)
th.setFormatter(logging.Formatter('%(name)s %(levelname)s: %(message)s'))
root.addHandler(th)
# (4/5) 自作関数を実行します。
root.debug('start')
root.info('%(name)s %(levelname)s: %(message)s')
jisaku_func()
root.debug('end')
print('\n(デバッグ) ログの作成日時 (ctime) を取得してみます。')
# ログファイルを列挙して、作成日時を表示していきます。
# p は Path の略です。
for p in pathlib.Path(r'F:\apps\data').glob('*log.txt*'):
if p.is_file():
mtime = datetime.datetime.fromtimestamp(p.stat().st_ctime)
print(f'({p}) {mtime.strftime("%Y-%m-%d_%H-%M-%S")}')
# type(p.stat().st_ctime): <class 'float'> (タイムスタンプ)
print('\n(デバッグ) ロガーとハンドラのロギングレベルを確認します。')
print(f'root.level: {logging.getLevelName(root.level)} ({root.level})')
print(f'root.getEffectiveLevel(): {logging.getLevelName(root.getEffectiveLevel())} ({root.getEffectiveLevel()})')
print(f'logger.level: {logging.getLevelName(logger.level)} ({logger.level})')
print(f'logger.getEffectiveLevel(): {logging.getLevelName(logger.getEffectiveLevel())} ({logger.getEffectiveLevel()})')
print(f'sh.level: {logging.getLevelName(sh.level)} ({sh.level})')
print(f'rh.level: {logging.getLevelName(th.level)} ({th.level})')
return
def jisaku_func():
"""自作関数です。"""
# (5/5) 名前付きロガーでログを記録してみます。
# (ログはルートロガーに取り付けたハンドラで記録されます)
temperatures = [ # 架空の気温
20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
]
for t in temperatures:
# (デバッグ) 1 秒待ちます。
time.sleep(1)
if t < 24:
logger.debug(f'気温 {t} ℃')
elif 24 <= t < 28:
logger.info(f'暑さに注意 {t} ℃')
elif 28 <= t < 31:
logger.warning(f'暑さに警戒 {t} ℃')
elif 31 <= t < 36:
logger.error(f'暑さに厳重警戒 {t} ℃')
elif 36 <= t:
logger.critical(f'危険な気温です {t} ℃')
return
if __name__ == '__main__':
main()
when='S'
と interval=10
で、ログのローテーションを 10 秒に設定しました。
なので、10 秒経過するごとに、ログファイルのローテーションが発生するはず。
実行結果
画面表示
コード例を実行したときの画面表示です。StreamHandler()
と print()
関数による表示です。
root DEBUG: start
root INFO: %(name)s %(levelname)s: %(message)s
__main__ DEBUG: 気温 20 ℃
__main__ DEBUG: 気温 21 ℃
__main__ DEBUG: 気温 22 ℃
__main__ DEBUG: 気温 23 ℃
__main__ INFO: 暑さに注意 24 ℃
__main__ INFO: 暑さに注意 25 ℃
__main__ INFO: 暑さに注意 26 ℃
__main__ INFO: 暑さに注意 27 ℃
__main__ WARNING: 暑さに警戒 28 ℃
__main__ WARNING: 暑さに警戒 29 ℃
__main__ WARNING: 暑さに警戒 30 ℃
__main__ ERROR: 暑さに厳重警戒 31 ℃
__main__ ERROR: 暑さに厳重警戒 32 ℃
__main__ ERROR: 暑さに厳重警戒 33 ℃
__main__ ERROR: 暑さに厳重警戒 34 ℃
__main__ ERROR: 暑さに厳重警戒 35 ℃
__main__ CRITICAL: 危険な気温です 36 ℃
__main__ CRITICAL: 危険な気温です 37 ℃
__main__ CRITICAL: 危険な気温です 38 ℃
__main__ CRITICAL: 危険な気温です 39 ℃
root DEBUG: end
(デバッグ) ログの作成日時 (ctime) を取得してみます。
(F:\apps\data\log.txt) 2022-04-07_16-37-33
(F:\apps\data\log.txt.2022-04-07_16-37-13) 2022-04-07_16-37-13
(F:\apps\data\log.txt.2022-04-07_16-37-23) 2022-04-07_16-37-23
(デバッグ) ロガーとハンドラのロギングレベルを確認します。
root.level: DEBUG (10)
root.getEffectiveLevel(): DEBUG (10)
logger.level: NOTSET (0)
logger.getEffectiveLevel(): DEBUG (10)
sh.level: NOTSET (0)
rh.level: NOTSET (0)
ログファイル
ログファイルの内容です。
作成日時は Windows のファイルのプロパティで確認した時刻です。
log.txt
作成日時: 2022年4月7日、16:37:33
__main__ CRITICAL: 危険な気温です 39 ℃
root DEBUG: end
log.txt.2022-04-07_16-37-23
作成日時: 2022年4月7日、16:37:23
__main__ WARNING: 暑さに警戒 29 ℃
__main__ WARNING: 暑さに警戒 30 ℃
__main__ ERROR: 暑さに厳重警戒 31 ℃
__main__ ERROR: 暑さに厳重警戒 32 ℃
__main__ ERROR: 暑さに厳重警戒 33 ℃
__main__ ERROR: 暑さに厳重警戒 34 ℃
__main__ ERROR: 暑さに厳重警戒 35 ℃
__main__ CRITICAL: 危険な気温です 36 ℃
__main__ CRITICAL: 危険な気温です 37 ℃
__main__ CRITICAL: 危険な気温です 38 ℃
log.txt.2022-04-07_16-37-13
作成日時: 2022年4月7日、16:37:13
root DEBUG: start
root INFO: %(name)s %(levelname)s: %(message)s
__main__ DEBUG: 気温 20 ℃
__main__ DEBUG: 気温 21 ℃
__main__ DEBUG: 気温 22 ℃
__main__ DEBUG: 気温 23 ℃
__main__ INFO: 暑さに注意 24 ℃
__main__ INFO: 暑さに注意 25 ℃
__main__ INFO: 暑さに注意 26 ℃
__main__ INFO: 暑さに注意 27 ℃
__main__ WARNING: 暑さに警戒 28 ℃
指定した時間の経過で、ログが分割されていました。
ローテーションは when='S'
と interval=10
で、10 秒に設定していました。
実際に、ファイル名の末尾に追加された日時の秒数から、10 秒間隔でローテーションが発生していたのが分かります。
ロギングの記事
logging モジュールでロガーを使う方法を書きました。
concurrent.futures
でログを記録するコード例。
multiprocessing
でログを記録するコード例。
ロガーで自動的に付加できる情報の表示例です。
エラーを記録するときは、自作例外でエラー名をつけてあげると便利でした。
Python マニュアル
コード例に関係する Python マニュアルの場所です。
モジュールレベルの関数
(Python) logging.getLevelName(level)
ロガー
(Python) logging.getLogger(name=None)
(Python) ロガー名の付け方(上級ロギングチュートリアル)logger = logging.getLogger(__name__)
(Python) logging.Logger.propagate
属性
(Python) logging.Logger.setLevel(level)
『名前付きロガー』取得時のロギングレベルは NOTSET で、『ルートロガー』取得時のロギングレベルは WARNING である旨の説明がありました。
(Python) ロギングレベル (..., logging.DEBUG, logging.INFO, ...)
(Python) logging.Logger.addHandler(hdlr)
(Python) logging.Logger.removeHandler(hdlr)
(Python) logging.Logger.debug(msg, *args, **kwargs)
(Python) logging.Logger.info(msg, *args, **kwargs)
(Python) logging.Logger.warning(msg, *args, **kwargs)
(Python) logging.Logger.error(msg, *args, **kwargs)
(Python) logging.Logger.critical(msg, *args, **kwargs)
ロガーはグローバル変数に入れて使うのが便利でした。普通にグローバルのところで代入しても OK でしたし、global 文を使用してから代入しても OK でした。
もちろん、ローカル変数に入れて使ってもいいと思いますし、logging.getLogger(name=None)
で都度取得して使用してもいいと思います。お好みで。
ハンドラ
タイムド ローテーティング ファイルハンドラ
(Python) 便利なハンドラ(各種ハンドラの日本語の説明)
(Python) class logging.StreamHandler(stream=None)
(Python) class logging.FileHandler(filename, mode='a', encoding=None, delay=False, errors=None)
(Python) logging.Handler.setLevel(level)
『ハンドラ』生成時のロギングレベルは NOTSET(すべてのメッセージが処理される)である旨の説明がありました。
(Python) logging.Handler.setFormatter(fmt)
フォーマッタ
(Python) class logging.Formatter(fmt=None, datefmt=None, style='%', validate=True, *, defaults=None)
(Python) LogRecord 属性 %(asctime)s, %(levelname)s, %(levelno)s, %(message)s, %(name)s, ...
パスリブ
(Python) class pathlib.Path(*pathsegments)
(Python) Path.stat(*, follow_symlinks=True)
(Python) os.stat_result.st_ctime
属性 Windows では作成時刻で、Unix ではメタデータの最終更新時刻とのこと。タイムスタンプを表す float 型の数値が入っていました。
タイム
デートタイム
(Python) classmethod datetime.fromtimestamp(timestamp, tz=None)
(Python) datetime.strftime(format)
組み込み機能
(Python) __main__
(トップレベルのスクリプト環境)
(Python) __name__
(インポート関連のモジュール属性)
引用符の前に『アール r 』を付けた文字列
(Python) r''
(raw strings、raw 文字列)(チュートリアル)
(Python) r''
(raw strings、raw 文字列)(言語リファレンス)
引用符の前に『エフ f 』を付けた文字列
(Python) f''
(f-string、formatted string literal、フォーマット済み文字列リテラル)(言語リファレンス)
r''
と f''
を組み合わせた文字列 rf''
(Python) rf''
フォーマット済みの raw 文字列リテラル
以上です。