Blame pagure/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 9c2953
"""
Pierre-Yves Chibon 91b7a2
Mail handler for logging.
Pierre-Yves Chibon 9c2953
"""
Aurélien Bompard dcf6f6
Pierre-Yves Chibon 67d1cc
from __future__ import unicode_literals, absolute_import
Aurélien Bompard dcf6f6
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 socket
Pierre-Yves Chibon 91b7a2
import traceback
Pierre-Yves Chibon 91b7a2
Pierre-Yves Chibon 3f104d
import flask
Pierre-Yves Chibon 3f104d
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 e64e62
def format_callstack():
Pierre-Yves Chibon e64e62
    """ Format the callstack to find out the stack trace. """
Pierre-Yves Chibon e64e62
    ind = 0
Pierre-Yves Chibon e64e62
    for ind, frame in enumerate(f[0] for f in inspect.stack()):
Pierre-Yves Chibon 9c2953
        if "__name__" not in frame.f_globals:
Pierre-Yves Chibon e64e62
            continue
Pierre-Yves Chibon 9c2953
        modname = frame.f_globals["__name__"].split(".")[0]
Pierre-Yves Chibon e64e62
        if modname != "logging":
Pierre-Yves Chibon e64e62
            break
Pierre-Yves Chibon e64e62
Pierre-Yves Chibon e64e62
    def _format_frame(frame):
Pierre-Yves Chibon e64e62
        """ Format the frame. """
Pierre-Yves Chibon e64e62
        return '  File "%s", line %i in %s\n    %s' % (frame)
Pierre-Yves Chibon e64e62
Pierre-Yves Chibon e64e62
    stack = traceback.extract_stack()
Pierre-Yves Chibon e64e62
    stack = stack[:-ind]
Pierre-Yves Chibon e64e62
    return "\n".join([_format_frame(frame) for frame in stack])
Pierre-Yves Chibon e64e62
Pierre-Yves Chibon e64e62
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 b8c7ae
        """ Set up additional information on the record object. """
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 9c2953
        record.pid = "-"
Pierre-Yves Chibon 91b7a2
        if not isinstance(current_process, str):
Pierre-Yves Chibon 91b7a2
            record.pid = current_process.pid
Pierre-Yves Chibon 841a58
            # Be compatible with python-psutil 1.0 and 2.0, 3.0
Pierre-Yves Chibon 841a58
            proc_name = current_process.name
Pierre-Yves Chibon 841a58
            if callable(proc_name):
Pierre-Yves Chibon 841a58
                proc_name = proc_name()
Pierre-Yves Chibon 841a58
            record.proc_name = proc_name
Pierre-Yves Chibon 841a58
            # Be compatible with python-psutil 1.0 and 2.0, 3.0
Pierre-Yves Chibon 841a58
            cmd_line = current_process.cmdline
Pierre-Yves Chibon 841a58
            if callable(cmd_line):
Pierre-Yves Chibon 841a58
                cmd_line = cmd_line()
Pierre-Yves Chibon 841a58
            record.command_line = " ".join(cmd_line)
Pierre-Yves Chibon 841a58
Pierre-Yves Chibon e64e62
        record.callstack = format_callstack()
Pierre-Yves Chibon 3f104d
Pierre-Yves Chibon 3f104d
        try:
Pierre-Yves Chibon 9c2953
            record.url = getattr(flask.request, "url", "-")
Pierre-Yves Chibon 9c2953
            record.args = getattr(flask.request, "args", "-")
Pierre-Yves Chibon 9c2953
            record.form = "-"
Pierre-Yves Chibon 9c2953
            record.username = "-"
Patrick Uiterwijk 3a11db
            try:
Patrick Uiterwijk 3a11db
                record.form = dict(flask.request.form)
Pierre-Yves Chibon 9c2953
                if "csrf_token" in record.form:
Pierre-Yves Chibon 9c2953
                    record.form["csrf_token"] = "Was present, is cleaned up"
Patrick Uiterwijk 3a11db
            except RuntimeError:
Patrick Uiterwijk 3a11db
                pass
Patrick Uiterwijk 3a11db
            try:
Patrick Uiterwijk 3a11db
                record.username = flask.g.fas_user.username
Pierre-Yves Chibon 1fc6d8
            except Exception:
Patrick Uiterwijk 3a11db
                pass
Pierre-Yves Chibon 3f104d
        except RuntimeError:
Patrick Uiterwijk 3a11db
            # This means we are sending an error email from the worker
Pierre-Yves Chibon 9c2953
            record.url = "* Worker *"
Pierre-Yves Chibon 9c2953
            record.args = ""
Pierre-Yves Chibon 9c2953
            record.form = "-"
Pierre-Yves Chibon 9c2953
            record.username = "-"
Pierre-Yves Chibon 3f104d
Pierre-Yves Chibon 91b7a2
        return True
Pierre-Yves Chibon 91b7a2
Pierre-Yves Chibon 91b7a2
    @staticmethod
Pierre-Yves Chibon 91b7a2
    def get_current_process():
Pierre-Yves Chibon b8c7ae
        """ Return the current process (PID). """
Pierre-Yves Chibon 91b7a2
        if not psutil:
Pierre-Yves Chibon 0d7319
            return "Could not import psutil"
Patrick Uiterwijk 186002
        return psutil.Process()
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 3f104d
Pierre-Yves Chibon 3f104d
URL:    %(url)s
Pierre-Yves Chibon 3f104d
args:   %(args)s
Pierre-Yves Chibon 3f104d
form:   %(form)s
Pierre-Yves Chibon 36a91b
user:   %(username)s
Pierre-Yves Chibon 3f104d
Pierre-Yves Chibon 3f104d
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 a67cd7
def get_mail_handler(smtp_server, mail_admin, from_email):
Pierre-Yves Chibon 91b7a2
    """ Set up the handler sending emails for big exception
Pierre-Yves Chibon 91b7a2
    """
Pierre-Yves Chibon a67cd7
Pierre-Yves Chibon 91b7a2
    mail_handler = logging.handlers.SMTPHandler(
Pierre-Yves Chibon 9c2953
        smtp_server, from_email, mail_admin, "Pagure error"
Pierre-Yves Chibon 9c2953
    )
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