工作中需要及时解决线上的 bug,所以,及时获取 log 中的 warning,error 是非常有必要的,在查找资料的过程中发现了logging.handlers.SMTPHandler可以实现日志发送到邮件,通过简单的配置就可以使用,不必自己再烦心重写。
1、SMTPHandler默认情况下不支持SMTPS
import logging
import logging.handlers
def get_logger(logger_name, logger_level, logger_location, days):
logger = logging.getLogger(logger_name)
logger.setLevel(logger_level)
log_format = "%(name)s\t%(asctime)s\t%(pathname)s\t[line:%(lineno)d]\t %(levelname)s\t %(message)s"
formater = logging.Formatter(log_format)
# 存入文件的日志
handler = logging.handlers.TimedRotatingFileHandler(logger_location, "midnight", 1, days, encoding="utf-8")
handler.suffix = "%Y%m%d"
handler.setFormatter(formater)
logger.addHandler(handler)
# 发邮件的日志
sh = logging.handlers.SMTPHandler("smtp.163.com", send_email, [receive_email_1, receive_email_2],
"log error", credentials=(send_email, authority_password),secure=())
sh.setLevel(logging.ERROR)
sh.setFormatter(formater)
logger.addHandler(sh)
return logger
注意:邮箱的密码不是登录密码,而是授权码。首先需要打开邮箱的客户端授权,然后设置授权码。
2、SMTPHandler支持SMTPS的方法:
def emit(self, record):
"""
Overwrite the logging.handlers.SMTPHandler.emit function with SMTP_SSL.
Emit a record.
Format the record and send it to the specified addressees.
"""
try:
import smtplib
from email.utils import formatdate
port = self.mailport
if not port:
port = smtplib.SMTP_PORT
smtp = smtplib.SMTP_SSL(self.mailhost, port, timeout=self._timeout)
msg = self.format(record)
msg = "From: %s\r\nTo: %s\r\nSubject: %s\r\nDate: %s\r\n\r\n%s" % (self.fromaddr, ", ".join(self.toaddrs), self.getSubject(record), formatdate(), msg)
if self.username:
smtp.ehlo()
smtp.login(self.username, self.password)
smtp.sendmail(self.fromaddr, self.toaddrs, msg)
smtp.quit()
except (KeyboardInterrupt, SystemExit):
raise
except:
self.handleError(record)
然后设置
logging.handlers.SMTPHandler.emit = emit
3、防止邮箱爆炸
每一条错误日志都会对应一封邮件的发送,如果多条错误,邮箱且不是要爆掉。logging.handlers.MemoryHandler是将日志输出的内存中定制的buffer,一旦buffer存满或是超过设定的级别就会将日志记录发送给指定的target handler处理。
def flush(self):
"""
For a MemoryHandler, flushing means just sending the buffered
records to the target, if there is one. Override if you want
different behaviour.
"""
if self.target:
for record in self.buffer:
self.target.handle(record)
self.buffer = []
从代码中可以看出,虽然我们暂时缓存了多条日志数据,但是当缓存溢出时,对应buffer中的每一条记录,都对应一个动作,即,如果我们设置target为SMTPHandler,还是会发送多个邮件。So, override if you want different behaviour。
class OptmizedMemoryHandler(logging.handlers.MemoryHandler):
def __init__(self, capacity, mail_subject, mail_host, mail_from, mail_to):
""" capacity: flush memory
mail_subject: warning mail subject
mail_host: the email host used
mail_from: address send from; str
mail_to: address send to; multi-addresses splitted by ';'
"""
logging.handlers.MemoryHandler.__init__(self, capacity, flushLevel = logging.ERROR,\
target = None)
self.mail_subject = mail_subject
self.mail_host = mail_host
self.mail_from = mail_from
self.mail_to = mail_to
def flush(self):
"""if flushed send mail
"""
if self.buffer != [] and len(self.buffer) >= self.capacity:
content = ''
for record in self.buffer:
message = record.getMessage()
content += record.levelname + " occurred at " + time.strftime('%Y-%m-%d %H:%M:%S',time.localti me(record.created)) + " : " + message + '\n'
self.send_warning_mail(self.mail_subject, content,self.mail_host, self.mail_from, self.mail_to)
self.buffer = []
def send_warning_mail(self, subject, content, host, from_addr, to_addr):
"""send mail
"""
msg = MIMEText(content)
msg['Subject'] = subject
try:
smtp = smtplib.SMTP()
smtp.connect(host)
smtp.sendmail(from_addr, to_addr, msg.as_string())
smtp.close()
except:
print traceback.format_exc()