Blame progit/mail_logging.py

Pierre-Yves Chibon 91b7a2
# -*- coding: utf-8 -*-
Pierre-Yves Chibon 91b7a2
#
Pierre-Yves Chibon 91b7a2
# Copyright Š 2014  Red Hat, Inc.
Pierre-Yves Chibon 91b7a2
#
Pierre-Yves Chibon 91b7a2
# This copyrighted material is made available to anyone wishing to use,
Pierre-Yves Chibon 91b7a2
# modify, copy, or redistribute it subject to the terms and conditions
Pierre-Yves Chibon 91b7a2
# of the GNU General Public License v.2, or (at your option) any later
Pierre-Yves Chibon 91b7a2
# version.  This program is distributed in the hope that it will be
Pierre-Yves Chibon 91b7a2
# useful, but WITHOUT ANY WARRANTY expressed or implied, including the
Pierre-Yves Chibon 91b7a2
# implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR
Pierre-Yves Chibon 91b7a2
# PURPOSE.  See the GNU General Public License for more details.  You
Pierre-Yves Chibon 91b7a2
# should have received a copy of the GNU General Public License along
Pierre-Yves Chibon 91b7a2
# with this program; if not, write to the Free Software Foundation,
Pierre-Yves Chibon 91b7a2
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Pierre-Yves Chibon 91b7a2
#
Pierre-Yves Chibon 91b7a2
# Any Red Hat trademarks that are incorporated in the source
Pierre-Yves Chibon 91b7a2
# code or documentation are not subject to the GNU General Public
Pierre-Yves Chibon 91b7a2
# License and may only be used or replicated with the express permission
Pierre-Yves Chibon 91b7a2
# of Red Hat, Inc.
Pierre-Yves Chibon 91b7a2
#
Pierre-Yves Chibon 91b7a2
Pierre-Yves Chibon 91b7a2
'''
Pierre-Yves Chibon 91b7a2
Mail handler for logging.
Pierre-Yves Chibon 91b7a2
'''
Pierre-Yves Chibon 91b7a2
import logging
Pierre-Yves Chibon 91b7a2
import logging.handlers
Pierre-Yves Chibon 91b7a2
Pierre-Yves Chibon 91b7a2
import inspect
Pierre-Yves Chibon 91b7a2
import os
Pierre-Yves Chibon 91b7a2
import socket
Pierre-Yves Chibon 91b7a2
import traceback
Pierre-Yves Chibon 91b7a2
Pierre-Yves Chibon 91b7a2
psutil = None
Pierre-Yves Chibon 91b7a2
try:
Pierre-Yves Chibon 91b7a2
    import psutil
Pierre-Yves Chibon 642e1c
except (OSError, ImportError):  # pragma: no cover
Pierre-Yves Chibon 91b7a2
    # We run into issues when trying to import psutil from inside mod_wsgi on
Pierre-Yves Chibon 91b7a2
    # rhel7.  If we hit that here, then just fail quietly.
Pierre-Yves Chibon 91b7a2
    # https://github.com/jmflinuxtx/kerneltest-harness/pull/17#issuecomment-48007837
Pierre-Yves Chibon 91b7a2
    pass
Pierre-Yves Chibon 91b7a2
Pierre-Yves Chibon 91b7a2
Pierre-Yves Chibon 642e1c
class ContextInjector(logging.Filter):  # pragma: no cover
Pierre-Yves Chibon 91b7a2
    """ Logging filter that adds context to log records.
Pierre-Yves Chibon 91b7a2
Pierre-Yves Chibon 91b7a2
    Filters are typically used to "filter" log records.  They declare a filter
Pierre-Yves Chibon 91b7a2
    method that can return True or False.  Only records with 'True' will
Pierre-Yves Chibon 91b7a2
    actually be logged.
Pierre-Yves Chibon 91b7a2
Pierre-Yves Chibon 91b7a2
    Here, we somewhat abuse the concept of a filter.  We always return true,
Pierre-Yves Chibon 91b7a2
    but we use the opportunity to hang important contextual information on the
Pierre-Yves Chibon 91b7a2
    log record to later be used by the logging Formatter.  We don't normally
Pierre-Yves Chibon 91b7a2
    want to see all this stuff in normal log records, but we *do* want to see
Pierre-Yves Chibon 91b7a2
    it when we are emailed error messages.  Seeing an error, but not knowing
Pierre-Yves Chibon 91b7a2
    which host it comes from, is not that useful.
Pierre-Yves Chibon 91b7a2
Pierre-Yves Chibon 91b7a2
    http://docs.python.org/2/howto/logging-cookbook.html#filters-contextual
Pierre-Yves Chibon 91b7a2
Pierre-Yves Chibon 91b7a2
    This code has been originally written by Ralph Bean for the fedmsg
Pierre-Yves Chibon 91b7a2
    project:
Pierre-Yves Chibon 91b7a2
        https://github.com/fedora-infra/fedmsg/
Pierre-Yves Chibon 91b7a2
    and can be found at:
Pierre-Yves Chibon 91b7a2
        https://infrastructure.fedoraproject.org/cgit/ansible.git/tree/roles/fedmsg/base/templates/logging.py.j2
Pierre-Yves Chibon 91b7a2
Pierre-Yves Chibon 91b7a2
    """
Pierre-Yves Chibon 91b7a2
Pierre-Yves Chibon 91b7a2
    def filter(self, record):
Pierre-Yves Chibon 91b7a2
        current_process = ContextInjector.get_current_process()
Pierre-Yves Chibon 91b7a2
        current_hostname = socket.gethostname()
Pierre-Yves Chibon 91b7a2
Pierre-Yves Chibon 91b7a2
        record.host = current_hostname
