summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatt Robenolt <matt@ydekproductions.com>2014-03-08 10:44:35 -0800
committerMatt Robenolt <matt@ydekproductions.com>2014-03-08 10:44:35 -0800
commit843f94708e430b9771ab3ee879aad24709edfcce (patch)
tree23921bc2995d1781c49ec5c3dccb4b6cec1232b3
parent0cd35e5ba1246bb401674be6d8d5d6d37ab35ce9 (diff)
downloadraven-protocol6.tar.gz
Initial pass at v6 of the protocolprotocol6
-rw-r--r--raven/base.py197
-rw-r--r--raven/context.py25
-rw-r--r--raven/contrib/bottle/__init__.py17
-rw-r--r--raven/contrib/bottle/utils.py14
-rw-r--r--raven/contrib/django/__init__.py4
-rw-r--r--raven/contrib/django/client.py102
-rw-r--r--raven/contrib/django/events.py42
-rw-r--r--raven/contrib/django/handlers.py7
-rw-r--r--raven/contrib/django/middleware/__init__.py19
-rw-r--r--raven/contrib/django/models.py4
-rw-r--r--raven/contrib/django/utils.py2
-rw-r--r--raven/contrib/flask.py13
-rw-r--r--raven/contrib/tornado/__init__.py4
-rw-r--r--raven/events.py101
-rw-r--r--raven/handlers/logbook.py18
-rw-r--r--raven/handlers/logging.py19
-rw-r--r--raven/middleware.py5
-rw-r--r--raven/utils/json.py2
-rw-r--r--tests/base/tests.py26
-rw-r--r--tests/contrib/bottle/tests.py36
-rw-r--r--tests/contrib/django/tests.py161
-rw-r--r--tests/contrib/flask/tests.py66
-rw-r--r--tests/contrib/tornado/tests.py12
-rw-r--r--tests/contrib/webpy/tests.py9
-rw-r--r--tests/handlers/logbook/tests.py42
-rw-r--r--tests/handlers/logging/tests.py49
-rw-r--r--tests/middleware/tests.py14
-rw-r--r--tests/transport/tests.py13
-rw-r--r--tests/utils/json/tests.py2
29 files changed, 639 insertions, 386 deletions
diff --git a/raven/base.py b/raven/base.py
index b06e8fd..0331fd6 100644
--- a/raven/base.py
+++ b/raven/base.py
@@ -14,18 +14,20 @@ import logging
import os
import sys
import time
-import uuid
import warnings
+from uuid import uuid4
from datetime import datetime
+from threading import local
import raven
from raven.conf import defaults
-from raven.context import Context
+from raven.context import Context, Timeline
+from raven.events import get_handler
from raven.utils import six, json, get_versions, get_auth_header, merge_dicts
from raven.utils.encoding import to_unicode
from raven.utils.serializer import transform
-from raven.utils.stacks import get_stack_info, iter_stack_frames, get_culprit
+from raven.utils.stacks import get_culprit
from raven.utils.urlparse import urlparse
from raven.utils.compat import HTTPError
from raven.transport.registry import TransportRegistry, default_transports
@@ -33,6 +35,7 @@ from raven.transport.registry import TransportRegistry, default_transports
__all__ = ('Client',)
PLATFORM_NAME = 'python'
+DEFAULT_ENVIRONMENT = 'production'
# singleton for the client
Raven = None
@@ -109,7 +112,7 @@ class Client(object):
>>> print "Exception caught; reference is %s" % ident
"""
logger = logging.getLogger('raven')
- protocol_version = '4'
+ protocol_version = '6'
_registry = TransportRegistry(transports=default_transports)
@@ -126,6 +129,7 @@ class Client(object):
self.logger = logging.getLogger(
'%s.%s' % (cls.__module__, cls.__name__))
self.error_logger = logging.getLogger('sentry.errors')
+ self._local = local()
if dsn is None and os.environ.get('SENTRY_DSN'):
msg = "Configuring Raven from environment variable 'SENTRY_DSN'"
@@ -192,6 +196,19 @@ class Client(object):
Raven = self
self._context = Context()
+ self._timeline = Timeline()
+
+ @property
+ def transaction(self):
+ try:
+ return self._local.transaction
+ except AttributeError:
+ self.start_transaction()
+ return self._local.transaction
+
+ def reset(self):
+ self._context.clear()
+ self._timeline.clear()
@classmethod
def register_scheme(cls, scheme, transport_class):
@@ -224,7 +241,10 @@ class Client(object):
return '$'.join(result)
def get_handler(self, name):
- return self.module_cache[name](self)
+ try:
+ return get_handler(name)(self)
+ except KeyError:
+ return None
def _get_public_dsn(self):
url = urlparse(self.servers[0])
@@ -251,7 +271,7 @@ class Client(object):
return url
return '%s:%s' % (scheme, url)
- def build_msg(self, event_type, data=None, date=None,
+ def build_msg(self, data=None, date=None,
time_spent=None, extra=None, stack=None, public_key=None,
tags=None, **kwargs):
"""
@@ -260,76 +280,41 @@ class Client(object):
The result of ``build_msg`` should be a standardized dict, with
all default values available.
"""
-
- # create ID client-side so that it can be passed to application
- event_id = uuid.uuid4().hex
-
data = merge_dicts(self.context.data, data)
+ data.setdefault('events', list(self._timeline))
data.setdefault('tags', {})
data.setdefault('extra', {})
- data.setdefault('level', logging.ERROR)
if stack is None:
stack = self.auto_log_stacks
- if '.' not in event_type:
- # Assume it's a builtin
- event_type = 'raven.events.%s' % event_type
+ events = []
+ for event in data['events']:
+ event_type = event.get('type', 'message')
+ handler = self.get_handler(event_type)
+ log_stack = event.pop('stack', stack)
+ if handler is None:
+ events.append(event)
+ else:
+ events.append(handler.handle(stack=log_stack, **event))
+
+ data['events'] = events
- handler = self.get_handler(event_type)
- result = handler.capture(**kwargs)
+ # Last event is considered significant
+ if not len(events):
+ raise Exception('No events to be sent')
+
+ significant_event = events[-1]
# data (explicit) culprit takes over auto event detection
- culprit = result.pop('culprit', None)
+ culprit = significant_event.pop('culprit', None)
if data.get('culprit'):
culprit = data['culprit']
- for k, v in six.iteritems(result):
- if k not in data:
- data[k] = v
-
- if stack and 'sentry.interfaces.Stacktrace' not in data:
- if stack is True:
- frames = iter_stack_frames()
-
- else:
- frames = stack
-
- data.update({
- 'sentry.interfaces.Stacktrace': {
- 'frames': get_stack_info(frames,
- transformer=self.transform)
- },
- })
-
- if 'sentry.interfaces.Stacktrace' in data:
- if self.include_paths:
- for frame in data['sentry.interfaces.Stacktrace']['frames']:
- if frame.get('in_app') is not None:
- continue
-
- path = frame.get('module')
- if not path:
- continue
-
- if path.startswith('raven.'):
- frame['in_app'] = False
- else:
- frame['in_app'] = (
- any(path.startswith(x) for x in self.include_paths)
- and not
- any(path.startswith(x) for x in self.exclude_paths)
- )
-
if not culprit:
- if 'sentry.interfaces.Stacktrace' in data:
- culprit = get_culprit(data['sentry.interfaces.Stacktrace']['frames'])
- elif data.get('sentry.interfaces.Exception', {}).get('stacktrace'):
- culprit = get_culprit(data['sentry.interfaces.Exception']['stacktrace']['frames'])
-
- if not data.get('level'):
- data['level'] = kwargs.get('level') or logging.ERROR
+ if significant_event['type'] == 'exception' and 'stacktrace' in significant_event:
+ culprit = get_culprit(significant_event['stacktrace']['frames'])
if not data.get('server_name'):
data['server_name'] = self.name
@@ -353,7 +338,7 @@ class Client(object):
data.update(processor.process(data))
if 'message' not in data:
- data['message'] = handler.to_string(data)
+ data['message'] = handler.to_string(significant_event)
# tags should only be key=>u'value'
for key, value in six.iteritems(data['tags']):
@@ -363,12 +348,16 @@ class Client(object):
for k, v in six.iteritems(data['extra']):
data['extra'][k] = self.transform(v)
+ # create ID client-side so that it can be passed to application
+ data.setdefault('id', uuid4().hex)
+ data.setdefault('transaction', self.transaction)
+
# It's important date is added **after** we serialize
data.setdefault('project', self.project)
data.setdefault('timestamp', date or datetime.utcnow())
data.setdefault('time_spent', time_spent)
- data.setdefault('event_id', event_id)
data.setdefault('platform', PLATFORM_NAME)
+ data.setdefault('environment', DEFAULT_ENVIRONMENT)
return data
@@ -391,6 +380,61 @@ class Client(object):
"""
return self._context
+ def pre_add_action(self, action, **kwargs):
+ """
+ Hook for subclasses to do something before adding an action to
+ the timeline.
+ """
+ pass
+
+ def add_action(self, action, **kwargs):
+ """
+ Appends an arbitrary action to the timeline.
+
+ >>> client.add_event({'message': 'sup sentry'})
+ """
+ # Pop off tags and extra so they don't get carried through
+ kwargs.pop('tags', None)
+ kwargs.pop('extra', None)
+
+ action.setdefault('type', 'message')
+ action.setdefault('timestamp', datetime.utcnow())
+ action.update(**kwargs)
+ self.pre_add_action(action)
+ self._timeline.append(action)
+
+ def add_http(self, data, **kwargs):
+ """
+ Appends an http action to the timeline.
+
+ >>> client.add_http({'url': 'http://example.com'})
+ """
+ data['type'] = 'http_request'
+ data.setdefault('method', 'GET')
+ kwargs['stack'] = False # Don't capture stack for http
+ if len(self._timeline) and self._timeline[0]['type'] == 'http_request':
+ self._timeline[0].update(data)
+ else:
+ self.add_action(data, **kwargs)
+
+ def add_message(self, message, **kwargs):
+ """
+ Appends a message event to the timeline.
+
+ >>> client.add_message('sup sentry')
+ """
+ if isinstance(message, six.string_types):
+ data = {'message': message}
+ data['type'] = 'message'
+ self.add_action(data, **kwargs)
+
+ def add_exception(self, exc_info=None, **kwargs):
+ data = {
+ 'type': 'exception',
+ 'exc_info': exc_info,
+ }
+ self.add_action(data, **kwargs)
+
def user_context(self, data):
"""
Update the user context for future events.
@@ -398,7 +442,7 @@ class Client(object):
>>> client.user_context({'email': 'foo@example.com'})
"""
return self.context.merge({
- 'sentry.interfaces.User': data,
+ 'user': data,
})
def http_context(self, data, **kwargs):
@@ -407,9 +451,7 @@ class Client(object):
>>> client.http_context({'url': 'http://example.com'})
"""
- return self.context.merge({
- 'sentry.interfaces.Http': data,
- })
+ return self.add_http(data, **kwargs)
def extra_context(self, data, **kwargs):
"""
@@ -431,7 +473,10 @@ class Client(object):
'tags': data,
})
- def capture(self, event_type, data=None, date=None, time_spent=None,
+ def start_transaction(self, tx_id=None):
+ self._local.transaction = tx_id or uuid4().hex
+
+ def capture(self, data=None, date=None, time_spent=None,
extra=None, stack=None, tags=None, **kwargs):
"""
Captures and processes an event and pipes it off to SentryClient.send.
@@ -468,9 +513,6 @@ class Client(object):
>>> }
>>> }
- :param event_type: the module path to the Event class. Builtins can use
- shorthand class notation and exclude the full module
- path.
:param data: the data base, useful for specifying structured data
interfaces. Any key which contains a '.' will be
assumed to be a data interface.
@@ -488,12 +530,14 @@ class Client(object):
return
data = self.build_msg(
- event_type, data, date, time_spent, extra, stack, tags=tags,
+ data, date, time_spent, extra, stack, tags=tags,
**kwargs)
self.send(**data)
+ # timeline starts back at 0
+ self._timeline.clear()
- return (data.get('event_id'),)
+ return (data.get('id'),)
def _get_log_message(self, data):
# decode message so we can show the actual event
@@ -610,7 +654,8 @@ class Client(object):
>>> client.captureMessage('My event just happened!')
"""
- return self.capture('raven.events.Message', message=message, **kwargs)
+ self.add_message(message, **kwargs)
+ return self.capture(**kwargs)
def captureException(self, exc_info=None, **kwargs):
"""
@@ -628,8 +673,8 @@ class Client(object):
``kwargs`` are passed through to ``.capture``.
"""
- return self.capture(
- 'raven.events.Exception', exc_info=exc_info, **kwargs)
+ self.add_exception(exc_info=exc_info)
+ return self.capture(**kwargs)
def captureQuery(self, query, params=(), engine=None, **kwargs):
"""
diff --git a/raven/context.py b/raven/context.py
index 5d93939..701e93b 100644
--- a/raven/context.py
+++ b/raven/context.py
@@ -12,6 +12,8 @@ from threading import local
from raven.utils import six
+TAGS_EXTRA = set(('tags', 'extra'))
+
class Context(local, Mapping, Iterable):
"""
@@ -42,8 +44,9 @@ class Context(local, Mapping, Iterable):
def merge(self, data):
d = self.data
+
for key, value in six.iteritems(data):
- if key in ('tags', 'extra'):
+ if key in TAGS_EXTRA:
d.setdefault(key, {})
for t_key, t_value in six.iteritems(value):
d[key][t_key] = t_value
@@ -58,3 +61,23 @@ class Context(local, Mapping, Iterable):
def clear(self):
self.data = {}
+
+
+class Timeline(local):
+ def __init__(self):
+ self.clear()
+
+ def __iter__(self):
+ return iter(self.timeline)
+
+ def __len__(self):
+ return len(self.timeline)
+
+ def __getitem__(self, index):
+ return self.timeline[index]
+
+ def append(self, what):
+ self.timeline.append(what)
+
+ def clear(self):
+ self.timeline = []
diff --git a/raven/contrib/bottle/__init__.py b/raven/contrib/bottle/__init__.py
index 90e56f2..db47175 100644
--- a/raven/contrib/bottle/__init__.py
+++ b/raven/contrib/bottle/__init__.py
@@ -48,7 +48,6 @@ class Sentry(object):
def handle_exception(self, *args, **kwargs):
self.client.captureException(
exc_info=kwargs.get('exc_info'),
- data=get_data_from_request(request),
extra={
'app': self.app,
},
@@ -60,6 +59,8 @@ class Sentry(object):
self.handle_exception(exc_info=exc_info)
return start_response(status, headers, exc_info)
+ self.client.add_http(get_data_from_request(request))
+
try:
return self.app(environ, session_start_response)
# catch ANY exception that goes through...
@@ -69,22 +70,8 @@ class Sentry(object):
def captureException(self, *args, **kwargs):
assert self.client, 'captureException called before application configured'
- data = kwargs.get('data')
- if data is None:
- try:
- kwargs['data'] = get_data_from_request(request)
- except RuntimeError:
- # app is probably not configured yet
- pass
return self.client.captureException(*args, **kwargs)
def captureMessage(self, *args, **kwargs):
assert self.client, 'captureMessage called before application configured'
- data = kwargs.get('data')
- if data is None:
- try:
- kwargs['data'] = get_data_from_request(request)
- except RuntimeError:
- # app is probably not configured yet
- pass
return self.client.captureMessage(*args, **kwargs)
diff --git a/raven/contrib/bottle/utils.py b/raven/contrib/bottle/utils.py
index 9c75577..2beff63 100644
--- a/raven/contrib/bottle/utils.py
+++ b/raven/contrib/bottle/utils.py
@@ -26,14 +26,12 @@ def get_data_from_request(request):
formdata = {}
data = {
- 'sentry.interfaces.Http': {
- 'url': '%s://%s%s' % (urlparts.scheme, urlparts.netloc, urlparts.path),
- 'query_string': urlparts.query,
- 'method': request.method,
- 'data': formdata,
- 'headers': dict(get_headers(request.environ)),
- 'env': dict(get_environ(request.environ)),
- }
+ 'url': '%s://%s%s' % (urlparts.scheme, urlparts.netloc, urlparts.path),
+ 'query_string': urlparts.query,
+ 'method': request.method,
+ 'data': formdata,
+ 'headers': dict(get_headers(request.environ)),
+ 'env': dict(get_environ(request.environ)),
}
return data
diff --git a/raven/contrib/django/__init__.py b/raven/contrib/django/__init__.py
index 7184f94..7dec9e8 100644
--- a/raven/contrib/django/__init__.py
+++ b/raven/contrib/django/__init__.py
@@ -7,4 +7,8 @@ raven.contrib.django
"""
from __future__ import absolute_import
+# Importing just to register the Django specific Exception handler
+from .events import Exception # NOQA
+del Exception # NOQA
+
from .client import DjangoClient # NOQA
diff --git a/raven/contrib/django/client.py b/raven/contrib/django/client.py
index 8ae98c8..1cd2b01 100644
--- a/raven/contrib/django/client.py
+++ b/raven/contrib/django/client.py
@@ -1,6 +1,6 @@
"""
raven.contrib.django.client
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
@@ -13,20 +13,27 @@ import logging
from django.conf import settings
from django.core.exceptions import SuspiciousOperation
from django.http import HttpRequest
-from django.template import TemplateSyntaxError
-from django.template.loader import LoaderOrigin
from raven.base import Client
-from raven.contrib.django.utils import get_data_from_template, get_host
+from raven.contrib.django.utils import get_host
from raven.contrib.django.middleware import SentryLogMiddleware
from raven.utils.wsgi import get_headers, get_environ
+try:
+ from django.contrib.auth.models import AbstractBaseUser as BaseUser
+except ImportError:
+ from django.contrib.auth.models import User as BaseUser # NOQA
+
__all__ = ('DjangoClient',)
class DjangoClient(Client):
logger = logging.getLogger('sentry.errors.client.django')
+ def __init__(self, *args, **kwargs):
+ super(DjangoClient, self).__init__(*args, **kwargs)
+ self._local.request_added = False
+
def is_enabled(self):
return bool(self.servers or 'sentry' in settings.INSTALLED_APPS)
@@ -49,17 +56,14 @@ class DjangoClient(Client):
return user_info
- def get_data_from_request(self, request):
- try:
- from django.contrib.auth.models import AbstractBaseUser as BaseUser
- except ImportError:
- from django.contrib.auth.models import User as BaseUser # NOQA
+ def add_request(self, request):
+ if hasattr(request, 'user') and isinstance(request.user, BaseUser):
+ self.user_context(self.get_user_info(request.user))
+ return self.add_http(self.get_data_from_request(request))
+ def get_data_from_request(self, request):
result = {}
- if hasattr(request, 'user') and isinstance(request.user, BaseUser):
- result['sentry.interfaces.User'] = self.get_user_info(request.user)
-
try:
uri = request.build_absolute_uri()
except SuspiciousOperation:
@@ -75,13 +79,13 @@ class DjangoClient(Client):
if request.method != 'GET':
try:
data = request.body
- except:
+ except Exception:
try:
data = request.raw_post_data
except Exception:
# assume we had a partial read.
try:
- data = request.POST or '<unavailable>'
+ data = dict(request.POST) or '<unavailable>'
except Exception:
data = '<unavailable>'
else:
@@ -90,15 +94,13 @@ class DjangoClient(Client):
environ = request.META
result.update({
- 'sentry.interfaces.Http': {
- 'method': request.method,
- 'url': uri,
- 'query_string': request.META.get('QUERY_STRING'),
- 'data': data,
- 'cookies': dict(request.COOKIES),
- 'headers': dict(get_headers(environ)),
- 'env': dict(get_environ(environ)),
- }
+ 'method': request.method,
+ 'url': uri,
+ 'query_string': request.META.get('QUERY_STRING'),
+ 'data': data,
+ 'cookies': dict(request.COOKIES),
+ 'headers': dict(get_headers(environ)),
+ 'env': dict(get_environ(environ)),
})
return result
@@ -106,19 +108,16 @@ class DjangoClient(Client):
def build_msg(self, *args, **kwargs):
data = super(DjangoClient, self).build_msg(*args, **kwargs)
- stacks = (
- data.get('sentry.interfaces.Stacktrace'),
- data.get('sentry.interfaces.Exception', {}).get('stacktrace'),
- )
-
- for stacktrace in filter(bool, stacks):
- for frame in stacktrace['frames']:
- module = frame.get('module')
- if not module:
- continue
+ for action in data['events']:
+ if action.get('type', 'message') == 'exception' and 'stacktrace' in action:
+ stacktrace = action['stacktrace']
+ for frame in stacktrace['frames']:
+ module = frame.get('module')
+ if not module:
+ continue
- if module.startswith('django.'):
- frame['in_app'] = False
+ if module[:7] == 'django.':
+ frame['in_app'] = False
if not self.site and 'django.contrib.sites' in settings.INSTALLED_APPS:
try:
@@ -132,7 +131,15 @@ class DjangoClient(Client):
return data
- def capture(self, event_type, request=None, **kwargs):
+ def pre_add_action(self, action, **kwargs):
+ request = action.pop('request', None)
+ if request is None:
+ request = getattr(SentryLogMiddleware.thread, 'request', None)
+
+ if isinstance(request, HttpRequest):
+ self.add_request(request)
+
+ def capture(self, request=None, **kwargs):
if 'data' not in kwargs:
kwargs['data'] = data = {}
else:
@@ -141,24 +148,9 @@ class DjangoClient(Client):
if request is None:
request = getattr(SentryLogMiddleware.thread, 'request', None)
- is_http_request = isinstance(request, HttpRequest)
- if is_http_request:
- data.update(self.get_data_from_request(request))
-
- if kwargs.get('exc_info'):
- exc_value = kwargs['exc_info'][1]
- # As of r16833 (Django) all exceptions may contain a ``django_template_source`` attribute (rather than the
- # legacy ``TemplateSyntaxError.source`` check) which describes template information.
- if hasattr(exc_value, 'django_template_source') or ((isinstance(exc_value, TemplateSyntaxError) and
- isinstance(getattr(exc_value, 'source', None), (tuple, list)) and isinstance(exc_value.source[0], LoaderOrigin))):
- source = getattr(exc_value, 'django_template_source', getattr(exc_value, 'source', None))
- if source is None:
- self.logger.info('Unable to get template source from exception')
- data.update(get_data_from_template(source))
+ result = super(DjangoClient, self).capture(**kwargs)
- result = super(DjangoClient, self).capture(event_type, **kwargs)
-
- if is_http_request and result:
+ if isinstance(request, HttpRequest) and result:
# attach the sentry object to the request
request.sentry = {
'project_id': data.get('project', self.project),
@@ -188,3 +180,7 @@ class DjangoClient(Client):
def send_integrated(self, kwargs):
from sentry.models import Group
return Group.objects.from_kwargs(**kwargs)
+
+ def reset(self):
+ self._local.request_added = False
+ super(DjangoClient, self).reset()
diff --git a/raven/contrib/django/events.py b/raven/contrib/django/events.py
new file mode 100644
index 0000000..55cd787
--- /dev/null
+++ b/raven/contrib/django/events.py
@@ -0,0 +1,42 @@
+"""
+raven.contrib.django.events
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+:copyright: (c) 2010-2014 by the Sentry Team, see AUTHORS for more details.
+:license: BSD, see LICENSE for more details.
+"""
+from __future__ import absolute_import
+
+import sys
+
+from django.template import TemplateSyntaxError
+from django.template.loader import LoaderOrigin
+
+from raven.events import register, Exception as BaseException
+from raven.contrib.django.utils import get_data_from_template
+
+__all__ = ('Exception',)
+
+
+@register
+class Exception(BaseException):
+ def capture(self, exc_info=None, stack=None, **kwargs):
+ data = super(Exception, self).capture(exc_info, stack, **kwargs)
+ if not exc_info or exc_info is True:
+ exc_info = sys.exc_info()
+
+ exc_value = exc_info[1]
+ # As of r16833 (Django) all exceptions may contain a ``django_template_source`` attribute (rather than the
+ # legacy ``TemplateSyntaxError.source`` check) which describes template information.
+ if (
+ hasattr(exc_value, 'django_template_source') or (
+ (isinstance(exc_value, TemplateSyntaxError) and
+ isinstance(getattr(exc_value, 'source', None), (tuple, list)) and
+ isinstance(exc_value.source[0], LoaderOrigin))
+ )
+ ):
+ source = getattr(exc_value, 'django_template_source', getattr(exc_value, 'source', None))
+ if source is None:
+ self.logger.info('Unable to get template source from exception')
+ data.update(get_data_from_template(source))
+ return data
diff --git a/raven/contrib/django/handlers.py b/raven/contrib/django/handlers.py
index 504fbc1..fcbd9a7 100644
--- a/raven/contrib/django/handlers.py
+++ b/raven/contrib/django/handlers.py
@@ -24,6 +24,7 @@ class SentryHandler(BaseSentryHandler):
client = property(_get_client)
def _emit(self, record):
- request = getattr(record, 'request', None)
-
- return super(SentryHandler, self)._emit(record, request=request)
+ request = getattr(record, 'request', getattr(record, 'extra', {}).get('request'))
+ if request is not None:
+ self.client.add_request(request)
+ return super(SentryHandler, self)._emit(record)
diff --git a/raven/contrib/django/middleware/__init__.py b/raven/contrib/django/middleware/__init__.py
index 9be15f1..1326e07 100644
--- a/raven/contrib/django/middleware/__init__.py
+++ b/raven/contrib/django/middleware/__init__.py
@@ -9,9 +9,11 @@ raven.contrib.django.middleware
from __future__ import absolute_import
import threading
-import logging
from django.conf import settings
+from raven.contrib.django.models import client
+
+IGNORABLE_404_URLS = getattr(settings, 'IGNORABLE_404_URLS', ())
def is_ignorable_404(uri):
@@ -20,22 +22,22 @@ def is_ignorable_404(uri):
"""
return any(
pattern.search(uri)
- for pattern in getattr(settings, 'IGNORABLE_404_URLS', ())
+ for pattern in IGNORABLE_404_URLS
)
class Sentry404CatchMiddleware(object):
- def process_response(self, request, response):
- from raven.contrib.django.models import client
+ def process_request(self, request):
+ client.add_request(request)
+ def process_response(self, request, response):
if response.status_code != 404 or is_ignorable_404(request.get_full_path()) or not client.is_enabled():
return response
- data = client.get_data_from_request(request)
- data.update({
- 'level': logging.INFO,
+ # data = client.get_data_from_request(request)
+ data = {
'logger': 'http404',
- })
+ }
result = client.captureMessage(message='Page Not Found: %s' % request.build_absolute_uri(), data=data)
request.sentry = {
'project_id': data.get('project', client.project),
@@ -64,3 +66,4 @@ class SentryLogMiddleware(object):
def process_request(self, request):
self.thread.request = request
+ client.add_request(request)
diff --git a/raven/contrib/django/models.py b/raven/contrib/django/models.py
index a41ee57..cfc84f5 100644
--- a/raven/contrib/django/models.py
+++ b/raven/contrib/django/models.py
@@ -160,8 +160,10 @@ def sentry_exception_handler(request=None, **kwargs):
exc_info=sys.exc_info())
return
+ if request is not None:
+ client.add_request(request)
try:
- client.captureException(exc_info=sys.exc_info(), request=request)
+ client.captureException(exc_info=sys.exc_info())
except Exception as exc:
try:
logger.exception('Unable to process log entry: %s' % (exc,))
diff --git a/raven/contrib/django/utils.py b/raven/contrib/django/utils.py
index 39d7c8a..a619206 100644
--- a/raven/contrib/django/utils.py
+++ b/raven/contrib/django/utils.py
@@ -41,7 +41,7 @@ def get_data_from_template(source):
context_line = source_lines[lineno]
return {
- 'sentry.interfaces.Template': {
+ 'template': {
'filename': origin.loadname,
'abs_path': origin.name,
'pre_context': pre_context,
diff --git a/raven/contrib/flask.py b/raven/contrib/flask.py
index 38c89b9..18b102c 100644
--- a/raven/contrib/flask.py
+++ b/raven/contrib/flask.py
@@ -17,7 +17,6 @@ else:
import sys
import os
-import logging
from flask import request, current_app
from flask.signals import got_request_exception
@@ -68,7 +67,7 @@ class Sentry(object):
Automatically configure logging::
- >>> sentry = Sentry(app, logging=True, level=logging.ERROR)
+ >>> sentry = Sentry(app, logging=True)
Capture an exception::
@@ -90,13 +89,11 @@ class Sentry(object):
- Capture information from Flask-Login (if available).
"""
def __init__(self, app=None, client=None, client_cls=Client, dsn=None,
- logging=False, level=logging.NOTSET, wrap_wsgi=True,
- register_signal=True):
+ logging=False, wrap_wsgi=True, register_signal=True):
self.dsn = dsn
self.logging = logging
self.client_cls = client_cls
self.client = client
- self.level = level
self.wrap_wsgi = wrap_wsgi
self.register_signal = register_signal
@@ -165,13 +162,13 @@ class Sentry(object):
'url': '%s://%s%s' % (urlparts.scheme, urlparts.netloc, urlparts.path),
'query_string': urlparts.query,
'method': request.method,
- 'data': formdata,
+ 'data': dict(formdata),
'headers': dict(get_headers(request.environ)),
'env': dict(get_environ(request.environ)),
}
def before_request(self, *args, **kwargs):
- self.client.http_context(self.get_http_info(request))
+ self.client.add_http(self.get_http_info(request))
self.client.user_context(self.get_user_info(request))
def init_app(self, app, dsn=None):
@@ -182,7 +179,7 @@ class Sentry(object):
self.client = make_client(self.client_cls, app, self.dsn)
if self.logging:
- setup_logging(SentryHandler(self.client, level=self.level))
+ setup_logging(SentryHandler(self.client))
if self.wrap_wsgi:
app.wsgi_app = SentryMiddleware(app.wsgi_app, self.client)
diff --git a/raven/contrib/tornado/__init__.py b/raven/contrib/tornado/__init__.py
index 0ca9355..0320606 100644
--- a/raven/contrib/tornado/__init__.py
+++ b/raven/contrib/tornado/__init__.py
@@ -39,7 +39,7 @@ class AsyncSentryClient(Client):
self.send(callback=kwargs.get('callback', None), **data)
- return (data['event_id'],)
+ return (data['id'],)
def send(self, auth_header=None, callback=None, **data):
"""
@@ -202,7 +202,7 @@ class SentryMixin(object):
Truth calue testing
"""
return {
- 'sentry.interfaces.User': {
+ 'user': {
'is_authenticated': True if self.get_current_user() else False
}
}
diff --git a/raven/events.py b/raven/events.py
index b8c8421..e291f16 100644
--- a/raven/events.py
+++ b/raven/events.py
@@ -11,9 +11,23 @@ import logging
import sys
from raven.utils.encoding import to_unicode
-from raven.utils.stacks import get_stack_info, iter_traceback_frames
+from raven.utils.stacks import (
+ get_stack_info, iter_traceback_frames, iter_stack_frames,
+)
-__all__ = ('BaseEvent', 'Exception', 'Message', 'Query')
+
+__all__ = ('BaseEvent', 'Exception', 'Message', 'Query', 'get_handler')
+
+handlers = {}
+
+
+def register(cls):
+ handlers[cls.__name__.lower()] = cls
+ return cls
+
+
+def get_handler(name):
+ return handlers[name]
class BaseEvent(object):
@@ -24,14 +38,58 @@ class BaseEvent(object):
def to_string(self, data):
raise NotImplementedError
+ def handle(self, stack=None, **kwargs):
+ data = self.capture(**kwargs)
+ data.setdefault('timestamp', kwargs['timestamp']),
+ if 'data' in kwargs:
+ data.update(**kwargs['data'])
+
+ if stack and 'stacktrace' not in kwargs:
+ if stack is True:
+ frames = iter_stack_frames()
+ else:
+ frames = stack
+
+ data.update({
+ 'stacktrace': {
+ 'frames': get_stack_info(
+ frames, transformer=self.transform)
+ },
+ })
+
+ if 'stacktrace' in kwargs:
+ if self.client.include_paths:
+ for frame in kwargs['stacktrace']['frames']:
+ if frame.get('in_app') is not None:
+ continue
+
+ module = frame.get('module')
+ abs_path = frame.get('abs_path')
+
+ if module and module[:6] == 'raven.':
+ frame['in_app'] = False
+ elif abs_path and '/site-packages/' in abs_path:
+ frame['in_app'] = False
+ elif module:
+ frame['in_app'] = (
+ any(module.startswith(x) for x in self.client.include_paths)
+ and not
+ any(module.startswith(x) for x in self.client.exclude_paths)
+ )
+
+ data.update({
+ 'stacktrace': kwargs['stacktrace'],
+ })
+ return data
+
def capture(self, **kwargs):
- return {
- }
+ raise NotImplementedError
def transform(self, value):
return self.client.transform(value)
+@register
class Exception(BaseEvent):
"""
Exceptions store the following metadata:
@@ -43,12 +101,11 @@ class Exception(BaseEvent):
"""
def to_string(self, data):
- exc = data['sentry.interfaces.Exception']
- if exc['value']:
- return '%s: %s' % (exc['type'], exc['value'])
- return exc['type']
+ if data['value']:
+ return '%s: %s' % (data['exc_type'], data['value'])
+ return data['exc_type']
- def capture(self, exc_info=None, **kwargs):
+ def capture(self, exc_info=None, stack=None, **kwargs):
if not exc_info or exc_info is True:
exc_info = sys.exc_info()
@@ -68,14 +125,12 @@ class Exception(BaseEvent):
exc_type = getattr(exc_type, '__name__', '<unknown>')
return {
- 'level': kwargs.get('level', logging.ERROR),
- 'sentry.interfaces.Exception': {
- 'value': to_unicode(exc_value),
- 'type': str(exc_type),
- 'module': to_unicode(exc_module),
- 'stacktrace': {
- 'frames': frames
- }
+ 'type': 'exception',
+ 'value': to_unicode(exc_value),
+ 'exc_type': str(exc_type),
+ 'module': to_unicode(exc_module),
+ 'stacktrace': {
+ 'frames': frames
},
}
finally:
@@ -85,6 +140,7 @@ class Exception(BaseEvent):
self.logger.exception(e)
+@register
class Message(BaseEvent):
"""
Messages store the following metadata:
@@ -92,19 +148,22 @@ class Message(BaseEvent):
- message: 'My message from %s about %s'
- params: ('foo', 'bar')
"""
+ def to_string(self, data):
+ return data['message']
+
def capture(self, message, params=(), formatted=None, **kwargs):
message = to_unicode(message)
data = {
- 'sentry.interfaces.Message': {
- 'message': message,
- 'params': self.transform(params),
- },
+ 'type': 'message',
+ 'message': message,
+ 'params': self.transform(params),
}
if 'message' not in data:
data['message'] = formatted or message
return data
+@register
class Query(BaseEvent):
"""
Messages store the following metadata:
diff --git a/raven/handlers/logbook.py b/raven/handlers/logbook.py
index bb79f9a..eb2513b 100644
--- a/raven/handlers/logbook.py
+++ b/raven/handlers/logbook.py
@@ -59,11 +59,11 @@ class SentryHandler(logbook.Handler):
def _emit(self, record):
data = {
- 'level': logbook.get_level_name(record.level).lower(),
'logger': record.channel,
}
- event_type = 'raven.events.Message'
+ if 'tags' in record.kwargs:
+ data['tags'] = record.kwargs['tags']
handler_kwargs = {
'message': record.msg,
@@ -71,17 +71,14 @@ class SentryHandler(logbook.Handler):
'formatted': self.format(record),
}
- if 'tags' in record.kwargs:
- handler_kwargs['tags'] = record.kwargs['tags']
+ data['message'] = handler_kwargs['formatted']
+
+ self.client.add_message(**handler_kwargs)
# 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 is True or (record.exc_info and all(record.exc_info)):
- 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
+ self.client.add_exception(exc_info=record.exc_info)
extra = {
'lineno': record.lineno,
@@ -92,8 +89,7 @@ class SentryHandler(logbook.Handler):
}
extra.update(record.extra)
- return self.client.capture(event_type,
+ return self.client.capture(
data=data,
extra=extra,
- **handler_kwargs
)
diff --git a/raven/handlers/logging.py b/raven/handlers/logging.py
index 827e2f7..4d84f91 100644
--- a/raven/handlers/logging.py
+++ b/raven/handlers/logging.py
@@ -130,7 +130,6 @@ class SentryHandler(logging.Handler, object):
stack = self._get_targetted_stack(stack)
date = datetime.datetime.utcfromtimestamp(record.created)
- event_type = 'raven.events.Message'
handler_kwargs = {
'params': record.args,
}
@@ -146,25 +145,21 @@ class SentryHandler(logging.Handler, object):
# Handle binary strings where it should be unicode...
handler_kwargs['formatted'] = repr(record.message)[1:-1]
+ data['message'] = handler_kwargs['formatted']
+
+ self.client.add_message(**handler_kwargs)
+
# 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}
+ self.client.add_exception(exc_info=record.exc_info)
# HACK: discover a culprit when we normally couldn't
- elif not (data.get('sentry.interfaces.Stacktrace') or data.get('culprit')) and (record.name or record.funcName):
+ elif not 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'):
@@ -173,5 +168,5 @@ class SentryHandler(logging.Handler, object):
kwargs.update(handler_kwargs)
return self.client.capture(
- event_type, stack=stack, data=data,
+ stack=stack, data=data,
extra=extra, date=date, **kwargs)
diff --git a/raven/middleware.py b/raven/middleware.py
index 588bd6f..e4ea1d0 100644
--- a/raven/middleware.py
+++ b/raven/middleware.py
@@ -29,7 +29,8 @@ class Sentry(object):
def __call__(self, environ, start_response):
# TODO(dcramer): ideally this is lazy, but the context helpers must
# support callbacks first
- self.client.http_context(self.get_http_context(environ))
+ self.client.start_transaction()
+ self.client.add_http(self.get_http_context(environ))
try:
iterable = self.application(environ, start_response)
@@ -51,7 +52,7 @@ class Sentry(object):
iterable.close()
except Exception:
self.handle_exception(environ)
- self.client.context.clear()
+ self.client.reset()
def get_http_context(self, environ):
return {
diff --git a/raven/utils/json.py b/raven/utils/json.py
index 57f337b..f52f940 100644
--- a/raven/utils/json.py
+++ b/raven/utils/json.py
@@ -26,7 +26,7 @@ except AttributeError:
class BetterJSONEncoder(json.JSONEncoder):
ENCODER_BY_TYPE = {
uuid.UUID: lambda o: o.hex,
- datetime.datetime: lambda o: o.strftime('%Y-%m-%dT%H:%M:%SZ'),
+ datetime.datetime: lambda o: o.strftime('%Y-%m-%dT%H:%M:%S.%fZ'),
set: list,
frozenset: list,
bytes: lambda o: o.decode('utf-8', errors='replace'),
diff --git a/tests/base/tests.py b/tests/base/tests.py
index 0b3563e..e734538 100644
--- a/tests/base/tests.py
+++ b/tests/base/tests.py
@@ -149,7 +149,7 @@ class ClientTest(TestCase):
'Content-Type': 'application/octet-stream',
'X-Sentry-Auth': (
'Sentry sentry_timestamp=1328055286.51, '
- 'sentry_client=raven-python/%s, sentry_version=4, '
+ 'sentry_client=raven-python/%s, sentry_version=6, '
'sentry_key=public, '
'sentry_secret=secret' % (raven.VERSION,))
},
@@ -237,18 +237,20 @@ class ClientTest(TestCase):
def test_exception_event(self):
try:
raise ValueError('foo')
- except:
+ except Exception:
self.client.captureException()
self.assertEquals(len(self.client.events), 1)
event = self.client.events.pop(0)
self.assertEquals(event['message'], 'ValueError: foo')
- self.assertTrue('sentry.interfaces.Exception' in event)
- exc = event['sentry.interfaces.Exception']
- self.assertEquals(exc['type'], 'ValueError')
+ timeline = event['events']
+ self.assertEquals(len(timeline), 1)
+ exc = timeline[0]
+ self.assertEquals(exc['type'], 'exception')
+ self.assertEquals(exc['exc_type'], 'ValueError')
self.assertEquals(exc['value'], 'foo')
self.assertEquals(exc['module'], ValueError.__module__) # this differs in some Python versions
- assert 'sentry.interfaces.Stacktrace' not in event
+ assert 'stacktrace' not in event
stacktrace = exc['stacktrace']
self.assertEquals(len(stacktrace['frames']), 1)
frame = stacktrace['frames'][0]
@@ -293,9 +295,10 @@ class ClientTest(TestCase):
self.assertEquals(len(self.client.events), 1)
event = self.client.events.pop(0)
self.assertEquals(event['message'], 'test')
- self.assertTrue('sentry.interfaces.Stacktrace' in event)
- self.assertEquals(len(frames), len(event['sentry.interfaces.Stacktrace']['frames']))
- for frame, frame_i in zip(frames, event['sentry.interfaces.Stacktrace']['frames']):
+ exc = event['events'][0]
+ self.assertTrue('stacktrace' in exc)
+ self.assertEquals(len(frames), len(exc['stacktrace']['frames']))
+ for frame, frame_i in zip(frames, exc['stacktrace']['frames']):
self.assertEquals(frame[0].f_code.co_filename, frame_i['abs_path'])
self.assertEquals(frame[0].f_code.co_name, frame_i['function'])
@@ -304,8 +307,11 @@ class ClientTest(TestCase):
self.assertEquals(len(self.client.events), 1)
event = self.client.events.pop(0)
+ timeline = event['events']
+ self.assertEquals(len(timeline), 1)
+ message = timeline[0]
self.assertEquals(event['message'], 'test')
- self.assertTrue('sentry.interfaces.Stacktrace' in event)
+ self.assertTrue('stacktrace' in message)
self.assertTrue('timestamp' in event)
def test_site(self):
diff --git a/tests/contrib/bottle/tests.py b/tests/contrib/bottle/tests.py
index 92cdbc0..baefcc1 100644
--- a/tests/contrib/bottle/tests.py
+++ b/tests/contrib/bottle/tests.py
@@ -1,5 +1,3 @@
-import logging
-
from exam import fixture
from webtest import TestApp
@@ -37,7 +35,7 @@ def create_app(raven):
def capture_exception():
try:
raise ValueError('Boom')
- except:
+ except Exception:
tapp.app.captureException()
return 'Hello'
@@ -66,12 +64,15 @@ class BottleTest(BaseTest):
self.assertEquals(len(self.raven.events), 1)
event = self.raven.events.pop(0)
- self.assertTrue('sentry.interfaces.Exception' in event)
-
- exc = event['sentry.interfaces.Exception']
- self.assertEquals(exc['type'], 'ValueError')
+ timeline = event['events']
+ self.assertEqual(len(timeline), 2)
+
+ http = timeline[0]
+ self.assertEqual(http['type'], 'http_request')
+ exc = timeline[1]
+ self.assertEqual(exc['type'], 'exception')
+ self.assertEquals(exc['exc_type'], 'ValueError')
self.assertEquals(exc['value'], 'hello world')
- self.assertEquals(event['level'], logging.ERROR)
self.assertEquals(event['message'], 'ValueError: hello world')
self.assertEquals(event['culprit'], 'tests.contrib.bottle.tests in an_error')
@@ -81,10 +82,15 @@ class BottleTest(BaseTest):
self.assertEquals(len(self.raven.events), 1)
event = self.raven.events.pop(0)
+ timeline = event['events']
+ self.assertEqual(len(timeline), 2)
+
+ http = timeline[0]
+ self.assertEqual(http['type'], 'http_request')
+ exc = timeline[1]
+ self.assertEqual(exc['type'], 'exception')
- assert event['message'] == 'ValueError: Boom'
- assert 'sentry.interfaces.Http' in event
- assert 'sentry.interfaces.Exception' in event
+ self.assertEqual(event['message'], 'ValueError: Boom')
def test_captureMessage_captures_http(self):
response = self.client.get('/message/?foo=bar')
@@ -92,6 +98,10 @@ class BottleTest(BaseTest):
self.assertEquals(len(self.raven.events), 1)
event = self.raven.events.pop(0)
+ timeline = event['events']
+ self.assertEqual(len(timeline), 2)
- self.assertTrue('sentry.interfaces.Message' in event)
- self.assertTrue('sentry.interfaces.Http' in event)
+ http = timeline[0]
+ self.assertEqual(http['type'], 'http_request')
+ exc = timeline[1]
+ self.assertEqual(exc['type'], 'message')
diff --git a/tests/contrib/django/tests.py b/tests/contrib/django/tests.py
index a573b30..29fabf2 100644
--- a/tests/contrib/django/tests.py
+++ b/tests/contrib/django/tests.py
@@ -126,32 +126,36 @@ class DjangoClientTest(TestCase):
def setUp(self):
self.raven = get_client()
+ def tearDown(self):
+ self.raven.reset()
+
def test_basic(self):
self.raven.captureMessage(message='foo')
self.assertEquals(len(self.raven.events), 1)
event = self.raven.events.pop(0)
- self.assertTrue('sentry.interfaces.Message' in event)
- message = event['sentry.interfaces.Message']
+ timeline = event['events']
+ self.assertEqual(len(timeline), 1)
+ message = timeline[0]
+ self.assertEquals(message['type'], 'message')
self.assertEquals(message['message'], 'foo')
- self.assertEquals(event['level'], logging.ERROR)
self.assertEquals(event['message'], 'foo')
self.assertEquals(type(event['timestamp']), datetime.datetime)
def test_signal_integration(self):
try:
int('hello')
- except:
+ except Exception:
got_request_exception.send(sender=self.__class__, request=None)
else:
self.fail('Expected an exception.')
self.assertEquals(len(self.raven.events), 1)
event = self.raven.events.pop(0)
- self.assertTrue('sentry.interfaces.Exception' in event)
- exc = event['sentry.interfaces.Exception']
- self.assertEquals(exc['type'], 'ValueError')
+ timeline = event['events']
+ self.assertEqual(len(timeline), 1)
+ exc = timeline[0]
+ self.assertEquals(exc['exc_type'], 'ValueError')
self.assertEquals(exc['value'], "invalid literal for int() with base 10: 'hello'")
- self.assertEquals(event['level'], logging.ERROR)
self.assertEquals(event['message'], "ValueError: invalid literal for int() with base 10: 'hello'")
self.assertEquals(event['culprit'], 'tests.contrib.django.tests in test_signal_integration')
@@ -160,11 +164,11 @@ class DjangoClientTest(TestCase):
self.assertEquals(len(self.raven.events), 1)
event = self.raven.events.pop(0)
- self.assertTrue('sentry.interfaces.Exception' in event)
- exc = event['sentry.interfaces.Exception']
- self.assertEquals(exc['type'], 'Exception')
+ timeline = event['events']
+ self.assertEqual(len(timeline), 2)
+ exc = timeline[1]
+ self.assertEquals(exc['exc_type'], 'Exception')
self.assertEquals(exc['value'], 'view exception')
- self.assertEquals(event['level'], logging.ERROR)
self.assertEquals(event['message'], 'Exception: view exception')
self.assertEquals(event['culprit'], 'tests.contrib.django.views in raise_exc')
@@ -178,7 +182,7 @@ class DjangoClientTest(TestCase):
assert len(self.raven.events) == 1
event = self.raven.events.pop(0)
- assert 'sentry.interfaces.User' not in event
+ assert 'user' not in event
assert self.client.login(username='admin', password='admin')
@@ -186,8 +190,8 @@ class DjangoClientTest(TestCase):
self.assertEquals(len(self.raven.events), 1)
event = self.raven.events.pop(0)
- assert 'sentry.interfaces.User' in event
- user_info = event['sentry.interfaces.User']
+ assert 'user' in event
+ user_info = event['user']
assert user_info == {
'is_authenticated': True,
'username': user.username,
@@ -225,12 +229,14 @@ class DjangoClientTest(TestCase):
self.assertEquals(len(self.raven.events), 1)
event = self.raven.events.pop(0)
+ timeline = event['events']
+ self.assertEqual(len(timeline), 2)
+
+ exc = timeline[1]
+ self.assertEquals(exc['type'], 'exception')
- self.assertTrue('sentry.interfaces.Exception' in event)
- exc = event['sentry.interfaces.Exception']
- self.assertEquals(exc['type'], 'ImportError')
+ self.assertEquals(exc['exc_type'], 'ImportError')
self.assertEquals(exc['value'], 'request')
- self.assertEquals(event['level'], logging.ERROR)
self.assertEquals(event['message'], 'ImportError: request')
self.assertEquals(event['culprit'], 'tests.contrib.django.middleware in process_request')
@@ -242,12 +248,14 @@ class DjangoClientTest(TestCase):
self.assertEquals(len(self.raven.events), 1)
event = self.raven.events.pop(0)
+ timeline = event['events']
+ self.assertEqual(len(timeline), 2)
+
+ exc = timeline[1]
+ self.assertEquals(exc['type'], 'exception')
- self.assertTrue('sentry.interfaces.Exception' in event)
- exc = event['sentry.interfaces.Exception']
- self.assertEquals(exc['type'], 'ImportError')
+ self.assertEquals(exc['exc_type'], 'ImportError')
self.assertEquals(exc['value'], 'response')
- self.assertEquals(event['level'], logging.ERROR)
self.assertEquals(event['message'], 'ImportError: response')
self.assertEquals(event['culprit'], 'tests.contrib.django.middleware in process_response')
@@ -258,39 +266,48 @@ class DjangoClientTest(TestCase):
self.assertRaises(Exception, client.get, reverse('sentry-raise-exc'))
- assert len(self.raven.events) == 2
+ self.assertEqual(len(self.raven.events), 2)
event = self.raven.events.pop(0)
-
- self.assertTrue('sentry.interfaces.Exception' in event)
- exc = event['sentry.interfaces.Exception']
- self.assertEquals(exc['type'], 'Exception')
+ timeline = event['events']
+ self.assertEqual(len(timeline), 2)
+ tx_id = event['transaction']
+
+ http = timeline[0]
+ self.assertEqual(http['type'], 'http_request')
+ exc = timeline[1]
+ self.assertEquals(exc['type'], 'exception')
+ self.assertEquals(exc['exc_type'], 'Exception')
self.assertEquals(exc['value'], 'view exception')
- self.assertEquals(event['level'], logging.ERROR)
self.assertEquals(event['message'], 'Exception: view exception')
self.assertEquals(event['culprit'], 'tests.contrib.django.views in raise_exc')
event = self.raven.events.pop(0)
+ timeline = event['events']
+ self.assertEqual(len(timeline), 1)
+
+ exc = timeline[0]
+ self.assertEqual(exc['type'], 'exception')
- self.assertTrue('sentry.interfaces.Exception' in event)
- exc = event['sentry.interfaces.Exception']
- self.assertEquals(exc['type'], 'ValueError')
+ self.assertEquals(exc['exc_type'], 'ValueError')
self.assertEquals(exc['value'], 'handler500')
- self.assertEquals(event['level'], logging.ERROR)
self.assertEquals(event['message'], 'ValueError: handler500')
self.assertEquals(event['culprit'], 'tests.contrib.django.urls in handler500')
+ # Make sure that both messages got the same tx_id
+ self.assertEqual(tx_id, event['transaction'])
+
def test_view_middleware_exception(self):
with Settings(MIDDLEWARE_CLASSES=['tests.contrib.django.middleware.BrokenViewMiddleware']):
self.assertRaises(ImportError, self.client.get, reverse('sentry-raise-exc'))
self.assertEquals(len(self.raven.events), 1)
event = self.raven.events.pop(0)
+ timeline = event['events']
+ self.assertEqual(len(timeline), 2)
+ exc = timeline[1]
- self.assertTrue('sentry.interfaces.Exception' in event)
- exc = event['sentry.interfaces.Exception']
- self.assertEquals(exc['type'], 'ImportError')
+ self.assertEquals(exc['exc_type'], 'ImportError')
self.assertEquals(exc['value'], 'view')
- self.assertEquals(event['level'], logging.ERROR)
self.assertEquals(event['message'], 'ImportError: view')
self.assertEquals(event['culprit'], 'tests.contrib.django.middleware in process_view')
@@ -364,12 +381,17 @@ class DjangoClientTest(TestCase):
self.assertEquals(len(self.raven.events), 1)
event = self.raven.events.pop(0)
+ timeline = event['events']
+ self.assertEqual(len(timeline), 2)
+
+ http = timeline[0]
+ self.assertEqual(http['type'], 'http_request')
+ message = timeline[1]
+ self.assertEqual(message['type'], 'message')
- self.assertEquals(event['level'], logging.INFO)
+ self.assertEquals(message['logger'], 'http404')
self.assertEquals(event['logger'], 'http404')
- self.assertTrue('sentry.interfaces.Http' in event)
- http = event['sentry.interfaces.Http']
self.assertEquals(http['url'], 'http://testserver/non-existant-page')
self.assertEquals(http['method'], 'GET')
self.assertEquals(http['query_string'], '')
@@ -395,7 +417,7 @@ class DjangoClientTest(TestCase):
self.assertTrue('X-Sentry-ID' in headers)
self.assertEquals(len(self.raven.events), 1)
event = self.raven.events.pop(0)
- assert event['event_id'] == headers['X-Sentry-ID']
+ self.assertEqual(event['id'], headers['X-Sentry-ID'])
def test_get_client(self):
self.assertEquals(get_client(), get_client())
@@ -423,9 +445,14 @@ class DjangoClientTest(TestCase):
self.assertEquals(len(self.raven.events), 1)
event = self.raven.events.pop(0)
+ timeline = event['events']
+ self.assertEqual(len(timeline), 2)
+
+ http = timeline[0]
+ self.assertEqual(http['type'], 'http_request')
+ message = timeline[1]
+ self.assertEquals(message['type'], 'message')
- self.assertTrue('sentry.interfaces.Http' in event)
- http = event['sentry.interfaces.Http']
self.assertEquals(http['method'], 'POST')
self.assertEquals(http['data'], '<unavailable>')
@@ -441,11 +468,16 @@ class DjangoClientTest(TestCase):
self.assertEquals(len(self.raven.events), 1)
event = self.raven.events.pop(0)
+ timeline = event['events']
+ self.assertEqual(len(timeline), 2)
+
+ http = timeline[0]
+ self.assertEqual(http['type'], 'http_request')
+ message = timeline[1]
+ self.assertEquals(message['type'], 'message')
- self.assertTrue('sentry.interfaces.Http' in event)
- http = event['sentry.interfaces.Http']
self.assertEquals(http['method'], 'POST')
- self.assertEquals(http['data'], {'foo': 'bar', 'ham': 'spam'})
+ self.assertEquals(http['data'], {'foo': ['bar'], 'ham': ['spam']})
# This test only applies to Django 1.3+
def test_request_capture(self):
@@ -458,9 +490,14 @@ class DjangoClientTest(TestCase):
self.assertEquals(len(self.raven.events), 1)
event = self.raven.events.pop(0)
+ timeline = event['events']
+ self.assertEqual(len(timeline), 2)
+
+ http = timeline[0]
+ self.assertEqual(http['type'], 'http_request')
+ message = timeline[1]
+ self.assertEquals(message['type'], 'message')
- self.assertTrue('sentry.interfaces.Http' in event)
- http = event['sentry.interfaces.Http']
self.assertEquals(http['method'], 'POST')
self.assertEquals(http['data'], '<unavailable>')
self.assertTrue('headers' in http)
@@ -478,8 +515,12 @@ class DjangoClientTest(TestCase):
self.assertEquals(len(self.raven.events), 1)
event = self.raven.events.pop(0)
+ timeline = event['events']
+ self.assertEqual(len(timeline), 2)
+ exc = timeline[1]
+ self.assertEqual(exc['type'], 'exception')
- frames = event['sentry.interfaces.Exception']['stacktrace']['frames']
+ frames = exc['stacktrace']['frames']
for frame in frames:
if frame['module'].startswith('django.'):
assert frame.get('in_app') is False
@@ -523,8 +564,7 @@ class DjangoClientTest(TestCase):
request.META['HTTP_HOST'] = 'example.com'
result = self.raven.get_data_from_request(request)
build_absolute_uri.assert_called_once_with()
- assert 'sentry.interfaces.Http' in result
- assert result['sentry.interfaces.Http']['url'] == 'http://example.com/'
+ assert result['url'] == 'http://example.com/'
class DjangoTemplateTagTest(TestCase):
@@ -552,20 +592,17 @@ class DjangoLoggingTest(TestCase):
logger.addHandler(handler)
logger.error('This is a test error', extra={
- 'request': WSGIRequest(environ={
- 'wsgi.input': StringIO(),
- 'REQUEST_METHOD': 'POST',
- 'SERVER_NAME': 'testserver',
- 'SERVER_PORT': '80',
- 'CONTENT_TYPE': 'application/octet-stream',
- 'ACCEPT': 'application/json',
- })
+ 'request': make_request()
})
self.assertEquals(len(self.raven.events), 1)
event = self.raven.events.pop(0)
- self.assertTrue('sentry.interfaces.Http' in event)
- http = event['sentry.interfaces.Http']
+ timeline = event['events']
+ self.assertEqual(len(timeline), 2)
+ http = timeline[0]
+ self.assertEqual(http['type'], 'http_request')
+ msg = timeline[1]
+ self.assertEqual(msg['type'], 'message')
self.assertEquals(http['method'], 'POST')
@@ -757,7 +794,7 @@ class SentryExceptionHandlerTest(TestCase):
exc_info.return_value = self.exc_info
sentry_exception_handler(request=self.request)
- captureException.assert_called_once_with(exc_info=self.exc_info, request=self.request)
+ captureException.assert_called_once_with(exc_info=self.exc_info)
@mock.patch.object(TempStoreClient, 'captureException')
@mock.patch('sys.exc_info')
diff --git a/tests/contrib/flask/tests.py b/tests/contrib/flask/tests.py
index 3453e8c..87179e1 100644
--- a/tests/contrib/flask/tests.py
+++ b/tests/contrib/flask/tests.py
@@ -1,5 +1,3 @@
-import logging
-
from exam import before, fixture
from mock import patch
@@ -46,7 +44,7 @@ def create_app(ignore_exceptions=None):
def capture_exception():
try:
raise ValueError('Boom')
- except:
+ except Exception:
current_app.extensions['sentry'].captureException()
return 'Hello'
@@ -105,12 +103,16 @@ class FlaskTest(BaseTest):
self.assertEquals(len(self.raven.events), 1)
event = self.raven.events.pop(0)
+ timeline = event['events']
+ self.assertEqual(len(timeline), 2)
+
+ http = timeline[0]
+ self.assertEqual(http['type'], 'http_request')
+ exc = timeline[1]
+ self.assertEqual(exc['type'], 'exception')
- self.assertTrue('sentry.interfaces.Exception' in event)
- exc = event['sentry.interfaces.Exception']
- self.assertEquals(exc['type'], 'ValueError')
+ self.assertEquals(exc['exc_type'], 'ValueError')
self.assertEquals(exc['value'], 'hello world')
- self.assertEquals(event['level'], logging.ERROR)
self.assertEquals(event['message'], 'ValueError: hello world')
self.assertEquals(event['culprit'], 'tests.contrib.flask.tests in an_error')
@@ -120,9 +122,14 @@ class FlaskTest(BaseTest):
self.assertEquals(len(self.raven.events), 1)
event = self.raven.events.pop(0)
+ timeline = event['events']
+ self.assertEqual(len(timeline), 2)
+
+ http = timeline[0]
+ self.assertEqual(http['type'], 'http_request')
+ exc = timeline[1]
+ self.assertEqual(exc['type'], 'exception')
- self.assertTrue('sentry.interfaces.Http' in event)
- http = event['sentry.interfaces.Http']
self.assertEquals(http['url'], 'http://localhost/an-error/')
self.assertEquals(http['query_string'], 'foo=bar')
self.assertEquals(http['method'], 'GET')
@@ -147,13 +154,15 @@ class FlaskTest(BaseTest):
self.assertEquals(len(self.raven.events), 1)
event = self.raven.events.pop(0)
+ timeline = event['events']
+ self.assertEqual(len(timeline), 2)
- self.assertTrue('sentry.interfaces.Http' in event)
- http = event['sentry.interfaces.Http']
+ http = timeline[0]
+ self.assertEqual(http['type'], 'http_request')
self.assertEquals(http['url'], 'http://localhost/an-error/')
self.assertEquals(http['query_string'], 'biz=baz')
self.assertEquals(http['method'], 'POST')
- self.assertEquals(http['data'], {'foo': 'bar'})
+ self.assertEquals(http['data'], {'foo': ['bar']})
self.assertTrue('headers' in http)
headers = http['headers']
self.assertTrue('Content-Length' in headers, headers.keys())
@@ -174,10 +183,14 @@ class FlaskTest(BaseTest):
self.assertEquals(len(self.raven.events), 1)
event = self.raven.events.pop(0)
+ timeline = event['events']
+ self.assertEqual(len(timeline), 2)
+ http = timeline[0]
+ self.assertEqual(http['type'], 'http_request')
+ exc = timeline[1]
+ self.assertEqual(exc['type'], 'exception')
assert event['message'] == 'ValueError: Boom'
- assert 'sentry.interfaces.Http' in event
- assert 'sentry.interfaces.Exception' in event
def test_captureMessage_captures_http(self):
response = self.client.get('/message/?foo=bar')
@@ -185,9 +198,13 @@ class FlaskTest(BaseTest):
self.assertEquals(len(self.raven.events), 1)
event = self.raven.events.pop(0)
+ timeline = event['events']
+ self.assertEqual(len(timeline), 2)
- self.assertTrue('sentry.interfaces.Message' in event)
- self.assertTrue('sentry.interfaces.Http' in event)
+ http = timeline[0]
+ self.assertEqual(http['type'], 'http_request')
+ msg = timeline[1]
+ self.assertEqual(msg['type'], 'message')
@patch('flask.wrappers.RequestBase._load_form_data')
def test_get_data_handles_disconnected_client(self, lfd):
@@ -196,9 +213,12 @@ class FlaskTest(BaseTest):
self.client.post('/capture/?foo=bar', data={'baz': 'foo'})
event = self.raven.events.pop(0)
+ timeline = event['events']
+ self.assertEqual(len(timeline), 2)
+
+ http = timeline[0]
+ self.assertEqual(http['type'], 'http_request')
- self.assertTrue('sentry.interfaces.Http' in event)
- http = event['sentry.interfaces.Http']
self.assertEqual({}, http.get('data'))
def test_error_handler_with_ignored_exception(self):
@@ -231,6 +251,10 @@ class FlaskLoginTest(BaseTest):
def test_user(self):
self.client.get('/an-error-logged-in/')
event = self.raven.events.pop(0)
- assert event['message'] == 'ValueError: hello world'
- assert 'sentry.interfaces.Http' in event
- assert 'sentry.interfaces.User' in event
+ timeline = event['events']
+ self.assertEqual(len(timeline), 2)
+
+ http = timeline[0]
+ self.assertEqual(http['type'], 'http_request')
+ self.assertEqual(event['message'], 'ValueError: hello world')
+ self.assertIn('user', event)
diff --git a/tests/contrib/tornado/tests.py b/tests/contrib/tornado/tests.py
index bd147f1..78dfe71 100644
--- a/tests/contrib/tornado/tests.py
+++ b/tests/contrib/tornado/tests.py
@@ -87,8 +87,18 @@ class TornadoAsyncClientTestCase(testing.AsyncHTTPTestCase):
self.assertEqual(response.code, 200)
self.assertEqual(send.call_count, 1)
args, kwargs = send.call_args
+ import pprint
+ pprint.pprint(args)
+ pprint.pprint(kwargs)
- self.assertTrue(('sentry.interfaces.User' in kwargs))
+ # timeline = event['events']
+ # self.assertEqual(len(timeline), 2)
+
+ # http = timeline[0]
+ # self.assertEqual(http['type'], 'http_request')
+ # exc = timeline[1]
+
+ self.assertIn('user', kwargs)
self.assertTrue(('sentry.interfaces.Http' in kwargs))
self.assertTrue(('sentry.interfaces.Exception' in kwargs))
diff --git a/tests/contrib/webpy/tests.py b/tests/contrib/webpy/tests.py
index f2603a4..e7ef3fd 100644
--- a/tests/contrib/webpy/tests.py
+++ b/tests/contrib/webpy/tests.py
@@ -51,10 +51,11 @@ class WebPyTest(TestCase):
self.assertEquals(resp.status, 500)
self.assertEquals(len(self.store.events), 1)
- event = self.store.events.pop()
- self.assertTrue('sentry.interfaces.Exception' in event)
- exc = event['sentry.interfaces.Exception']
- self.assertEquals(exc['type'], 'ValueError')
+ event = self.store.events.pop(0)
+ timeline = event['events']
+ self.assertEqual(len(timeline), 1)
+ exc = timeline[0]
+ self.assertEquals(exc['exc_type'], 'ValueError')
self.assertEquals(exc['value'], 'That\'s what she said')
self.assertEquals(event['message'], 'ValueError: That\'s what she said')
self.assertEquals(event['culprit'], 'tests.contrib.webpy.tests in GET')
diff --git a/tests/handlers/logbook/tests.py b/tests/handlers/logbook/tests.py
index a58d3e1..4c11116 100644
--- a/tests/handlers/logbook/tests.py
+++ b/tests/handlers/logbook/tests.py
@@ -34,24 +34,25 @@ class LogbookHandlerTest(TestCase):
self.assertEquals(len(client.events), 1)
event = client.events.pop(0)
+ timeline = event['events']
+ self.assertEqual(len(timeline), 1)
+ msg = timeline[0]
+ self.assertEqual(msg['type'], 'message')
+
self.assertEquals(event['logger'], __name__)
- self.assertEquals(event['level'], 'error')
self.assertEquals(event['message'], 'This is a test error')
- self.assertFalse('sentry.interfaces.Exception' in event)
- self.assertTrue('sentry.interfaces.Message' in event)
- msg = event['sentry.interfaces.Message']
self.assertEquals(msg['message'], 'This is a test error')
self.assertEquals(msg['params'], ())
logger.warning('This is a test warning')
self.assertEquals(len(client.events), 1)
event = client.events.pop(0)
+ timeline = event['events']
+ self.assertEqual(len(timeline), 1)
+ msg = timeline[0]
+ self.assertEqual(msg['type'], 'message')
self.assertEquals(event['logger'], __name__)
- self.assertEquals(event['level'], 'warning')
self.assertEquals(event['message'], 'This is a test warning')
- self.assertFalse('sentry.interfaces.Exception' in event)
- self.assertTrue('sentry.interfaces.Message' in event)
- msg = event['sentry.interfaces.Message']
self.assertEquals(msg['message'], 'This is a test warning')
self.assertEquals(msg['params'], ())
@@ -60,14 +61,14 @@ class LogbookHandlerTest(TestCase):
))
self.assertEquals(len(client.events), 1)
event = client.events.pop(0)
+ timeline = event['events']
+ self.assertEqual(len(timeline), 1)
+ msg = timeline[0]
if six.PY3:
expected = "'http://example.com'"
else:
expected = "u'http://example.com'"
self.assertEquals(event['extra']['url'], expected)
- self.assertFalse('sentry.interfaces.Exception' in event)
- self.assertTrue('sentry.interfaces.Message' in event)
- msg = event['sentry.interfaces.Message']
self.assertEquals(msg['message'], 'This is a test info with a url')
self.assertEquals(msg['params'], ())
@@ -78,14 +79,16 @@ class LogbookHandlerTest(TestCase):
self.assertEquals(len(client.events), 1)
event = client.events.pop(0)
+ timeline = event['events']
+ self.assertEqual(len(timeline), 2)
+ msg = timeline[0]
+ self.assertEqual(msg['type'], 'message')
+ exc = timeline[1]
+ self.assertEqual(exc['type'], 'exception')
self.assertEquals(event['message'], 'This is a test info with an exception')
- self.assertTrue('sentry.interfaces.Exception' in event)
- exc = event['sentry.interfaces.Exception']
- self.assertEquals(exc['type'], 'ValueError')
+ self.assertEquals(exc['exc_type'], 'ValueError')
self.assertEquals(exc['value'], 'This is a test ValueError')
- self.assertTrue('sentry.interfaces.Message' in event)
- msg = event['sentry.interfaces.Message']
self.assertEquals(msg['message'], 'This is a test info with an exception')
self.assertEquals(msg['params'], ())
@@ -93,10 +96,11 @@ class LogbookHandlerTest(TestCase):
logger.info('This is a test of {0}', 'args')
self.assertEquals(len(client.events), 1)
event = client.events.pop(0)
+ timeline = event['events']
+ self.assertEqual(len(timeline), 1)
+ msg = timeline[0]
+ self.assertEqual(msg['type'], 'message')
self.assertEquals(event['message'], 'This is a test of args')
- self.assertFalse('sentry.interfaces.Exception' in event)
- self.assertTrue('sentry.interfaces.Message' in event)
- msg = event['sentry.interfaces.Message']
self.assertEquals(msg['message'], 'This is a test of {0}')
expected = ("'args'",) if six.PY3 else ("u'args'",)
self.assertEquals(msg['params'], expected)
diff --git a/tests/handlers/logging/tests.py b/tests/handlers/logging/tests.py
index 905d069..d3e21ba 100644
--- a/tests/handlers/logging/tests.py
+++ b/tests/handlers/logging/tests.py
@@ -39,12 +39,12 @@ class LoggingIntegrationTest(TestCase):
self.assertEqual(len(self.client.events), 1)
event = self.client.events.pop(0)
+ timeline = event['events']
+ self.assertEqual(len(timeline), 1)
+ msg = timeline[0]
+ self.assertEqual(msg['type'], 'message')
self.assertEqual(event['logger'], 'root')
- self.assertEqual(event['level'], logging.INFO)
self.assertEqual(event['message'], 'This is a test error')
- self.assertFalse('sentry.interfaces.Exception' in event)
- self.assertTrue('sentry.interfaces.Message' in event)
- msg = event['sentry.interfaces.Message']
self.assertEqual(msg['message'], 'This is a test error')
self.assertEqual(msg['params'], ())
@@ -87,14 +87,16 @@ class LoggingIntegrationTest(TestCase):
self.assertEqual(len(self.client.events), 1)
event = self.client.events.pop(0)
+ timeline = event['events']
+ self.assertEqual(len(timeline), 2)
+ msg = timeline[0]
+ self.assertEqual(msg['type'], 'message')
+ exc = timeline[1]
+ self.assertEqual(exc['type'], 'exception')
self.assertEqual(event['message'], 'This is a test info with an exception')
- self.assertTrue('sentry.interfaces.Exception' in event)
- exc = event['sentry.interfaces.Exception']
- self.assertEqual(exc['type'], 'ValueError')
+ self.assertEqual(exc['exc_type'], 'ValueError')
self.assertEqual(exc['value'], 'This is a test ValueError')
- self.assertTrue('sentry.interfaces.Message' in event)
- msg = event['sentry.interfaces.Message']
self.assertEqual(msg['message'], 'This is a test info with an exception')
self.assertEqual(msg['params'], ())
@@ -104,8 +106,11 @@ class LoggingIntegrationTest(TestCase):
self.assertEqual(len(self.client.events), 1)
event = self.client.events.pop(0)
+ timeline = event['events']
+ self.assertEqual(len(timeline), 1)
+ msg = timeline[0]
+ self.assertEqual(msg['type'], 'message')
self.assertEqual(event['message'], 'This is a test of args')
- msg = event['sentry.interfaces.Message']
self.assertEqual(msg['message'], 'This is a test of %s')
expected = ("'args'",) if six.PY3 else ("u'args'",)
self.assertEqual(msg['params'], expected)
@@ -116,13 +121,14 @@ class LoggingIntegrationTest(TestCase):
self.assertEqual(len(self.client.events), 1)
event = self.client.events.pop(0)
- self.assertTrue('sentry.interfaces.Stacktrace' in event)
- frames = event['sentry.interfaces.Stacktrace']['frames']
+ timeline = event['events']
+ self.assertEqual(len(timeline), 1)
+ msg = timeline[0]
+ self.assertEqual(msg['type'], 'message')
+ frames = msg['stacktrace']['frames']
self.assertNotEquals(len(frames), 1)
frame = frames[0]
self.assertEqual(frame['module'], 'raven.handlers.logging')
- self.assertFalse('sentry.interfaces.Exception' in event)
- self.assertTrue('sentry.interfaces.Message' in event)
self.assertEqual(event['culprit'], 'root in make_record')
self.assertEqual(event['message'], 'This is a test of stacks')
@@ -141,14 +147,15 @@ class LoggingIntegrationTest(TestCase):
self.assertEqual(len(self.client.events), 1)
event = self.client.events.pop(0)
- assert 'sentry.interfaces.Stacktrace' in event
- assert 'culprit' in event
- assert event['culprit'] == 'root in make_record'
- self.assertTrue('message' in event, event)
+ timeline = event['events']
+ self.assertEqual(len(timeline), 1)
+ msg = timeline[0]
+ self.assertEqual(msg['type'], 'message')
+ self.assertIn('stacktrace', msg)
+ self.assertIn('culprit', event)
+ self.assertEqual(event['culprit'], 'root in make_record')
+ self.assertIn('message', event)
self.assertEqual(event['message'], 'This is a test of stacks')
- self.assertFalse('sentry.interfaces.Exception' in event)
- self.assertTrue('sentry.interfaces.Message' in event)
- msg = event['sentry.interfaces.Message']
self.assertEqual(msg['message'], 'This is a test of stacks')
self.assertEqual(msg['params'], ())
diff --git a/tests/middleware/tests.py b/tests/middleware/tests.py
index 50f90f5..e18d2c7 100644
--- a/tests/middleware/tests.py
+++ b/tests/middleware/tests.py
@@ -1,6 +1,5 @@
from __future__ import with_statement
-import logging
import webob
from exam import fixture
from raven.utils.testutils import TestCase
@@ -64,16 +63,17 @@ class MiddlewareTestCase(TestCase):
self.assertEquals(len(self.client.events), 1)
event = self.client.events.pop(0)
+ timeline = event['events']
+ self.assertEqual(len(timeline), 2)
- self.assertTrue('sentry.interfaces.Exception' in event)
- exc = event['sentry.interfaces.Exception']
- self.assertEquals(exc['type'], 'ValueError')
+ http = timeline[0]
+ self.assertEqual(http['type'], 'http_request')
+ exc = timeline[1]
+
+ self.assertEquals(exc['exc_type'], 'ValueError')
self.assertEquals(exc['value'], 'hello world')
- self.assertEquals(event['level'], logging.ERROR)
self.assertEquals(event['message'], 'ValueError: hello world')
- self.assertTrue('sentry.interfaces.Http' in event)
- http = event['sentry.interfaces.Http']
self.assertEquals(http['url'], 'http://localhost/an-error')
self.assertEquals(http['query_string'], 'foo=bar')
self.assertEquals(http['method'], 'GET')
diff --git a/tests/transport/tests.py b/tests/transport/tests.py
index 9385095..ba43f24 100644
--- a/tests/transport/tests.py
+++ b/tests/transport/tests.py
@@ -63,12 +63,14 @@ class TransportTest(TestCase):
mydate = datetime.datetime(2012, 5, 4, tzinfo=pytz.utc)
d = calendar.timegm(mydate.timetuple())
- msg = c.build_msg('raven.events.Message', message='foo', date=d)
+ c.add_message('foo')
+ msg = c.build_msg(date=d)
expected = {
'project': '1',
- 'sentry.interfaces.Message': {'message': 'foo', 'params': ()},
+ 'events': [
+ {'type': 'message', 'message': 'foo', 'params': ()},
+ ],
'server_name': 'test_server',
- 'level': 40,
'modules': {},
'tags': {},
'time_spent': None,
@@ -77,6 +79,9 @@ class TransportTest(TestCase):
}
# The event_id is always overridden
- del msg['event_id']
+ del msg['id']
+
+ # Timestamps are unique
+ del msg['events'][0]['timestamp']
self.assertDictContainsSubset(expected, msg)
diff --git a/tests/utils/json/tests.py b/tests/utils/json/tests.py
index 9715033..0bcdb74 100644
--- a/tests/utils/json/tests.py
+++ b/tests/utils/json/tests.py
@@ -14,7 +14,7 @@ class JSONTest(TestCase):
def test_datetime(self):
res = datetime.datetime(day=1, month=1, year=2011, hour=1, minute=1, second=1)
- assert json.dumps(res) == '"2011-01-01T01:01:01Z"'
+ assert json.dumps(res) == '"2011-01-01T01:01:01.000000Z"'
def test_set(self):
res = set(['foo', 'bar'])