debian-python-raven/raven/handlers/logging.py

180 lines
6.0 KiB
Python

"""
raven.handlers.logging
~~~~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import absolute_import
from __future__ import print_function
import datetime
import logging
import sys
import traceback
from raven.base import Client
from raven.utils import six
from raven.utils.encoding import to_string
from raven.utils.stacks import iter_stack_frames, label_from_frame
RESERVED = frozenset((
'stack', 'name', 'module', 'funcName', 'args', 'msg', 'levelno',
'exc_text', 'exc_info', 'data', 'created', 'levelname', 'msecs',
'relativeCreated', 'tags',
))
class SentryHandler(logging.Handler, object):
def __init__(self, *args, **kwargs):
client = kwargs.get('client_cls', Client)
if len(args) == 1:
arg = args[0]
if isinstance(arg, six.string_types):
self.client = client(dsn=arg, **kwargs)
elif isinstance(arg, Client):
self.client = arg
else:
raise ValueError('The first argument to %s must be either a Client instance or a DSN, got %r instead.' % (
self.__class__.__name__,
arg,
))
elif 'client' in kwargs:
self.client = kwargs['client']
elif len(args) == 2 and not kwargs:
servers, key = args
self.client = client(servers=servers, key=key)
else:
self.client = client(*args, **kwargs)
logging.Handler.__init__(self, level=kwargs.get('level', logging.NOTSET))
def can_record(self, record):
return not (
record.name == 'raven' or
record.name.startswith(('sentry.errors', 'raven.'))
)
def emit(self, record):
try:
# Beware to python3 bug (see #10805) if exc_info is (None, None, None)
self.format(record)
if not self.can_record(record):
print(to_string(record.message), file=sys.stderr)
return
return self._emit(record)
except Exception:
if self.client.raise_send_errors:
raise
print("Top level Sentry exception caught - failed creating log record", file=sys.stderr)
print(to_string(record.msg), file=sys.stderr)
print(to_string(traceback.format_exc()), file=sys.stderr)
def _get_targetted_stack(self, stack):
# we might need to traverse this multiple times, so coerce it to a list
stack = list(stack)
frames = []
started = False
last_mod = ''
for item in stack:
if isinstance(item, (list, tuple)):
frame, lineno = item
else:
frame, lineno = item, item.f_lineno
if not started:
f_globals = getattr(frame, 'f_globals', {})
module_name = f_globals.get('__name__', '')
if ((last_mod and last_mod.startswith('logging'))
and not module_name.startswith('logging')):
started = True
else:
last_mod = module_name
continue
frames.append((frame, lineno))
# We failed to find a starting point
if not frames:
return stack
return frames
def _emit(self, record, **kwargs):
data = {}
extra = getattr(record, 'data', None)
if not isinstance(extra, dict):
if extra:
extra = {'data': extra}
else:
extra = {}
for k, v in six.iteritems(vars(record)):
if k in RESERVED:
continue
if k.startswith('_'):
continue
if '.' not in k and k not in ('culprit', 'server_name'):
extra[k] = v
else:
data[k] = v
stack = getattr(record, 'stack', None)
if stack is True:
stack = iter_stack_frames()
if stack:
stack = self._get_targetted_stack(stack)
date = datetime.datetime.utcfromtimestamp(record.created)
event_type = 'raven.events.Message'
handler_kwargs = {
'params': record.args,
}
try:
handler_kwargs['message'] = six.text_type(record.msg)
except UnicodeDecodeError:
# Handle binary strings where it should be unicode...
handler_kwargs['message'] = repr(record.msg)[1:-1]
try:
handler_kwargs['formatted'] = six.text_type(record.message)
except UnicodeDecodeError:
# Handle binary strings where it should be unicode...
handler_kwargs['formatted'] = repr(record.message)[1:-1]
# If there's no exception being processed, exc_info may be a 3-tuple of None
# http://docs.python.org/library/sys.html#sys.exc_info
if record.exc_info and all(record.exc_info):
# capture the standard message first so that we ensure
# the event is recorded as an exception, in addition to having our
# message interface attached
handler = self.client.get_handler(event_type)
data.update(handler.capture(**handler_kwargs))
event_type = 'raven.events.Exception'
handler_kwargs = {'exc_info': record.exc_info}
# HACK: discover a culprit when we normally couldn't
elif not (data.get('stacktrace') or data.get('culprit')) and (record.name or record.funcName):
culprit = label_from_frame({'module': record.name, 'function': record.funcName})
if culprit:
data['culprit'] = culprit
data['level'] = record.levelno
data['logger'] = record.name
if hasattr(record, 'tags'):
kwargs['tags'] = record.tags
kwargs.update(handler_kwargs)
return self.client.capture(
event_type, stack=stack, data=data,
extra=extra, date=date, **kwargs)