Python 多进程日志轮转:ConcurrentLogHandler 实战与常见坑点解析
本文详细介绍 Python 中实现多进程安全日志轮转的解决方案。对比内置 handler 的局限性,重点讲解 ConcurrentLogHandler 的安装与配置方法,包括代码实现和 ini 配置文件方式。同时针对 Django 开发模式下常见的日志文件占用导致 Permission denied 错误,提供了具体的排查思路与通过 --noreload 参数运行的解决建议。
在程序开发中,记录日志是不可或缺的功能。然而,若缺乏合理的日志管理机制,随着时间推移,日志文件体积可能膨胀至数百兆,导致分析困难甚至无法打开。频繁手动清理或管理这些文件既不现实也不高效。
日志轮转机制旨在根据预设的时间周期或文件大小自动限制日志文件的数量,从而免除手动管理的负担。
Python 标准库中的 logging 模块内置了多种支持日志轮转的 Handler。
其中较为常用的是 TimedRotatingFileHandler,它依据时间进行轮转;以及 RotatingFileHandler,它依据文件大小进行轮转。
需要注意的是,上述内置 Handler 仅具备多线程安全性,并不原生支持多进程环境。若需在多进程场景下使用,可能需要修改源码并添加锁机制来保证进程安全。
针对多进程场景,推荐使用 ConcurrentLogHandler(需自行安装)。该模块支持基于文件大小的日志轮转,能够有效处理多进程并发写入日志的情况。
pip install ConcurrentLogHandler
一、简单的日志轮转功能实现:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import logging
from cloghandler import ConcurrentRotatingFileHandler
from config import LOG_PATH, LOG_FILENAME, LOG_MAX_BYTES
def create_logger(log_path=os.getcwd(), # 存放日志的目录
level=logging.DEBUG,
formatter=logging.BASIC_FORMAT, # 日志输出格式
logger_name="", # 可以使用logging.getlogger(logger_name)使用此logger
mode='a',
delay=0,
debug=True,
log_filename=LOG_FILENAME, # 保存日志的文件名(备份出的文件会以此名+.1、 .2命名)
encoding=None,
maxBytes=LOG_MAX_BYTES, # 每个日志文件的最大容量
backupCount=3 # 最多备份几个日志文件):
# 判断存放日志的文件夹是否存在 如果不存在新建
if not os.path.exists(log_path):
os.mkdir(log_path)
# 存放log的文件名
log_filename = os.path.join(log_path, log_filename)
# 创建一个logger
logger = logging.getLogger(logger_name)
# 设置日志等级
logger.setLevel(level)
# 创建一个滚动日志处理器
crfh = ConcurrentRotatingFileHandler(log_filename, mode=mode, maxBytes=maxBytes, backupCount=backupCount, delay=delay, debug=debug, encoding=encoding)
# 定义handler的输出格式
# 设定日志输出格式
crfh.setFormatter(formatter)
# 添加日志处理器
logger.addHandler(crfh)
# 返回logger对象
return logger
# 日志格式
formatter_log = logging.Formatter('%(asctime)s - %(filename)s [line: %(lineno)d] 【%(levelname)s】 ----- %(message)s')
# 生成一个logger
logger = create_logger(log_path=LOG_PATH, logger_name="mylogger", formatter=formatter_log)
通过上述配置,即可在其他模块中导入 logger 对象进行日志记录。
logger.error("error msg")
有时我们需要将特定数据写入文件,若多个程序同时向同一文件写入数据,可能导致内容混乱。此时,也可利用此模块替代传统的 f.write() 方法来实现安全写入。
from config import CHANNEL_PATH, CHANNEL_FILENAME, LOG_MAX_BYTES
# 只需要把日志的格式改为只存入信息就可以了
formatter_writer = logging.Formatter('%(message)s')
# 创建一个写入器(logger)
writer = create_logger(logger_name="writer",log_path=CHANNEL_PATH, log_filename=CHANNEL_FILENAME, formatter=formatter_writer, level=logging.INFO)
配置完成后,即可通过 writer.info("msg") 等方式记录数据。
二、使用 ini 配置文件
首先创建配置文件 xxx.ini。
[loggers]
keys = root,public
[handlers]
keys = consoleHandler,publicFileHandler
[formatters]
keys = my_formatter
[logger_root]
level = DEBUG
handlers = consoleHandler
[logger_public]
handlers = publicFileHandler
qualname = public
propagate = 0
[handler_consoleHandler]
class = StreamHandler
level = DEBUG
formatter = my_formatter
args = (sys.stdout,)
[handler_publicFileHandler]
class = cloghandler.ConcurrentRotatingFileHandler
level = INFO
formatter = my_formatter
kwargs = {"filename": "./logs/public.log", "maxBytes": 1024 * 1024 * 10, "backupCount": 10, "delay": True, "debug": True}
[formatter_my_formatter]
format = %(asctime)s - %(filename)s [line: %(lineno)d] [%(levelname)s] ----- %(message)s
datefmt = %Y-%m-%d %H:%M:%S
随后创建 mylogger.py 脚本以加载配置。
import logging
import logging.config
logging.config.fileConfig("./xxx.ini")
logger = logging.getLogger("public")
logger.info("hello world!")
补充:关于 Python 日志轮转中 RotatingFileHandler 动态加载导致无法记录日志的问题。
在 Linux 环境下:
Traceback (most recent call last): File “/usr/lib64/python2.7/logging/handlers.py”, line 77, in emit self.doRollover() File “/usr/lib64/python2.7/logging/handlers.py”, line 136, in doRollover os.rename(sfn, dfn) OSError: [Errno 13] Permission denied Logged from file utils.py, line 89
在 Windows 环境下:
Traceback (most recent call last): File “F:\Python27\lib\logging\handlers.py”, line 77, in emit self.doRollover() File “F:\Python27\lib\logging\handlers.py”, line 142, in doRollover os.rename(self.baseFilename, dfn) WindowsError: [Error 32] Logged from file utils.py, line 89
该问题通常由 Django 开发模式引起。在开发模式下,系统会同时启动两个进程加载 settings.py 文件,导致日志文件被占用,进而无法执行重命名或删除操作。
众所周知,Django 在开发模式下若检测到文件变动会自动重启服务。因此,会同时存在一个正常运行的进程和一个用于监听变更并重启服务的进程,两者均会加载 settings.py。在 settings.py 中添加 print 语句启动服务时,控制台会出现两次输出,印证了双进程的存在。
解决方式:
python C:\Users\pc\Desktop\yunserver-1.1\manage.py runserver --noreload 0.0.0.0:8000
使用不启用动态加载的方式运行服务(这可能会影响开发环境中对代码变更的实时响应)。
以上为个人经验分享,希望能为大家提供参考。也希望大家多多支持本站。如有错误或未考虑周全之处,欢迎批评指正。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









