pythonpython-3.xemailloggingmonitoring

Python logging - already logging to a rotating file; want to send an email when logger.warn() or worse is called


I've seen mentioned that with Python's built-in logging functionality you can create multiple handlers to output logs of different levels to different locations. I've already setup my logger with a QueueHandler and a RotatingFileHandler so that log messages are queued and written to a rotating file so as to minimally impact the runtime of my other code.

What I want to do next is use an email handler to notify myself if my application encounters any higher level issues during execution.

I've found answers like this: Send email with Python logging, but I haven't yet found something that combines everything I'm doing.

Below is the code I'm currently working with.

import logging
import logging.handlers
import logging.config
import os
import platform
from queue import Queue

class JSONLogFormatter(logging.Formatter):
    def format(self, record):
        # removed for brevity, this formats the log output...
        return json.dumps(log_record)

def get_log_path(app_name):
    # removed for brevity, this generates log_path based on the OS my code is running on
    return log_path

def initialize(app_name="app_name", log_level=logging.DEBUG, log_size=10*1024*1024, num_files=10):
    
    # Create the main logger
    logger = logging.getLogger(__name__)
    logger.setLevel(log_level)

    # Create a queue for log records
    log_queue = Queue()

    # Create a QueueHandler to send logs to the queue
    queue_handler = logging.handlers.QueueHandler(log_queue)
    # Set a log level for the QueueHandler if needed
    # queue_handler.setLevel(log_level)

    # Add the Handler to the main logger    
    logger.addHandler(queue_handler)

    # Get the log path
    log_path = get_log_path(app_name)

    # Create a RotatingFileHandler that manages file size & rotation
    rotating_file_handler = logging.handlers.RotatingFileHandler(
        log_path,
        log_size,     # default = 10x1024x1024 = 10 MB per log file
        num_files     # default = 10 = 100 MBs worth of logs
    )
    rotating_file_handler.setFormatter(JSONLogFormatter())
    # Set a log level for the RotatingFileHandler if needed
    # rotating_file_handler.setLevel(log_level)

    # Create a QueueListener to process records in a separate thread
    queue_listener = logging.handlers.QueueListener(log_queue, rotating_file_handler)

    return logger, queue_listener

def _test():
    logger, listener = initialize()
    listener.start()

    try:
        logger.info("This log entry should go to a platform-appropriate directory.")
    finally:
        listener.stop()

Solution

  • You can use python's SMTPHandler.

    1. Create SMTPHandler instance.

    2. Set the SMPTHandler instance level to 'WARNING'.

    3. Add it to QueueListener. You can add multiple handlers to QueueListener.

      QueueListener(queue, *handlers, respect_handler_level=False)
      
    4. Set respect_handler_level to True

    If respect_handler_level is True, a handler’s level is respected (compared with the level for the message) when deciding whether to pass messages to that handler.

    import logging
    from logging.handlers import SMTPHandler
    
    def initialize(app_name="app_name", log_level=logging.DEBUG, log_size=10*1024*1024, num_files=10):
        # Create the main logger
        logger = logging.getLogger(__name__)
        logger.setLevel(log_level)
        # provide necessary arguments
        mailHandler = SMTPHandler(mailhost, fromaddr, toaddrs=['user1@gmail.com', 'user2@gmail.com'], subject, credentials=(username, password), secure=())
        mailHandler.setLevel(logging.WARNING)
        logger.addHandler(mailHandler)
        # Your code here
    
        queue_listener = logging.handlers.QueueListener(log_queue, rotating_file_handler, mailHandler, respect_handler_level=True)
        return logger, queue_listener
    
    def _test():
        logger, listener = initialize()
        listener.start()
    
        try:
            logger.info("This log entry should go to a platform-appropriate directory.")
            logger.warning('this is a warning log')
        finally:
            listener.stop()
    

    "want to send an email when logger.warn() or worse is called"

    Note:

    logger.warn() is deprecated, do not use it - use logger.warning() instead. link