简体   繁体   English

使用 SMTPHandler 在 Python 日志记录 MemoryHandler 中整理输出

[英]Collate output in Python logging MemoryHandler with SMTPHandler

I have the logging module MemoryHandler set up to queue debug and error messages for the SMTPHandler target.我已将日志记录模块 MemoryHandler 设置为将 SMTPHandler 目标的调试和错误消息排队。 What I want is for an email to be sent when the process errors that contains all debug statements up to that point (one per line).我想要的是在包含所有调试语句(每行一个)的进程错误时发送电子邮件。 What I get instead is a separate email for every debug message.我得到的是每条调试消息的单独电子邮件。

This seems like it should be trivial, and part of the logging package, but I can't find anything about it, no examples, nothing on Google.这似乎应该是微不足道的,并且是日志记录包的一部分,但我找不到任何关于它的信息,没有示例,谷歌上什么也没有。

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)

Related to this:与此相关:

Do I need to add the error_mail_handler to the logger explicitly if it is a target of memory_handler anyway?如果它是memory_handler的目标,我是否需要明确地将error_mail_handler添加到记录器中? Should error_mail_handler be set to DEBUG or ERROR target? error_mail_handler应该设置为 DEBUG 还是 ERROR 目标? Does it even need a target when it is being fed from memory_handler ?当它从memory_handler被馈送时,它甚至需要一个目标吗?

Would love to see some working code from anyone who has solved this problem.希望看到解决此问题的任何人的一些工作代码。

You might want to use or adapt the BufferingSMTPHandler which is in this test script .您可能希望使用或调整此测试脚本中BufferingSMTPHandler

In general, you don't need to add a handler to a logger if it's the target of a MemoryHandler handler which has been added to a logger.通常,如果它是已添加到记录器的 MemoryHandler 处理程序的目标,则不需要向记录器添加处理程序。 If you set the level of a handler, that will affect what the handler actually processes - it won't process anything which is less severe than its level setting.如果您设置处理程序的级别,这将影响处理程序实际处理的内容 - 它不会处理比其级别设置更轻的任何内容。

Instead of buffering for email, consider posting unbuffered to a message stream on a messaging app, eg on Matrix, Discord, Slack, etc. Having said that, I wrote my own beastly thread-safe implementation of BufferingSMTPHandler ( backup link ) which sends emails from a separate thread.与其缓冲电子邮件,不如考虑将无缓冲发布到消息应用程序上的消息流中,例如在 Matrix、Discord、Slack 等上。话虽如此,我编写了自己的BufferingSMTPHandler备份链接)的线程安全实现,它发送电子邮件从一个单独的线程。 The primary goal is to not block the main thread.主要目标是不阻塞主线程。

As written, it uses two queues - this seemed necessary in order to implement some useful class-level parameters that are defined in the "Configurable parameters" section of the code.正如所写,它使用两个队列——这似乎是实现代码“可配置参数”部分中定义的一些有用的类级参数所必需的。 Although you can use the code as-is, it's probably better if you study and use it to write your own class.尽管您可以按原样使用代码,但如果您学习并使用它来编写自己的类可能会更好。

Issues:问题:

  • Some class-level parameters can perhaps be instance-level instead.一些类级参数也许可以是实例级的。
  • Either threading.Timer or the signal module could perhaps be used to avoid loops that run forever. threading.Timersignal模块都可以用来避免永远运行的循环。

If you are using django - here is simple buffering handler, which will use standard django email methods:如果您使用的是 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 = []

In django settings.py you will need to configure email and logging like this:在 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'
        }
    },
    ...
}

Updated Vinay Sajip's answer for python3.更新了 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)

For this purpose I use the BufferingSMTPHandler suggested by Vinay Sajip with one minor tweak: I set the buffer length to something really big (say 5000 log records) and manualy call the flush method of the handler every some seconds and after checking for internet conectivity.为此,我使用了 Vinay Sajip 建议的BufferingSMTPHandler并做了一个小调整:我将缓冲区长度设置为非常大的值(比如 5000 条日志记录),并每隔几秒手动调用一次处理程序的刷新方法,并在检查互联网连接后。

# 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)

I think the point about the SMTP logger is that it is meant to send out a significant log message functioning as some kind of alert if sent to a human recipient or else to be further processed by an automated recipient.我认为关于 SMTP 记录器的要点在于,它旨在发送一条重要的日志消息,如果发送给人类收件人或由自动收件人进一步处理,它会起到某种警报的作用。

If a collection of log messages is to be sent by email then that constitutes a report being sent at the end of execution of a task and writing that log to a file and then emailing the file would seem to be a reasonable solution.如果要通过电子邮件发送一组日志消息,那么这构成了在任务执行结束时发送的报告并将该日志写入文件然后通过电子邮件发送该文件似乎是一个合理的解决方案。

I took a look at the basic FileHandler log handler and how to build a mechanism to write to a temp file then attach that temp file when the script exits.我查看了基本的 FileHandler 日志处理程序,以及如何构建一种写入临时文件的机制,然后在脚本退出时附加该临时文件。

I found the "atexit" module that allows for a method to be registered that will be executed against an object when the script is exiting.我找到了“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()

My basic test script is:我的基本测试脚本是:

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")

Hope this helps somebody.希望这可以帮助某人。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM