# Copyright (c) 2010, 2011, Canonical Ltd
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, version 3 only.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see .
# GNU Lesser General Public License version 3 (see the file LICENSE).
"""Various hooks that can be used to populate OOPS reports.
The default_hooks list contains some innocuous hooks which most reporters will
want.
"""
__all__ = [
"attach_exc_info",
"attach_date",
"attach_hostname",
"copy_reporter",
"copy_topic",
"copy_url",
"default_hooks",
"safe_unicode",
]
import datetime
import socket
import traceback
from pytz import utc
# Used to detect missing keys.
_sentinel = object()
def _simple_copy(key):
"""Curry a simple hook that copies a key from context to report."""
def copy_key(report, context):
value = context.get(key, _sentinel)
if value is not _sentinel:
report[key] = value
copy_key.__doc__ = (
f"Copy the {key} field from context to report, if present."
)
return copy_key
copy_reporter = _simple_copy("reporter")
copy_topic = _simple_copy("topic")
copy_url = _simple_copy("url")
def safe_unicode(obj):
"""Used to reliably get *a* string for an object.
This is called on objects like exceptions, where bson won't be able to
serialize it, but a representation is needed for the report. It is
exposed a convenience for other on_create hook authors.
"""
if isinstance(obj, str):
return obj
# A call to str(obj) could raise anything at all.
# We'll ignore these errors, and print something
# useful instead, but also log the error.
# We disable the pylint warning for the blank except.
try:
value = str(obj)
except Exception:
value = f""
# Some objects give back bytestrings to __unicode__...
if isinstance(value, bytes):
value = value.decode("latin-1")
return value
def attach_date(report, context):
"""Set the time key in report to a datetime of now."""
report["time"] = datetime.datetime.now(utc)
def attach_exc_info(report, context):
"""Attach exception info to the report.
This reads the 'exc_info' key from the context and sets the:
* type
* value
* tb_text
keys in the report.
exc_info must be a tuple, but it can contain either live exception
information or simple strings (allowing exceptions that have been
serialised and received over the network to be reported).
"""
info = context.get("exc_info")
if info is None:
return
report["type"] = getattr(info[0], "__name__", info[0])
report["value"] = safe_unicode(info[1])
if isinstance(info[2], str):
tb_text = info[2]
else:
tb_text = "".join(map(safe_unicode, traceback.format_tb(info[2])))
report["tb_text"] = tb_text
def attach_hostname(report, context):
"""Add the machine's hostname to report in the 'hostname' key."""
report["hostname"] = socket.gethostname()
# hooks that are installed into Config objects by default.
default_hooks = [
attach_exc_info,
attach_date,
copy_reporter,
copy_topic,
copy_url,
attach_hostname,
]