【Python】ログファイルを『時間でローテーション』するコード例 TimedRotatingFileHandler

Python

Python の TimedRotatingFileHandlerタイムド ローテーティング ファイル ハンドラ でログ出力するコードれいです。

『ログ出力』と『ログファイルの時間経過によるローテーション』は、TimedRotatingFileHandler() を使うとできました。

(Python) class logging.handlers.TimedRotatingFileHandler(filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False, atTime=None, errors=None)

コード例です(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 でした。

(Python) global

もちろん、ローカル変数に入れて使ってもいいと思いますし、logging.getLogger(name=None) で都度取得して使用してもいいと思います。お好みで。

ハンドラ

タイムド ローテーティング ファイルハンドラ

(Python) class logging.handlers.TimedRotatingFileHandler(filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False, atTime=None, errors=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) Path.glob(pattern)

タイム

time(時刻データへのアクセスと変換)

time.sleep(secs)

デートタイム

(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 文字列リテラル

以上です。

スポンサーリンク
シェアする(押すとSNS投稿用の『編集ページ』に移動します)
フォローする(RSSフィードに移動します)
スポンサーリンク
シラベルノート
タイトルとURLをコピーしました