使用SMTPHandler整理Python日志记录MemoryHandler中的输出
我已经设置了日志模块MemoryHandler,将调试和错误消息排入SMTPHandler目标的队列。我想要的是在进程出错时发送一封电子邮件,其中包含截至该点的所有调试语句(每行一条)。我得到的是一封针对每个调试消息的单独电子邮件 这看起来应该很简单,是日志包的一部分,但我找不到关于它的任何东西,没有例子,在谷歌上什么都没有使用SMTPHandler整理Python日志记录MemoryHandler中的输出,python,logging,Python,Logging,我已经设置了日志模块MemoryHandler,将调试和错误消息排入SMTPHandler目标的队列。我想要的是在进程出错时发送一封电子邮件,其中包含截至该点的所有调试语句(每行一条)。我得到的是一封针对每个调试消息的单独电子邮件 这看起来应该很简单,是日志包的一部分,但我找不到关于它的任何东西,没有例子,在谷歌上什么都没有 log = logging.getLogger() log.setLevel(logging.DEBUG) debug_format = logging.Formatter
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)
与此相关:
如果它是内存处理程序
的目标,我是否需要将错误\u邮件处理程序
显式添加到记录器中?
是否应将error\u mail\u handler
设置为DEBUG或error target?当它从内存\u处理程序馈送时,它甚至需要一个目标吗
希望从解决此问题的任何人那里看到一些工作代码。您可能希望使用或调整中的缓冲mtphandler
通常,如果某个处理程序是已添加到记录器的MemoryHandler处理程序的目标,则不需要将其添加到记录器。如果您设置处理程序的级别,这将影响处理程序实际处理的内容-它不会处理比其级别设置不严重的任何内容。为此,我使用Vinay Sajip建议的BufferingSMTPHandler进行了一个小调整:我将缓冲区长度设置为非常大的值(例如5000个日志记录)并在检查internet连接后每隔几秒钟手动调用处理程序的刷新方法
# init
log_handler1 = BufferingSMTPHandler(
'smtp.host.lala', "from@test.com", ['to@test.com'], 'Log event(s)',5000)
...
logger.addHandler(log_handler1)
...
# main code
...
if internet_connection_ok and seconds_since_last_flush>60:
log_handler1.flush() # send buffered log records (if any)
代替缓存电子邮件,考虑在消息应用程序上发布缓冲消息,例如矩阵、不和谐、松弛等。主要目标是不阻塞主线程
如前所述,它使用了两个队列——这似乎是实现代码“可配置参数”部分中定义的一些有用的类级参数所必需的。尽管您可以按原样使用代码,但如果您学习并使用它编写自己的类,可能会更好
问题:
- 有些类级参数可能是实例级的
threading.Timer
或signal
模块可能用于避免永远运行的循环
如果您使用的是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'
}
},
...
}
我认为SMTP记录器的要点是,如果发送给人工收件人或由自动收件人进一步处理,它将发送一条重要的日志消息,作为某种警报
如果要通过电子邮件发送日志消息的集合,那么在任务执行结束时发送报告并将该日志写入文件,然后通过电子邮件发送该文件似乎是一个合理的解决方案
我研究了基本的FileHandler日志处理程序,以及如何构建一种机制来写入临时文件,然后在脚本退出时附加该临时文件
我找到了“atexit”模块,该模块允许注册一个方法,该方法将在脚本退出时针对对象执行
import logging
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
import os
from email import encoders
import uuid
# atexit allows for a method to be set to handle an object when the script exits
import atexit
filename = uuid.uuid4().hex
class MailLogger:
def __init__(self, filePath, smtpDict):
self.filePath = filePath
self.smtpDict = smtpDict
# Generate random file name
filename = '%s.txt' % ( uuid.uuid4().hex )
# Create full filename
filename = '%s/%s' % (filePath,filename)
self.filename = filename
self.fileLogger = logging.getLogger('mailedLog')
self.fileLogger.setLevel(logging.INFO)
self.fileHandler = logging.FileHandler(filename)
self.fileHandler.setLevel(logging.INFO)
formatter = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
self.fileHandler.setFormatter(formatter)
self.fileLogger.addHandler(self.fileHandler)
atexit.register(self.mailOut)
def mailOut(self):
'''
Script is exiting so time to mail out the log file
"emailSettings": {
"smtpServer" : "smtp.dom.com",
"smtpPort" : 25,
"sender" : "sender@dom.com>",
"recipients" : [
"recipient@dom.com"
],
"subject" : "Email Subject"
},
'''
# Close the file handler
smtpDict = self.smtpDict
self.fileHandler.close()
msg = MIMEMultipart('alternative')
s = smtplib.SMTP(smtpDict["smtpServer"], smtpDict["smtpPort"] )
msg['Subject'] = smtpDict["subject"]
msg['From'] = smtpDict["sender"]
msg['To'] = ','.join(smtpDict["recipients"])
body = 'See attached report file'
content = MIMEText(body, 'plain')
msg.attach(content)
attachment = MIMEBase('application', 'octet-stream')
attachment.set_payload(open(self.filename, 'rb').read())
encoders.encode_base64(attachment)
attachment.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(self.filename))
msg.attach(attachment)
s.send_message(msg)
s.quit()
我的基本测试脚本是:
from EmailLogRpt import MailLogger
import time
smtpDict = {
"smtpServer" : "smtp.dom.com",
"smtpPort" : 25,
"sender" : "sender@dom.com",
"recipients" : [
"recpient@dom.com>"
],
"subject" : "Email Subject"
}
myMailLogger = MailLogger("/home/ed/tmp",smtpDict).fileLogger
myMailLogger.info("test msg 1")
time.sleep(5)
myMailLogger.info("test msg 2")
希望这对某人有所帮助。更新了Vinay Sajip对python3的回答
import logging
from logging.handlers import BufferingHandler
class BufferingSMTPHandler(BufferingHandler):
def __init__(self, mailhost, fromaddr, toaddrs, subject, capacity):
logging.handlers.BufferingHandler.__init__(self, capacity)
self.mailhost = mailhost
self.mailport = None
self.fromaddr = fromaddr
self.toaddrs = toaddrs
self.subject = subject
self.setFormatter(logging.Formatter("%(asctime)s %(levelname)-5s %(message)s"))
def flush(self):
if len(self.buffer) > 0:
try:
import smtplib
port = self.mailport
if not port:
port = smtplib.SMTP_PORT
smtp = smtplib.SMTP(self.mailhost, port)
msg = '''From: {}\r\nTo: {}\r\nSubject: {}\r\n\r\n'''.format(
self.fromaddr,
",".join(self.toaddrs),
self.subject
)
for record in self.buffer:
s = self.format(record)
print (s)
msg = msg + s + "\r\n"
smtp.sendmail(self.fromaddr, self.toaddrs, msg)
smtp.quit()
except:
self.handleError(None) # no particular record
self.buffer = []
#update for @Anant
if __name__ == '__main__'
buff_smtp_handler=BufferingSMTPHandler(...your args)
buff_smtp_handler.setLevel(logging.ERROR)
handlers=[buff_smtp_handler]
logging.basicConfig(handlers=handlers)
这仅在刷新处理程序时处理程序仍在作用域中时才起作用。否则,必须从logger对象检索处理程序才能调用flush。为什么该类不是标准库的一部分?一次发送所有消息似乎更有用,特别是对于批处理作业(与长期运行的服务器相反)@anarcat:只是因为你必须在某个地方画条线。“为什么在那里画条线?”“因为你必须在某个地方画条线”真的,我的观点是,我发现SMTPHandler基本上是无用的,而BufferingSMTPHandler对于日志中的SMTP支持是必不可少的。这条线划错了地方。@anarcat:事实上,这条线可能已经划得太远了。SMTP可能是出了名的滞后,所以实际上最好使用QueueHandler之类的工具,并在单独的线程或进程中发送电子邮件。当然,在例如web应用程序中,当SMTP连接超时时,登录到SMTP可能会导致响应缓慢,甚至出现挂起。@Alf47应该是这样,不过我建议使用dictConfig()
API而不是fileConfig()
,以更好地配置日志API。Hi@10mjg,您能否帮助我了解如何仅为严重错误或更高级别的日志发送电子邮件。@Anant我不确定,但我已尝试为您查找解决方案。如果这就是您想要的,那么它可能只需要logger.setLevel(logging.ERROR)。。。或者使用MemoryHandler而不是BufferingHandler,并为MemoryHandler设置logging.ERROR的刷新级别@Anant(类似于logger.setLevel解决方案,如果您不想使用其他处理程序,也可以仅在BufferingSMTPHAndler处理程序实例上调用.setLevel(logging.ERROR)。我在上面的代码中添加了一个小示例。您的建议确实有效,并帮助我按预期进行配置。