質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.31%
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

1回答

936閲覧

pythonのloggerのファイルローテーションについて

SPICA

総合スコア1

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

0クリップ

投稿2023/08/26 11:58

実現したいこと

logをファイルで日付ごとに分けて保存したい。
ファイル名は「app_20230101.log」のようにし、日付が更新されると、「app_20230102.log」を新しく作成して書き込んでいくような感じ。

該当のソースコード

python

1import os 2from pathlib import Path 3from datetime import datetime 4import logging 5from logging import Formatter, handlers, getLogger, Handler 6import inspect 7 8from rich.logging import RichHandler 9 10class Logger: 11 """ 12 ログを管理するためのクラスです。 13 14 Attributes 15 ---------- 16 log_level : str 17 ログレベル ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL')。デフォルトは 'INFO'。 18 save_log_dir : str 19 ログファイルを保存するディレクトリのパス。デフォルトは 'logs/console'。 20 21 Methods 22 ------- 23 debug(msg: str) -> None 24 DEBUG レベルのログを出力します。 25 info(msg: str) -> None 26 INFO レベルのログを出力します。 27 warn(msg: str) -> None 28 WARNING レベルのログを出力します。 29 error(msg: str, exc_info: bool = True) -> None 30 ERROR レベルのログを出力します。exc_info が True の場合、例外情報も表示します。 31 critical(msg: str) -> None 32 CRITICAL レベルのログを出力します。 33 remove_oldlog(max_num_log: int = 100) -> None 34 古いログファイルを削除します。max_num_log で保持するログファイルの最大数を指定します。 35 """ 36 37 def __init__( 38 self, 39 *, 40 log_level: str = "INFO", 41 save_log_dir: str = "logs/console" 42 ): 43 self.log_level = log_level 44 self.log_dir: Path = Path(save_log_dir) 45 self.log_backupcount: int = 3 46 self.log_file_path: Path = self.get_log_file_path() 47 48 stdout_formatter: Formatter = Formatter(fmt="%(message)s") 49 file_formatter: Formatter = Formatter( 50 fmt="%(asctime)s.%(msecs)03d %(levelname)7s %(message)s [%(name)s:%(lineno)d]", 51 datefmt="%Y/%m/%d %H:%M:%S", 52 ) 53 54 stdout_handler: Handler = RichHandler(rich_tracebacks=True) 55 stdout_handler.setFormatter(fmt=stdout_formatter) 56 file_handler: Handler = handlers.TimedRotatingFileHandler( 57 filename=self.log_file_path, 58 when="midnight", 59 encoding="UTF-8", 60 backupCount=self.log_backupcount, 61 ) 62 file_handler.setFormatter(fmt=file_formatter) 63 64 logging.basicConfig( 65 level=self.log_level, 66 handlers=[stdout_handler, file_handler], 67 ) 68 caller_func_name: str = inspect.stack()[1].filename.split("/")[-1] 69 self.logger = getLogger(name=caller_func_name) 70 71 def get_log_file_path(self) -> Path: 72 log_filename = f"app_{datetime.strftime(datetime.now(), '%Y%m%d')}.log" 73 return self.log_dir / log_filename 74 75 def update_log_file(self) -> None: 76 new_log_file_path = self.get_log_file_path() 77 if new_log_file_path != self.log_file_path: 78 self.log_file_path = new_log_file_path 79 self.logger.info(f"ログファイルが更新されました: {self.log_file_path}") 80 for handler in self.logger.handlers: 81 if isinstance(handler, handlers.TimedRotatingFileHandler): 82 handler.baseFilename = new_log_file_path 83 84 def debug(self, msg: str) -> None: 85 self.update_log_file() 86 self.logger.debug(msg, stacklevel=2) 87 88 def info(self, msg: str) -> None: 89 self.update_log_file() 90 self.logger.info(msg, stacklevel=2) 91 92 def warn(self, msg: str) -> None: 93 self.update_log_file() 94 self.logger.warning(msg, stacklevel=2) 95 96 def error(self, msg: str, *, exc_info: bool = True) -> None: 97 self.update_log_file() 98 self.logger.error(msg, exc_info=exc_info, stacklevel=2) 99 100 def critical(self, msg: str) -> None: 101 self.update_log_file() 102 self.logger.critical(msg, stacklevel=2) 103 104 def remove_oldlog(self, *, max_num_log: int = 100) -> None: 105 logs = list(self.log_dir.glob("*.log")) 106 if len(logs) > max_num_log: 107 log_name_pairs = [ 108 (log, datetime.strptime(log.stem[-8:], "%Y%m%d")) for log in logs 109 ] 110 log_name_pairs = sorted(log_name_pairs, key=lambda s: s[1]) 111 remove_log_path = log_name_pairs[0][0] 112 os.remove(remove_log_path) 113 self.info(f"古いログを削除しました: {remove_log_path}") 114 for i in range(1, self.log_backupcount + 1): 115 remove_rotating_log_path = f"{remove_log_path}.{i}" 116 if os.path.exists(remove_rotating_log_path): 117 os.remove(remove_rotating_log_path) 118 self.info(f"古いログを削除しました: {remove_rotating_log_path}")

試したこと

上記のまま実行すると、ファイルは分かれずに同じファイルのまま書き込まれていきます。

ひとこと

かなりいろいろ試しましたが、なかなか希望通りに動いてくれなくて困っています。よろしくお願いいたします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

まず、最新のログファイルのファイル名ですが、これはハンドラを設定したときに1つだけ決めることができる仕様なので、新しいログファイルができるたびに日付を付けることはできません。通常は「app.log」などのような名前になります。
ハンドラそのものを自分で作れば、そのような動作をするようにすることはできます。pythonのソースを参考にすば、それほど難しくはないと思います。

ローテートした後のバックアップファイルの名前は変更することができます。
ログハンドラには、namerという属性があって、そこにローテートするときに名前を更新するための関数を設定することができます。

https://docs.python.org/ja/3/howto/logging-cookbook.html#using-a-rotator-and-namer-to-customize-log-rotation-processing

この例のnamerは

python

1def namer(name): 2 return name + ".gz"

ファイルを圧縮するようにしているので、末尾に「.gz」を付けるようになっています。
namerは、ベースの名前文字列(app.logとか)が渡ってきますので、その文字列を加工することになります。

こうすることで、 現在のログは「app.log」で、バックアップファイルは 「app_20230102.log」のようにすることができます。


以下のようにすることで、ローテートしたときに関数を呼んでくれるようになると期待しているのだと思いますが、関数がそのような仕様ではないので、期待通りにはなりません。

python

1 file_handler: Handler = handlers.TimedRotatingFileHandler( 2 filename=self.log_file_path,

投稿2023/08/26 15:16

編集2023/08/27 10:43
TakaiY

総合スコア14286

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

SPICA

2023/08/27 06:56

バックアップファイルだけですが希望のファイル名に変更できました。ありがとうございました!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.31%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問