核心方案:使用 TimedRotatingFileHandler
这是 Python 标准库 logging 模块提供的专门用于按时间轮转日志的处理器。

简单示例
这个例子会创建一个日志文件 app.log,并在每天午夜 (when='midnight') 自动轮转,将旧日志重命名为 app.log.2025-10-27 这样的格式。
import logging
from logging.handlers import TimedRotatingFileHandler
import os
# 1. 创建一个 logger 实例
logger = logging.getLogger('my_app')
logger.setLevel(logging.DEBUG) # 设置最低日志级别
# 2. 创建一个 TimedRotatingFileHandler
# - 'app.log': 日志文件名
# - when='D': 表示按天轮转
# - interval=1: 与 'when' 配合,表示每天轮转一次 (当 when='D' 时, interval 通常为1)
# - backupCount=7: 保留7天的日志,超过这个数量的旧日志将被删除
# - encoding='utf-8': 避免编码问题
handler = TimedRotatingFileHandler(
filename='app.log',
when='D',
interval=1,
backupCount=7,
encoding='utf-8'
)
# 3. 设置日志格式
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
# 4. 将 handler 添加到 logger
logger.addHandler(handler)
# --- 测试 ---
if __name__ == '__main__':
logger.debug("这是一条调试信息。")
logger.info("应用程序启动成功。")
logger.warning("这是一个警告。")
logger.error("发生了一个错误!")
logger.critical("这是一个严重错误!")
# 模拟多次运行,观察日志文件的变化
# 第一次运行: app.log
# 第二天运行: app.log 会被重命名为 app.log.2025-10-27,然后创建新的 app.log
更完整的最佳实践示例
在实际项目中,我们通常会将日志配置封装成一个函数,并确保日志目录存在,这个版本更健壮,可以直接复制到你的项目中使用。
import logging
import logging.handlers
import os
from datetime import datetime
def setup_logger(log_dir='logs', log_level=logging.INFO, backup_count=30):
"""
设置一个每天轮转的日志记录器。
:param log_dir: 日志文件存放的目录
:param log_level: 日志级别
:param backup_count: 保留的日志文件数量
:return: 配置好的 logger 实例
"""
# 确保日志目录存在
if not os.path.exists(log_dir):
os.makedirs(log_dir)
# 创建 logger 实例
logger = logging.getLogger('daily_logger')
logger.setLevel(log_level)
# 定义日志格式
formatter = logging.Formatter(
fmt='%(asctime)s - %(name)s - %(levelname)s - %(funcName)s:%(lineno)d - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# 1. 文件处理器 - 按天轮转
# when='D' 表示按天轮转,'midnight' 表示在午夜轮转
file_handler = logging.handlers.TimedRotatingFileHandler(
filename=os.path.join(log_dir, 'app.log'),
when='midnight',
interval=1,
backupCount=backup_count,
encoding='utf-8'
)
file_handler.setFormatter(formatter)
file_handler.suffix = "%Y-%m-%d.log" # 自定义日志轮转后的文件名后缀,默认是 '.rotate'
# 2. 控制台处理器 - 同时在终端输出
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
# 将处理器添加到 logger
logger.addHandler(file_handler)
logger.addHandler(console_handler)
return logger
# --- 使用示例 ---
if __name__ == '__main__':
# 获取配置好的 logger
logger = setup_logger(log_dir='my_app_logs', log_level=logging.DEBUG)
logger.debug("调试信息:程序开始执行。")
logger.info("信息:用户 'Alice' 登录成功。")
try:
result = 1 / 0
except ZeroDivisionError:
logger.error("错误:发生了除以零的异常!", exc_info=True) # exc_info=True 会记录堆栈信息
logger.info("信息:程序执行完毕。")
TimedRotatingFileHandler 关键参数详解
| 参数 | 说明 | 示例 |
|---|---|---|
filename |
日志文件的基准名称。 | 'my_app.log' |
when |
轮转的时间单位,可以是:'S' (秒), 'M' (分), 'H' (小时), 'D' (天), 'midnight' (在午夜轮转), 'W0-W6' (周一至周日)。 |
'D' 或 'midnight' |
interval |
轮转的时间间隔,当 when='D' 时,interval=1 表示每天轮转一次。 |
1 |
backupCount |
保留的日志文件数量,当超过这个数量时,最旧的日志文件将被删除。 | 7 (保留7天日志) |
atTime |
一个 datetime.time 对象,指定轮转的具体时间,只有当 when='H' 或 'D' 时有效。 |
datetime.time(hour=23, minute=59) (在每天23:59轮转) |
suffix |
轮转后日志文件的后缀,默认是 '%Y-%m-%d_%H-%M-%S',可以自定义,'%Y-%m-%d.log'。 |
'%Y-%m-%d.log' |
常见问题与解决方案
日志文件名乱码或编码问题
问题:在某些系统上,日志文件名可能显示为乱码。
解决:在创建 TimedRotatingFileHandler 时,明确指定 encoding='utf-8',这能确保日志内容和文件名都使用 UTF-8 编码。
如何让日志文件名更清晰?
问题:默认的轮转文件名可能很长,如 app.log.2025-10-27_10-00-00。
解决:使用 suffix 参数自定义后缀。

# 创建一个在每天午夜轮转,且文件名后缀为年-月-日.log的处理器
handler = TimedRotatingFileHandler(
filename='app.log',
when='midnight',
backupCount=7,
encoding='utf-8'
)
handler.suffix = "%Y-%m-%d.log"
# 轮转后,app.log 会变成 app.log.2025-10-27.log,然后创建新的 app.log
为什么日志没有按预期轮转?
原因:这通常是因为 logger 的级别设置过高,或者 handler 的级别设置过高。
解决:确保 logger.setLevel() 和 handler.setLevel() 的级别足够低,能够捕获到你想要记录的日志。
# 正确的做法 logger = logging.getLogger() logger.setLevel(logging.DEBUG) # logger级别设为最低 # 然后为不同的handler设置不同的级别 file_handler = TimedRotatingFileHandler(...) file_handler.setLevel(logging.INFO) # 文件只记录INFO及以上级别的日志 console_handler = logging.StreamHandler(...) console_handler.setLevel(logging.DEBUG) # 控制台打印所有级别的日志 logger.addHandler(file_handler) logger.addHandler(console_handler)
如何在多个模块中共享同一个 logger?
问题:在大型项目中,每个模块都调用 setup_logger() 会创建多个重复的 handler,导致日志重复输出。
解决:不要在每个模块中都调用 setup_logger(),应该在程序的入口点(main.py 或 app.py)中调用一次,然后在其他模块中通过 getLogger() 获取同一个实例。
# main.py
import logging
from logging_handlers import setup_logger # 假设 setup_logger 在另一个文件中
# 只在这里配置一次全局 logger
logger = setup_logger()
# app.py
import logging
# 直接获取在 main.py 中配置好的 logger,而不是重新创建
logger = logging.getLogger('daily_logger')
# other_module.py
import logging
logger = logging.getLogger('daily_logger')
对于“每天创建一个新日志文件”的需求,logging.handlers.TimedRotatingFileHandler 是最标准、最有效的解决方案。
推荐的最佳实践步骤:

- 封装配置:将日志配置(创建目录、创建 handler、设置格式、添加 handler)封装成一个函数
setup_logger。 - 单例模式:在程序的入口点(如
main.py)调用一次setup_logger,获取 logger 实例。 - 全局获取:在其他所有需要记录日志的模块中,通过
logging.getLogger('你定义的logger_name')来获取同一个 logger 实例。 - 灵活配置:通过参数控制日志级别、保留天数、日志格式等,使配置更灵活。
