生产环境必须用RotatingFileHandler替代basicConfig,因其支持路径预建、delay延迟打开、UTF-8编码容错、10MB大小轮转与5份备份,并需重置sys.excepthook和threading.excepthook捕获全链路异常。

生产环境报错日志必须能定位到具体模块、行号、时间,并且不能因日志写入失败导致主流程崩溃——logging 模块本身默认不满足这点,得手动加固。
为什么直接用 basicConfig 在生产环境会出问题
它只配置一次,且默认输出到 stderr;容器或 systemd 环境下 stderr 可能被截断、无轮转、无权限写入磁盘,更严重的是:一旦日志文件磁盘满或被误删,FileHandler 抛异常会直接冒泡到业务逻辑里。
-
basicConfig无法捕获未处理异常(比如主线程崩了),需配合sys.excepthook - 默认不记录
pathname、lineno、funcName,排查时只能看到 “ERROR: root: xxx”,没法定位 - 没设置
encoding='utf-8',中文日志在某些 Linux 发行版上会UnicodeEncodeError
如何安全地初始化一个生产级 RotatingFileHandler
核心是:容忍路径不可写、磁盘满、编码失败,且确保日志线程安全、不阻塞主逻辑。
- 用
os.makedirs(log_dir, exist_ok=True)提前建目录,避免FileHandler初始化时报FileNotFoundError - 给
RotatingFileHandler加delay=True,延迟到第一次emit()才打开文件,避开启动时权限检查 - 设置
encoding='utf-8'和errors='backslashreplace',防止中文或二进制数据崩掉 handler - 加
maxBytes=10*1024*1024(10MB)和backupCount=5,避免单文件无限增长
import logging import os from logging.handlers import RotatingFileHandler <p>log_dir = "/var/log/myapp" os.makedirs(log_dir, exist_ok=True)</p><p>handler = RotatingFileHandler( filename=os.path.join(log_dir, "error.log"), maxBytes=10<em>1024</em>1024, backupCount=5, encoding="utf-8", errors="backslashreplace", delay=True, ) handler.setLevel(logging.ERROR) handler.setFormatter(logging.Formatter( "%(asctime)s %(name)s %(levelname)s %(pathname)s:%(lineno)d %(funcName)s - %(message)s" )) logging.getLogger().addHandler(handler) </p>
怎样捕获所有未处理异常(包括线程和主线程崩溃)
logging 默认只管你主动 logger.error() 的地方,但生产环境最要命的是那些没 try/except 的意外崩溃——比如某个后台线程抛了 KeyError,主线程照常跑,但功能已残。
- 重置
sys.excepthook:覆盖默认的崩溃打印,改用logging.critical记录,带上traceback.format_exc() - 对
threading.Thread设置threading.excepthook(Python 3.8+),否则子线程异常直接静默丢失 - 注意:不要在 hook 里做耗时操作(如发 HTTP 请求),避免卡死进程
import sys
import threading
import traceback
import logging
<p>def handle_exception(exc_type, exc_value, exc_traceback):
if issubclass(exc_type, KeyboardInterrupt):
sys.<strong>excepthook</strong>(exc_type, exc_value, exc_traceback)
return
logging.critical("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback))</p><p>sys.excepthook = handle_exception</p><p>if hasattr(threading, "excepthook"):
threading.excepthook = lambda args: logging.critical(
"Uncaught thread exception", exc_info=args.exc_value
)
</p>
为什么 getLogger(__name__) 比 getLogger() 更关键
全局 root logger 被多个模块共享,一旦某处调用 basicConfig 或 setLevel,就可能干扰其他模块的日志级别——比如 A 模块设成 WARNING,B 模块的 INFO 就全丢了。
- 每个模块开头写
logger = logging.getLogger(__name__),天然隔离命名空间 - 在入口处统一给
logging.getLogger().setLevel(logging.WARNING),再单独对关键模块(如"myapp.db")调高到DEBUG - 避免用
logger.propagate = False,除非你明确要切断向 root 传递——多数情况该让 error 向上透传到文件 handler
真正难的不是写几行 RotatingFileHandler,而是想清楚:日志路径是否跨容器持久化、backupCount 是否匹配运维 logrotate 策略、errors='backslashreplace' 是否掩盖了真正的编码问题。这些细节不验证,上线后第一波流量就把日志系统拖垮。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











