180 lines
6.0 KiB
Python
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)
|