SMTPHandler发不出邮件的主因是日志级别不匹配、异常未触发handler、propagation未配置或认证/网络问题;需确保logger与handler同级ERROR以上、显式调用exception/error并传exc_info=True、正确配置mailhost端口及TLS/SSL、使用应用专用密码而非账户密码,并添加重试与本地fallback机制。

为什么 SMTPHandler 发不出邮件,但手动发 SMTP 却成功?
根本原因通常是日志级别没触发、异常没被捕获进 handler,或者 SMTPHandler 默认不处理 root logger 的 propagation。它只转发自己绑定的 logger 发出的日志,且默认只响应 ERROR 及以上——而很多异常是 WARNING 或未显式 logger.error() 调用的。
- 确保目标 logger(比如
logger = logging.getLogger("myapp"))设置了.setLevel(logging.ERROR),且SMTPHandler也设了同级或更低的level - 调用
logger.exception("出错了")或logger.error("xxx", exc_info=True),否则 traceback 不会进邮件正文 - 别依赖
logging.basicConfig():它配置的是rootlogger,而SMTPHandler通常挂载在自定义 logger 上,root的日志不会自动传过去 - 检查
SMTPHandler的mailhost参数是否含端口(如("smtp.qq.com", 587)),QQ/163 邮箱必须用 TLS 端口,填465却没开 SSL 会静默失败
怎么让邮件里带完整 traceback 和上下文变量?
SMTPHandler 默认只发日志消息和基础格式化信息,不包含局部变量、函数名、行号以外的上下文。要加 traceback 得靠 exc_info=True,但变量快照得自己塞进去。
- 重写
format()方法,在LogRecord中注入额外字段:record.context = {"user_id": user_id, "request_id": req_id} - 在 formatter 的
%(message)s后追加自定义模板,比如%(context)s,再用logging.Formatter的format()扩展逻辑解析它 - 更稳妥的做法是:捕获异常后构造结构化字典,用
json.dumps()转成字符串再logger.error(),避免格式器解析失败 - 注意不要在 format 过程中做耗时操作(如查数据库),handler 是同步阻塞的,会拖慢主流程
用 Gmail/Outlook/QQ 邮箱发日志邮件的常见认证失败点
不是密码不对,而是没开「开启 SMTP 服务」或用了错误的认证方式。Gmail 已全面停用“用户名+密码”直连,必须用 App Password;QQ 邮箱需在账户页生成独立密码,且必须勾选「POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV 服务」。
- Gmail:启用 2-Step Verification 后,在 App passwords 生成 16 位密码,
mailhost填("smtp.gmail.com", 587),credentials传(邮箱, app密码) - QQ 邮箱:登录邮箱网页版 → 设置 → 账户 → 「POP3/IMAP…」→ 开启 SMTP 服务 → 生成授权码,
credentials用这个码,不是登录密码 - 所有邮箱都必须显式调用
handler.setFormatter(formatter),否则默认格式无时间、无级别,收件方难以判断严重性 - 别在生产环境硬编码密码:从环境变量读取,比如
os.getenv("SMTP_PASSWORD"),避免泄露到代码或日志中
为什么本地测试能发,部署到服务器就超时或被拒?
多数情况是云服务器(阿里云、腾讯云)默认封禁 25/465/587 等外发端口,或 ISP 屏蔽了 SMTP 流量。另一个常被忽略的点是:容器或 systemd 服务没加载环境变量,导致 SMTP_PASSWORD 为空。
立即学习“Python免费学习笔记(深入)”;
- 先 telnet 测试连通性:
telnet smtp.qq.com 587,不通就说明网络层被拦,得提工单申请解封 - 改用企业邮箱 API(如 SendGrid、Mailgun)替代 SMTP:它们走 HTTPS,端口开放,还自带重试和投递状态回调
- 如果坚持用 SMTP,把
SMTPHandler包一层带重试的 wrapper,避免单次失败丢日志;同时加 fallback:失败时写入本地文件/var/log/myapp/error.log - 检查
syslog或journalctl -u your-service,看 handler 初始化时有没有ConnectionRefusedError或TimeoutError报错,这是最直接的线索
真正难的不是配通 SMTP,而是确保异常发生时,那条日志一定能离开进程——网络抖动、磁盘满、DNS 失败,都可能让 handler 沉默。所以别只信一个通道。


















