自定义CSVTimedRotatingFileHandler无法触发日志滚动问题求助
自定义CSVTimedRotatingFileHandler无法触发日志滚动问题求助
我们的Python (v3.11.7) + Django (v3.2.23) 应用有个需求:把特定安全相关事件记录到CSV文件,要求每小时自动滚动日志,生成的文件名格式类似audit_logs20250130_0800-0900.csv。
后端跑在Docker容器里,启动命令如下:
gunicorn wsgi:application --bind 0.0.0.0:8000 --workers 4 --threads 4
为了实现这个需求,我们继承logging.handlers.TimedRotatingFileHandler自定义了CSVTimedRotatingFileHandler,代码如下:
import logging import os from datetime import datetime, timedelta from logging.handlers import TimedRotatingFileHandler import pytz import redis from django.conf import settings REDIS_KEY = 'CSVTimedRotatingFileHandler_RolloverAt' class CSVTimedRotatingFileHandler(TimedRotatingFileHandler): def __init__(self, filename, when, interval, backup_count, encoding=None, delay=False, headers=None): super().__init__(filename, when=when, interval=interval, backupCount=backup_count, encoding=encoding, delay=delay, utc=False, atTime=None, errors=None) self.headers = headers def emit(self, record): try: last_rollover_at = self.get_redis_rollover_at_value() # Check if a rollover happened and refresh the stream if needed (for multiple workers) if self.rolloverAt != last_rollover_at: self.rolloverAt = last_rollover_at if self.stream and not self.stream.closed: self.stream.close() self.stream = self._open() if self.shouldRollover(record): self.doRollover() # If the stream is still closed or None, open it again if self.stream is None or self.stream.closed: self.stream = self._open() # Write headers if the file is empty if self.stream.tell() == 0 and self.headers: self.stream.write(','.join(self.headers) + self.terminator) self.flush() logging.FileHandler.emit(self, record) except Exception: self.handleError(record) @staticmethod def get_redis_rollover_at_value(): r = redis.StrictRedis(host=settings.REDIS_HOST, port=settings.REDIS_PORT, db=0) redis_rollover_value = r.get(REDIS_KEY) if redis_rollover_value: formatted_redis_rollover_value = int(redis_rollover_value.decode('utf-8')) else: formatted_redis_rollover_value = 0 return formatted_redis_rollover_value @staticmethod def set_redis_rollover_at_value(value): r = redis.StrictRedis(host=settings.REDIS_HOST, port=settings.REDIS_PORT, db=0) r.set(REDIS_KEY, value) def computeRollover(self, currentTime): redis_rollover_value = self.get_redis_rollover_at_value() if currentTime > redis_rollover_value: utc_timezone = pytz.utc date = datetime.fromtimestamp(currentTime, utc_timezone) updated_date = date.replace(minute=0, second=0, microsecond=0) + timedelta(seconds=self.interval) updated_timestamp = int(updated_date.timestamp()) self.set_redis_rollover_at_value(updated_timestamp) return updated_timestamp return redis_rollover_value def rotation_filename(self, filename): file = os.path.basename(self.baseFilename) directory_path = os.path.dirname(self.baseFilename) name = file.split('.')[0] extension = file.split('.')[1] dot = '.' separator = '_' rollover_date = datetime.fromtimestamp(self.rolloverAt, tz=pytz.timezone('UTC')) date = rollover_date.strftime('%Y%m%d') from_hour = (rollover_date - timedelta(hours=1)).hour from_min = (rollover_date - timedelta(hours=1)).minute to_hour = rollover_date.hour to_minute = rollover_date.minute hours_delta = f'{from_hour:02}{from_min:02}-{to_hour:02}{to_minute:02}' return os.path.join(directory_path, f'{name}{date}{separator}{hours_delta}{dot}{extension}') def getFilesToDelete(self): result = [] directory_path = os.path.dirname(self.baseFilename) files = [os.path.join(directory_path, f) for f in os.listdir(directory_path) if os.path.isfile(os.path.join(directory_path, f)) and f != self.baseFilename] if os.path.join(directory_path, self.baseFilename) in files: files.remove(os.path.join(directory_path, self.baseFilename)) files.sort(key=os.path.getctime) if len(files) > self.backupCount: result = files[:len(files) - self.backupCount] return result def flush(self): if self.stream and hasattr(self.stream, "flush"): self.stream.flush()
现在遇到的问题是:日志事件能正常写入CSV文件,但完全不会触发日志滚动——所有日志一直写到基础文件audit_logs.csv里,永远不会生成按小时命名的滚动文件。
我们做了手动测试:进入容器的python manage.py shell后,
- 调用
logging.handlers.TimedRotatingFileHandler.shouldRollover()能返回正确结果 - 手动执行
logging.handlers.TimedRotatingFileHandler.doRollover()也能正常完成滚动操作
有没有朋友能帮忙分析下,为什么实际运行时(多worker场景下)滚动逻辑不生效?
备注:内容来源于stack exchange,提问作者Kyriakos
相关产品推荐
相关产品推荐

