2013-06-07 20:01:23 +00:00
|
|
|
# Setup fedmsg logging.
|
2014-02-27 14:40:06 +00:00
|
|
|
|
|
|
|
# All of these modules are just used by the ContextInjector below.
|
|
|
|
import inspect
|
2015-05-08 19:08:45 +00:00
|
|
|
import hashlib
|
2014-02-27 14:40:06 +00:00
|
|
|
import logging
|
|
|
|
import os
|
|
|
|
import socket
|
|
|
|
import traceback
|
|
|
|
|
2014-07-04 04:35:27 +00:00
|
|
|
psutil = None
|
|
|
|
try:
|
|
|
|
import psutil
|
2014-07-07 15:12:56 +00:00
|
|
|
except (OSError, ImportError):
|
2014-07-04 04:35:27 +00:00
|
|
|
# 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
|
|
|
|
|
2014-02-27 14:40:06 +00:00
|
|
|
|
2015-05-08 19:08:45 +00:00
|
|
|
radio_silence = """
|
|
|
|
*** %i instances of this error seen. No more mail will be sent. ***
|
|
|
|
""".strip()
|
|
|
|
|
|
|
|
seen_errors = {} # This could be a default dict.
|
|
|
|
error_limit = 100
|
|
|
|
|
|
|
|
|
2014-02-27 14:40:06 +00:00
|
|
|
class ContextInjector(logging.Filter):
|
|
|
|
""" 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.
|
|
|
|
|
2015-05-08 19:08:45 +00:00
|
|
|
Here, we somewhat abuse the concept of a filter. We for the most part
|
|
|
|
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.
|
|
|
|
|
|
|
|
After we've seen an error ~100 times, we stop sending email to avoid choking
|
|
|
|
the world.
|
2014-02-27 14:40:06 +00:00
|
|
|
|
|
|
|
http://docs.python.org/2/howto/logging-cookbook.html#filters-contextual
|
|
|
|
"""
|
|
|
|
|
|
|
|
def filter(self, record):
|
2014-02-27 15:05:34 +00:00
|
|
|
current_process = ContextInjector.get_current_process()
|
|
|
|
current_hostname = socket.gethostname()
|
|
|
|
|
2014-02-27 14:40:06 +00:00
|
|
|
record.host = current_hostname
|
|
|
|
record.proc = current_process
|
|
|
|
record.pid = current_process.pid
|
|
|
|
record.proc_name = current_process.name
|
2015-09-28 18:04:59 +00:00
|
|
|
record.command_line = current_process.cmdline
|
|
|
|
# These are callabls on more modern versions of psutil.
|
|
|
|
if callable(record.proc_name):
|
|
|
|
record.proc_name = record.proc_name()
|
|
|
|
if callable(record.command_line):
|
|
|
|
record.command_line = record.command_line()
|
|
|
|
record.command_line = " ".join(record.command_line)
|
2014-02-27 14:40:06 +00:00
|
|
|
record.callstack = self.format_callstack()
|
2015-05-08 19:08:45 +00:00
|
|
|
record.farewell = ""
|
|
|
|
|
|
|
|
key = hashlib.sha256(record.callstack).hexdigest()
|
|
|
|
if not key in seen_errors:
|
|
|
|
seen_errors[key] = 0
|
|
|
|
|
|
|
|
if seen_errors[key] > error_limit:
|
|
|
|
return False
|
|
|
|
|
|
|
|
seen_errors[key] += 1
|
|
|
|
|
|
|
|
if seen_errors[key] > error_limit:
|
|
|
|
record.farewell = radio_silence % error_limit
|
|
|
|
|
2014-02-27 14:40:06 +00:00
|
|
|
return True
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def format_callstack():
|
|
|
|
for i, frame in enumerate(f[0] for f in inspect.stack()):
|
|
|
|
if not '__name__' in frame.f_globals:
|
|
|
|
continue
|
|
|
|
modname = frame.f_globals['__name__'].split('.')[0]
|
|
|
|
if modname != "logging":
|
|
|
|
break
|
|
|
|
|
|
|
|
def _format_frame(frame):
|
|
|
|
return ' File "%s", line %i in %s\n %s' % (frame)
|
|
|
|
|
|
|
|
stack = traceback.extract_stack()
|
|
|
|
stack = stack[:-i]
|
|
|
|
return "\n".join([_format_frame(frame) for frame in stack])
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def get_current_process():
|
|
|
|
mypid = os.getpid()
|
2014-07-04 04:35:27 +00:00
|
|
|
|
|
|
|
if not psutil:
|
|
|
|
raise OSError("Could not import psutil for %r" % mypid)
|
|
|
|
|
2014-02-27 14:40:06 +00:00
|
|
|
for proc in psutil.process_iter():
|
|
|
|
if proc.pid == mypid:
|
|
|
|
return proc
|
|
|
|
|
|
|
|
# This should be impossible.
|
|
|
|
raise ValueError("Could not find process %r" % mypid)
|
|
|
|
|
2014-02-27 15:01:09 +00:00
|
|
|
@classmethod
|
|
|
|
def __json__(cls):
|
|
|
|
""" We need to be jsonifiable for "fedmsg-config" """
|
|
|
|
return {'name': 'ContextInjector'}
|
|
|
|
|
2014-02-27 14:40:06 +00:00
|
|
|
|
|
|
|
hefty_format = """Message
|
|
|
|
-------
|
|
|
|
[%(asctime)s][%(name)10s %(levelname)7s]
|
|
|
|
%(message)s
|
2015-05-08 19:08:45 +00:00
|
|
|
%(farewell)s
|
2014-02-27 14:40:06 +00:00
|
|
|
|
|
|
|
Process Details
|
|
|
|
---------------
|
|
|
|
host: %(host)s
|
|
|
|
PID: %(pid)s
|
|
|
|
name: %(proc_name)s
|
|
|
|
command: %(command_line)s
|
|
|
|
|
|
|
|
Callstack that lead to the logging statement
|
|
|
|
--------------------------------------------
|
|
|
|
%(callstack)s
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
2013-06-07 20:01:23 +00:00
|
|
|
# See the following for constraints on this format http://bit.ly/Xn1WDn
|
|
|
|
config = dict(
|
|
|
|
logging=dict(
|
|
|
|
version=1,
|
|
|
|
formatters=dict(
|
|
|
|
bare={
|
2013-10-09 13:25:02 +00:00
|
|
|
"datefmt": "%Y-%m-%d %H:%M:%S",
|
2015-10-01 16:13:20 +00:00
|
|
|
{% if inventory_hostname.startswith('bodhi-backend01') %}
|
|
|
|
# Here we use a different format just so we can include
|
|
|
|
# threadName. We set the threadName in the bodhi masher to some
|
|
|
|
# useful strings that will tell us which branch is doing what.
|
|
|
|
# See https://github.com/fedora-infra/bodhi/commit/66d94094
|
|
|
|
"format": "[%(asctime)s][%(name)10s %(levelname)7s]%(threadName)s %(message)s"
|
|
|
|
{% else %}
|
2013-10-09 13:25:02 +00:00
|
|
|
"format": "[%(asctime)s][%(name)10s %(levelname)7s] %(message)s"
|
2015-10-01 16:13:20 +00:00
|
|
|
{% endif %}
|
2013-06-07 20:01:23 +00:00
|
|
|
},
|
2014-02-27 14:40:06 +00:00
|
|
|
hefty={
|
|
|
|
"datefmt": "%Y-%m-%d %H:%M:%S",
|
|
|
|
"format": hefty_format,
|
|
|
|
},
|
|
|
|
),
|
|
|
|
filters=dict(
|
|
|
|
context={
|
|
|
|
# This "()" syntax in the stdlib doesn't seem to be documented
|
|
|
|
# anywhere. I had to read
|
|
|
|
# /usr/lib64/python2.7/logging/config.py to figure it out.
|
|
|
|
"()": ContextInjector,
|
|
|
|
},
|
2013-06-07 20:01:23 +00:00
|
|
|
),
|
|
|
|
handlers=dict(
|
|
|
|
console={
|
|
|
|
"class": "logging.StreamHandler",
|
|
|
|
"formatter": "bare",
|
2015-02-12 15:12:58 +00:00
|
|
|
"level": "{{fedmsg_loglevel}}",
|
2013-06-07 20:01:23 +00:00
|
|
|
"stream": "ext://sys.stdout",
|
2014-02-24 19:38:38 +00:00
|
|
|
},
|
|
|
|
mailer={
|
|
|
|
"class": "logging.handlers.SMTPHandler",
|
2014-02-27 14:40:06 +00:00
|
|
|
"formatter": "hefty",
|
|
|
|
"filters": ["context"],
|
2014-02-24 19:38:38 +00:00
|
|
|
"level": "ERROR",
|
2014-02-24 19:59:27 +00:00
|
|
|
"mailhost": "bastion.phx2.fedoraproject.org",
|
2014-02-24 19:38:38 +00:00
|
|
|
"fromaddr": "fedmsg@fedoraproject.org",
|
|
|
|
"toaddrs": ["sysadmin-datanommer-members@fedoraproject.org"],
|
2015-02-09 02:38:24 +00:00
|
|
|
"subject": "fedmsg error log %s" % socket.gethostname(),
|
2014-02-24 19:38:38 +00:00
|
|
|
},
|
2013-06-07 20:01:23 +00:00
|
|
|
),
|
|
|
|
loggers=dict(
|
|
|
|
fedmsg={
|
2015-02-12 15:12:58 +00:00
|
|
|
"level": "{{fedmsg_loglevel}}",
|
2013-06-07 20:01:23 +00:00
|
|
|
"propagate": False,
|
2014-02-24 19:38:38 +00:00
|
|
|
"handlers": ["console", "mailer"],
|
2013-06-07 20:01:23 +00:00
|
|
|
},
|
|
|
|
moksha={
|
2015-02-12 15:12:58 +00:00
|
|
|
"level": "{{fedmsg_loglevel}}",
|
2013-06-07 20:01:23 +00:00
|
|
|
"propagate": False,
|
2014-02-24 19:38:38 +00:00
|
|
|
"handlers": ["console", "mailer"],
|
2013-06-07 20:01:23 +00:00
|
|
|
},
|
|
|
|
),
|
|
|
|
),
|
|
|
|
)
|