在Python日志中使用MemoryHandler与SMTPHandler整理输出
我设置了一个叫做 MemoryHandler 的日志模块,用来把调试和错误信息排队,准备发送给 SMTPHandler(也就是发送邮件的目标)。我想要的是,当程序出错时,能发一封包含所有调试信息的邮件,每条信息单独一行。但我现在得到的却是每条调试信息都发一封单独的邮件。
这看起来应该很简单,而且应该是日志包的一部分,但我在网上找不到任何相关的信息、例子,甚至谷歌也没有。
log = logging.getLogger()
log.setLevel(logging.DEBUG)
debug_format = logging.Formatter("%(levelname)s at %(asctime)s in %(filename)s (line %(lineno)d):: %(message)s")
# write errors to email
error_mail_subject = "ERROR: Script error in %s on %s" % (sys.argv[0], os.uname()[1])
error_mail_handler = logging.handlers.SMTPHandler(SMTP_HOST, 'errors@'+os.uname()[1], [LOG_EMAIL], error_mail_subject)
error_mail_handler.setLevel(logging.ERROR)
#error_mail_handler.setLevel(logging.DEBUG)
error_mail_handler.setFormatter(debug_format)
# buffer debug messages so they can be sent with error emails
memory_handler = logging.handlers.MemoryHandler(1024*10, logging.ERROR, error_mail_handler)
memory_handler.setLevel(logging.DEBUG)
# attach handlers
log.addHandler(memory_handler)
log.addHandler(error_mail_handler)
相关的问题:
如果 memory_handler
已经是目标的话,我还需要把 error_mail_handler
明确添加到日志记录器吗?
error_mail_handler
应该设置为 DEBUG 还是 ERROR 目标?当它是从 memory_handler
获取信息时,真的需要一个目标吗?
希望能看到一些已经解决这个问题的人的代码示例。
6 个回答
如果你在使用Django框架,这里有一个简单的缓冲处理器,它会使用Django标准的邮件发送方法:
import logging
from django.conf import settings
from django.core.mail import EmailMessage
class DjangoBufferingSMTPHandler(logging.handlers.BufferingHandler):
def __init__(self, capacity, toaddrs=None, subject=None):
logging.handlers.BufferingHandler.__init__(self, capacity)
if toaddrs:
self.toaddrs = toaddrs
else:
# Send messages to site administrators by default
self.toaddrs = zip(*settings.ADMINS)[-1]
if subject:
self.subject = subject
else:
self.subject = 'logging'
def flush(self):
if len(self.buffer) == 0:
return
try:
msg = "\r\n".join(map(self.format, self.buffer))
emsg = EmailMessage(self.subject, msg, to=self.toaddrs)
emsg.send()
except Exception:
# handleError() will print exception info to stderr if logging.raiseExceptions is True
self.handleError(record=None)
self.buffer = []
在Django的设置文件settings.py中,你需要像下面这样配置邮件和日志:
EMAIL_USE_TLS = True
EMAIL_PORT = 25
EMAIL_HOST = '' # example: 'smtp.yandex.ru'
EMAIL_HOST_USER = '' # example: 'user@yandex.ru'
EMAIL_HOST_PASSWORD = ''
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
SERVER_EMAIL = EMAIL_HOST_USER
LOGGING = {
'handlers': {
...
'mail_buffer': {
'level': 'WARN',
'capacity': 9999,
'class': 'utils.logging.DjangoBufferingSMTPHandler',
# optional:
# 'toaddrs': 'admin@host.com'
# 'subject': 'log messages'
}
},
...
}
与其为发送邮件而缓冲,不如考虑直接把信息发送到消息应用的消息流中,比如在Matrix、Discord、Slack等平台上。说到这里,我自己写了一个线程安全的邮件发送工具,叫做 BufferingSMTPHandler
(备份链接),它可以在一个单独的线程中发送邮件。这样做的主要目的是不让主线程被阻塞。
这个工具的实现中用了两个队列——这样做是为了能够实现一些在代码的“可配置参数”部分定义的有用的类级别参数。虽然你可以直接使用这段代码,但最好还是先研究一下,然后用它来写自己的类。
存在的问题:
- 有些类级别的参数可能更适合做成实例级别的。
- 可以考虑使用
threading.Timer
或者signal
模块,来避免出现永远运行的循环。
你可能想要使用或调整一下 BufferingSMTPHandler
,它在这个测试脚本里。
一般来说,如果你已经给一个日志记录器添加了一个 MemoryHandler,那么就不需要再给这个日志记录器添加其他处理器了。如果你设置了一个处理器的级别,这个处理器就只会处理比它设置的级别更严重的日志信息,轻于这个级别的日志信息就不会被处理。