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