Collate output in Python logging MemoryHandler with SMTPHandler

前端 未结 5 1808
傲寒
傲寒 2020-12-01 00:14

I have the logging module MemoryHandler set up to queue debug and error messages for the SMTPHandler target. What I want is for an email to be sent when the process errors t

相关标签:
5条回答
  • 2020-12-01 00:47

    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.

    # 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)
    
    0 讨论(0)
  • 2020-12-01 00:51

    If you are using django - here is simple buffering handler, which will use standard django email methods:

    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:

    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'
            }
        },
        ...
    }
    
    0 讨论(0)
  • 2020-12-01 00:54

    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.

    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.

    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.

    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.

    0 讨论(0)
  • 2020-12-01 01:07

    Instead of buffering for email, consider posting unbuffered to a message stream on a messaging app, e.g. 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. 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.
    0 讨论(0)
  • 2020-12-01 01:10

    You might want to use or adapt the BufferingSMTPHandler which is in this test script.

    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. 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.

    0 讨论(0)
提交回复
热议问题