Pierre-Yves Chibon 91b7a2
        record.proc = current_process
Pierre-Yves Chibon 91b7a2
        record.pid = '-'
Pierre-Yves Chibon 91b7a2
        if not isinstance(current_process, str):
Pierre-Yves Chibon 91b7a2
            record.pid = current_process.pid
Pierre-Yves Chibon c0483c
            record.proc_name = current_process.name
Pierre-Yves Chibon c0483c
            record.command_line = " ".join(current_process.cmdline)
Pierre-Yves Chibon 91b7a2
        record.callstack = self.format_callstack()
Pierre-Yves Chibon 91b7a2
        return True
Pierre-Yves Chibon 91b7a2
Pierre-Yves Chibon 91b7a2
    @staticmethod
Pierre-Yves Chibon 91b7a2
    def format_callstack():
Pierre-Yves Chibon 91b7a2
        for i, frame in enumerate(f[0] for f in inspect.stack()):
Pierre-Yves Chibon 91b7a2
            if '__name__' not in frame.f_globals:
Pierre-Yves Chibon 91b7a2
                continue
Pierre-Yves Chibon 91b7a2
            modname = frame.f_globals['__name__'].split('.')[0]
Pierre-Yves Chibon 91b7a2
            if modname != "logging":
Pierre-Yves Chibon 91b7a2
                break
Pierre-Yves Chibon 91b7a2
Pierre-Yves Chibon 91b7a2
        def _format_frame(frame):
Pierre-Yves Chibon 91b7a2
            return '  File "%s", line %i in %s\n    %s' % (frame)
Pierre-Yves Chibon 91b7a2
Pierre-Yves Chibon 91b7a2
        stack = traceback.extract_stack()
Pierre-Yves Chibon 91b7a2
        stack = stack[:-i]
Pierre-Yves Chibon 91b7a2
        return "\n".join([_format_frame(frame) for frame in stack])
Pierre-Yves Chibon 91b7a2
Pierre-Yves Chibon 91b7a2
    @staticmethod
Pierre-Yves Chibon 91b7a2
    def get_current_process():
Pierre-Yves Chibon 91b7a2
        mypid = os.getpid()
Pierre-Yves Chibon 91b7a2
Pierre-Yves Chibon 91b7a2
        if not psutil:
Pierre-Yves Chibon 91b7a2
            return "Could not import psutil for %r" % mypid
Pierre-Yves Chibon 91b7a2
Pierre-Yves Chibon 91b7a2
        for proc in psutil.process_iter():
Pierre-Yves Chibon 91b7a2
            if proc.pid == mypid:
Pierre-Yves Chibon 91b7a2
                return proc
Pierre-Yves Chibon 91b7a2
Pierre-Yves Chibon 91b7a2
        # This should be impossible.
Pierre-Yves Chibon 91b7a2
        raise ValueError("Could not find process %r" % mypid)
Pierre-Yves Chibon 91b7a2
Pierre-Yves Chibon 91b7a2
Pierre-Yves Chibon 91b7a2
MSG_FORMAT = """Process Details
Pierre-Yves Chibon 91b7a2
---------------
Pierre-Yves Chibon 91b7a2
host:     %(host)s
Pierre-Yves Chibon 91b7a2
PID:      %(pid)s
Pierre-Yves Chibon 91b7a2
name:     %(proc_name)s
Pierre-Yves Chibon 91b7a2
command:  %(command_line)s
Pierre-Yves Chibon 91b7a2
Pierre-Yves Chibon 91b7a2
Message type:       %(levelname)s
Pierre-Yves Chibon 91b7a2
Location:           %(pathname)s:%(lineno)d
Pierre-Yves Chibon 91b7a2
Module:             %(module)s
Pierre-Yves Chibon 91b7a2
Function:           %(funcName)s
Pierre-Yves Chibon 91b7a2
Time:               %(asctime)s
Pierre-Yves Chibon 91b7a2
Pierre-Yves Chibon 91b7a2
Message:
Pierre-Yves Chibon 91b7a2
--------
Pierre-Yves Chibon 91b7a2
Pierre-Yves Chibon 91b7a2
%(message)s
Pierre-Yves Chibon 91b7a2
Pierre-Yves Chibon 91b7a2
Pierre-Yves Chibon 91b7a2
Callstack that lead to the logging statement
Pierre-Yves Chibon 91b7a2
--------------------------------------------
Pierre-Yves Chibon 91b7a2
%(callstack)s
Pierre-Yves Chibon 91b7a2
"""
Pierre-Yves Chibon 91b7a2
Pierre-Yves Chibon 91b7a2
Pierre-Yves Chibon 91b7a2
def get_mail_handler(smtp_server, mail_admin):
Pierre-Yves Chibon 91b7a2
    """ Set up the handler sending emails for big exception
Pierre-Yves Chibon 91b7a2
    """
Pierre-Yves Chibon 91b7a2
    mail_handler = logging.handlers.SMTPHandler(
Pierre-Yves Chibon 91b7a2
        smtp_server,
Pierre-Yves Chibon 91b7a2
        'nobody@fedoraproject.org',
Pierre-Yves Chibon 91b7a2
        mail_admin,
Pierre-Yves Chibon 91b7a2
        'Progit error')
Pierre-Yves Chibon 91b7a2
    mail_handler.setFormatter(logging.Formatter(MSG_FORMAT))
Pierre-Yves Chibon 91b7a2
    mail_handler.setLevel(logging.ERROR)
Pierre-Yves Chibon 91b7a2
    mail_handler.addFilter(ContextInjector())
Pierre-Yves Chibon 91b7a2
    return mail_handler