Synopsis: 如果你想使用 Python 的内置模块 logging 中的 SMTPHandler 将出错时的日志,通过邮件的方式发送给管理员的话,可能你会遇到很多坑,本文将解决诸如 socket.timeout: timed out 和 smtplib.SMTPServerDisconnected: Connection unexpectedly closed: timed out 等错误,亲测有效
创建 test_smtphandler.py
import logging
from logging.handlers import SMTPHandler
import sys
def main():
# 1. 创建 logger 实例
logger = logging.getLogger('test-smtphandler')
# 2. 设置 logger 实例的日志级别,默认是 logging.WARNING
logger.setLevel(logging.INFO)
# 3. 创建 Handler
# 注意 163 邮箱要求 fromaddr 和你发送邮件的邮箱(即你的邮箱账号)要一致
mail_handler = SMTPHandler(
mailhost=('smtp.qq.com', 25),
fromaddr='xxx@qq.com',
toaddrs='接收报警邮件的地址',
subject='服务器出现问题啦!!!',
credentials=('xxx@qq.com', '客户端授权密码'))
# 4. 单独设置 mail_handler 的日志级别为 ERROR
mail_handler.setLevel(logging.ERROR)
# 5. 将 Handler 添加到 logger 中
logger.addHandler(mail_handler)
# 6. 应用的业务代码(故意出错)
try:
x = 1 / 0
except Exception:
logger.error('[计算出错了] x = 1 / 0', exc_info=sys.exc_info())
if __name__ == '__main__':
main()
运行脚本后,邮箱未收到信息,但是在垃圾箱中有新的出现。
邮箱的 SMTP 服务器地址为 smtp.qq.com,其中 非 SSL 协议端口号 为 25, SSL 协议端口号 为 465/587,此api不支持协议端口,于是对其进行了修改:
import logging
import sys
from logging.handlers import SMTPHandler
import time
class CompatibleSMTPSSLHandler(SMTPHandler):
"""
官方的SMTPHandler不支持SMTP_SSL的邮箱,这个可以两个都支持,并且支持邮件发送频率限制
"""
def __init__(self, mailhost, fromaddr, toaddrs: tuple, subject,
credentials=None, secure=None, timeout=5.0, is_use_ssl=True, mail_time_interval=0):
"""
:param mailhost:
:param fromaddr:
:param toaddrs:
:param subject:
:param credentials:
:param secure:
:param timeout:
:param is_use_ssl:
:param mail_time_interval: 发邮件的时间间隔,可以控制日志邮件的发送频率,为0不进行频率限制控制,如果为60,代表1分钟内最多发送一次邮件
"""
super().__init__(mailhost, fromaddr, toaddrs, subject,
credentials, secure, timeout)
self._is_use_ssl = is_use_ssl
self._time_interval = mail_time_interval
self._msg_map = dict() # 是一个内容为键时间为值得映射
def emit(self, record: logging.LogRecord):
"""
Emit a record.
Format the record and send it to the specified addressees.
"""
from threading import Thread
if sys.getsizeof(self._msg_map) > 10 * 1000 * 1000:
self._msg_map.clear()
Thread(target=self.__emit, args=(record,)).start()
def __emit(self, record):
if record.msg not in self._msg_map or time.time() - self._msg_map[record.msg] > self._time_interval:
try:
import smtplib
from email.message import EmailMessage
import email.utils
t_start = time.time()
port = self.mailport
if not port:
port = smtplib.SMTP_PORT
smtp = smtplib.SMTP_SSL(self.mailhost, port,
timeout=self.timeout) if self._is_use_ssl else smtplib.SMTP(self.mailhost, port,
timeout=self.timeout)
msg = EmailMessage()
msg['From'] = self.fromaddr
msg['To'] = ','.join(self.toaddrs)
msg['Subject'] = self.getSubject(record)
msg['Date'] = email.utils.localtime()
msg.set_content(self.format(record))
if self.username:
if self.secure is not None:
smtp.ehlo()
smtp.starttls(*self.secure)
smtp.ehlo()
smtp.login(self.username, self.password)
smtp.send_message(msg)
smtp.quit()
print('{}发送邮件给 {} 成功,用时{} ,发送的内容是--> {}\033[0;35m!!!请去邮箱检查,可能在垃圾邮件中\033[0m'.format(self.fromaddr,
self.toaddrs, round(
time.time() - t_start, 2), record.msg))
self._msg_map[record.msg] = time.time()
except Exception:
self.handleError(record)
else:
pass
print('邮件发送太频繁,此次不发送这个邮件内容:{}'.format(record.msg))
- 增加一个频率控制的参数,比如要设置一个报警邮件,异常时候通知我们,但假设1分钟内异常几千次,那是不需要发几千次相同日志的,handler自带频率限制,使用的时候一秒钟调用运行logger.waning(‘某某报警’)几万次都没问题,直接自动忽略相同内容的报警信息,不会发送邮件。
- 发邮件时候另开线程,发邮件是需要一段时间的,运气不好时候发邮件需要两三秒,把主线程阻塞两三秒了,另开线程不会有阻塞
将原来SMTPHandler替换为CompatibleSMTPSSLHandler即可,
这样就可以正常接受邮箱信息了
来源:CSDN
作者:懒惰的小肥肥
链接:https://blog.csdn.net/hua1017177499/article/details/103462701