Python日志记录-启动时移动文件



在我的Flask应用程序中,我使用logging库实现了一个日志记录系统。它目前在以下功能中运行:


if __name__ == "__main__":
"""[Runs the webserver.
Finally block is used for some logging management. It will first shut down
logging, to ensure no files are open, then renames the file to 'log_'
+ the current date, and finally moves the file to the /logs archive
directory]
"""
try:
session_management.clean_uploads_on_start(UPLOAD_FOLDER)
app.run(debug=False)
finally:
try:
logging.shutdown()
new_log_file_name = log_management.rename_log(app.config['DEFAULT_LOG_NAME'])
log_management.move_log(new_log_file_name)
except FileNotFoundError:
logging.warning("Current log file not found")
except PermissionError:
logging.warning("Permissions lacking to rename or move log.")

我发现,如果(或者(强制关闭cmd提示符,或者服务器崩溃,则文件不会被重命名和移动。我认为在服务器启动之前,最好将重命名并移动到函数的初始"try"块中,但我遇到了问题,因为我有一个配置文件(在此脚本中导入(,其中包含以下代码:

logging.basicConfig(filename='current_log.log', level=logging.INFO,
filemode='a',
format='%(asctime)s:%(levelname)s:%(message)s')

我试过做下面这样的事情,但我仍然遇到权限错误,但我认为我仍然遇到错误,因为log_management脚本还导入了config。此外,我找不到一个类似于logging.shutdown((的函数来启动日志系统,该函数在系统结束时使用,否则我会关闭它,移动文件(如果存在(并重新启动它。


try:
session_management.clean_uploads_on_start(UPLOAD_FOLDER)
log_management.check_log_on_startup(app.config['DEFAULT_LOG_NAME'])
import config
app.run(debug=False)
finally:
try:
logging.shutdown()
new_log_file_name = log_management.rename_log(app.config['DEFAULT_LOG_NAME'])
log_management.move_log(new_log_file_name)
except FileNotFoundError:
logging.warning("Current log file not found")
except PermissionError:
logging.warning("Permissions lacking to rename or move log.")
# (in another script)
def check_log_on_startup(file_name):
if os.path.exists(file_name):
move_log(rename_log(file_name))

任何建议都很受欢迎,因为我觉得自己就像在碰壁!

正如您已经发现的,如果流程不明确地终止,尝试在流程生命周期结束时执行清理可能会失败。

在开始时执行清理的问题是,在尝试移动旧日志文件之前,您显然从导入中调用了logging.basicConfig
当您尝试重命名和移动现有日志时,这会导致隐式创建的FileHandler在现有日志上保留一个打开的文件对象。根据您使用的文件系统,这可能不会带来什么好处。

如果您想将潜在旧日志文件的处理完全移到应用程序的开头,则必须在调用logging.basicConfig之前执行重命名和移动,因此必须将其从导入中删除,并以某种方式将其添加到log_management中。

作为一种替代方案,您可以通过子类化标准FileHandler类,将日志文件的整个处理转移到日志文件处理程序,例如:

import logging
import os
from datetime import datetime
class CustomFileHandler(logging.FileHandler):
def __init__(self, filename, archive_path='archive', archive_name='log_%Y%m%d', **kwargs):
self._archive = os.path.join(archive_path, archive_name)
self._archive_log(filename)
super().__init__(filename, **kwargs)
def _archive_log(self, filepath):
if os.path.exists(filepath):
os.rename(filepath, datetime.now().strftime(self._archive))
def close(self):
super().close()
self._archive_log(self.baseFilename)

有了这个,你可以这样配置你的日志:

hdler = CustomFileHandler('current.log')
logging.basicConfig(level=logging.INFO, handlers=[hdler], 
format='%(asctime)s:%(levelname)s:%(message)s')

CustomFileHandler将在初始化期间检查并可能存档旧日志。这将处理不干净进程终止后无法进行关闭清理的遗留问题。由于父类初始值设定项是在尝试日志归档后调用的,因此日志上还没有打开的句柄会导致PermissionError

重写的close()方法将在干净进程关闭时执行存档。

这应该消除了对专用log_management模块的需要,至少就代码中显示的函数而言是这样。rename_logmove_logcheck_log_on_startup都封装在CustomFileHandler中。也不需要显式地调用CCD_ 15。


一些注意事项:

找不到与logging.shutdown()等效的启动函数的原因是,在导入logging模块时,日志记录系统已启动/初始化。除其他外,它实例化了隐式根记录器,并通过atexit将logging.shutdown注册为退出处理程序
后者是不需要用上述解决方案显式调用logging.shutdown()的原因。Python解释器将在完成过程中调用它,为退出处理程序注册导致的解释器关闭做准备。然后,logging.shutdown()遍历已注册处理程序的列表,并调用它们的close()方法,这些方法将在干净关闭期间执行日志归档。

根据您选择的移动(和重命名(旧日志文件的方法,上述解决方案可能需要一些针对异常的额外保护措施。如果目标路径已经存在,则os.rename将引发异常,即当您之前在同一天停止并启动了进程时,而os.replace将静默地覆盖现有文件。请在此处查看有关通过Python移动文件的更多详细信息。

因此,我建议不仅按当前日期,而且按时间命名归档日志
在上文中,通过datetimestrftime将当前日期添加到存档文件名中,因此"log_%Y%m%d"是自定义文件处理程序的archive_name参数的默认值。前面有%的字符是有效的格式代码,strftime()会将其替换为被调用的datetime对象的相应部分。要将当前时间附加到存档日志文件名,只需将相应的格式代码附加到archive_name,例如:"log_%Y%m%d_%H%m%S",这将产生一个如log_20200819_123721的日志名。