必须重写 dorollover 方法才能使 timedrotatingfilehandler 归档文件保留原始扩展名(如 app.log.2026-09-08.log),因默认实现硬编码拼接日期且不提取 basefilename 的扩展名,suffix 参数仅控制日期格式,rotation_filename 钩子无法安全区分场景。

TimedRotatingFileHandler 默认后缀名不可配,必须重写 doRollover
默认情况下 TimedRotatingFileHandler 生成的归档文件名是 app.log.2026-09-08 这种格式,末尾不带 .log 后缀。如果你希望变成 app.log.2026-09-08.log(即保留原始扩展名),不能靠配置参数实现——suffix 参数只控制日期部分格式(如 %Y-%m-%d),不负责拼接最终扩展名。
真正决定归档文件名的是 doRollover 方法里这行代码:
dfn = self.rotation_filename(self.baseFilename + "." + time.strftime(self.suffix, timeTuple))
它硬编码了只加一个点+日期,没加原始后缀。所以必须继承并重写该方法。
重写 doRollover 时要保留 .log 的关键操作
核心就是把 self.baseFilename 的原始扩展名提取出来,拼到日期后面。别直接硬写 ".log",否则换 app.debug 就失效。
- 用
os.path.splitext(self.baseFilename)拆出文件名和扩展名,取[1]得到.log(含点) - 构造新文件名时:
self.baseFilename + "." + time.strftime(...) + ext - 注意:如果
self.baseFilename本身不含扩展名(如"app"),ext会是空字符串,不会出错 - 务必调用
self.rotation_filename(dfn)包一层,否则自定义命名策略(如加前缀)会失效
多进程下 concurrent-log 的 doRollover 补丁要点
你用的 concurrent-log 是基于标准库的封装,其 ConcurrentTimedRotatingFileHandler 也继承自 TimedRotatingFileHandler。补丁逻辑一致,但要注意两个现实约束:
- 原库的
doRollover已有额外逻辑(比如检查dfn是否已存在、更新before_rollover_at静态变量),你的修改必须嵌入其中,不能覆盖整段逻辑 -
concurrent-log依赖进程锁保证翻转原子性,你在拼接dfn时不能引入新锁或阻塞,否则可能死锁 - 示例中那行修改:
dfn = self.rotation_filename(self.baseFilename + "." + time.strftime(self.suffix, timeTuple) + ".log")是最简方案,但不够健壮——推荐用os.path.splitext动态提取
为什么不用 rotation_filename 钩子?它不生效
有人想绕过重写 doRollover,改用 rotation_filename 方法做后缀追加。这是无效的:
-
rotation_filename接收的是「待重命名的原始路径」,比如"app.log",不是带日期的中间名 - 它在
doRollover内部被调用两次:一次传baseFilename(主文件),一次传拼好的dfn(归档名)。你无法在钩子里区分场景 - 即使强行判断字符串含日期,也容易误伤——比如日志内容里有
"2026-09-08"就崩了
真正可控且安全的入口只有 doRollover 里那行 dfn = ... 的构造逻辑。其他路径都是障眼法。











