summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Cramer <dcramer@gmail.com>2012-07-05 11:38:10 +0200
committerDavid Cramer <dcramer@gmail.com>2012-07-05 11:38:10 +0200
commit1e0e18d1ccf8864ac839ee9abdb6dc1a591278ee (patch)
tree45c18aaa4af86a3661a20372a1eb3d63d8dea418
parent2ca52a31c2b857dbf396c32b567ee3afe839eb44 (diff)
downloadraven-1e0e18d1ccf8864ac839ee9abdb6dc1a591278ee.tar.gz
Initial work on extendable serializers
-rw-r--r--raven/base.py5
-rw-r--r--raven/contrib/django/models.py5
-rw-r--r--raven/contrib/django/serializers.py24
-rw-r--r--raven/utils/encoding.py70
-rw-r--r--raven/utils/serializer/__init__.py11
-rw-r--r--raven/utils/serializer/base.py124
-rw-r--r--raven/utils/serializer/manager.py84
7 files changed, 257 insertions, 66 deletions
diff --git a/raven/base.py b/raven/base.py
index c856205..cb70100 100644
--- a/raven/base.py
+++ b/raven/base.py
@@ -296,7 +296,7 @@ class Client(object):
data.update(processor.process(data))
# Make sure all data is coerced
- data = transform(data)
+ data = self.transform(data)
if 'message' not in data:
data['message'] = handler.to_string(data)
@@ -312,6 +312,9 @@ class Client(object):
return data
+ def transform(self, data):
+ return transform(data)
+
def capture(self, event_type, data=None, date=None, time_spent=None,
extra=None, stack=None, public_key=None, tags=None, **kwargs):
"""
diff --git a/raven/contrib/django/models.py b/raven/contrib/django/models.py
index a54db8d..ecd7d5d 100644
--- a/raven/contrib/django/models.py
+++ b/raven/contrib/django/models.py
@@ -194,5 +194,10 @@ def register_handlers():
except Exception, e:
logger.exception('Failed installing django-celery hook: %s' % e)
+
+def register_serializers():
+ import raven.contrib.django.serializers # force import so serializers can call register
+
if 'raven.contrib.django' in django_settings.INSTALLED_APPS:
register_handlers()
+ register_serializers()
diff --git a/raven/contrib/django/serializers.py b/raven/contrib/django/serializers.py
new file mode 100644
index 0000000..23f3f8d
--- /dev/null
+++ b/raven/contrib/django/serializers.py
@@ -0,0 +1,24 @@
+"""
+raven.contrib.django.serializers
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+:copyright: (c) 2010 by the Sentry Team, see AUTHORS for more details.
+:license: BSD, see LICENSE for more details.
+"""
+
+from django.utils.functional import Promise
+from raven.utils.serializer import Serializer, register
+
+__all__ = ('PromiseSerializer',)
+
+
+@register
+class PromiseSerializer(Serializer):
+ types = (Promise,)
+
+ def serialize(self, value):
+ # EPIC HACK
+ # handles lazy model instances (which are proxy values that dont easily give you the actual function)
+ pre = value.__class__.__name__[1:]
+ value = getattr(value, '%s__func' % pre)(*getattr(value, '%s__args' % pre), **getattr(value, '%s__kw' % pre))
+ return self.recurse(value)
diff --git a/raven/utils/encoding.py b/raven/utils/encoding.py
index 41c7db9..525dd47 100644
--- a/raven/utils/encoding.py
+++ b/raven/utils/encoding.py
@@ -6,8 +6,7 @@ raven.utils.encoding
:license: BSD, see LICENSE for more details.
"""
-import uuid
-from types import ClassType, TypeType
+import warnings
def force_unicode(s, encoding='utf-8', errors='strict'):
@@ -54,71 +53,12 @@ def force_unicode(s, encoding='utf-8', errors='strict'):
return s
-def _has_sentry_metadata(value):
- try:
- return callable(value.__getattribute__("__sentry__"))
- except:
- return False
-
-
-def transform(value, stack=[], context=None):
- # TODO: make this extendable
- if context is None:
- context = {}
+def transform(value):
+ from raven.utils.serializer import transform
- objid = id(value)
- if objid in context:
- return '<...>'
+ warnings.warn('You should switch to raven.utils.serializers.transform', DeprecationWarning)
- context[objid] = 1
- transform_rec = lambda o: transform(o, stack + [value], context)
-
- if any(value is s for s in stack):
- ret = 'cycle'
- elif isinstance(value, (tuple, list, set, frozenset)):
- try:
- ret = type(value)(transform_rec(o) for o in value)
- except Exception:
- # We may be dealing with a namedtuple
- class value_type(list):
- __name__ = type(value).__name__
- ret = value_type(transform_rec(o) for o in value)
- elif isinstance(value, uuid.UUID):
- ret = repr(value)
- elif isinstance(value, dict):
- ret = dict((to_string(k), transform_rec(v)) for k, v in value.iteritems())
- elif isinstance(value, unicode):
- ret = to_unicode(value)
- elif isinstance(value, str):
- ret = to_string(value)
- elif not isinstance(value, (ClassType, TypeType)) and \
- _has_sentry_metadata(value):
- ret = transform_rec(value.__sentry__())
- # elif isinstance(value, Promise):
- # # EPIC HACK
- # # handles lazy model instances (which are proxy values that dont easily give you the actual function)
- # pre = value.__class__.__name__[1:]
- # value = getattr(value, '%s__func' % pre)(*getattr(value, '%s__args' % pre), **getattr(value, '%s__kw' % pre))
- # return transform(value)
- elif isinstance(value, bool):
- ret = bool(value)
- elif isinstance(value, float):
- ret = float(value)
- elif isinstance(value, int):
- ret = int(value)
- elif isinstance(value, long):
- ret = long(value)
- elif value is not None:
- try:
- ret = transform(repr(value))
- except:
- # It's common case that a model's __unicode__ definition may try to query the database
- # which if it was not cleaned up correctly, would hit a transaction aborted exception
- ret = u'<BadRepr: %s>' % type(value)
- else:
- ret = None
- del context[objid]
- return ret
+ return transform(value)
def to_unicode(value):
diff --git a/raven/utils/serializer/__init__.py b/raven/utils/serializer/__init__.py
new file mode 100644
index 0000000..c90ce6b
--- /dev/null
+++ b/raven/utils/serializer/__init__.py
@@ -0,0 +1,11 @@
+"""
+raven.utils.serializer
+~~~~~~~~~~~~~~~~~~~~~~
+
+:copyright: (c) 2010 by the Sentry Team, see AUTHORS for more details.
+:license: BSD, see LICENSE for more details.
+"""
+
+
+from raven.utils.serializer.base import Serializer
+from raven.utils.serializer.manager import register, transform
diff --git a/raven/utils/serializer/base.py b/raven/utils/serializer/base.py
new file mode 100644
index 0000000..ce71198
--- /dev/null
+++ b/raven/utils/serializer/base.py
@@ -0,0 +1,124 @@
+"""
+raven.utils.serializer.base
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+:copyright: (c) 2010 by the Sentry Team, see AUTHORS for more details.
+:license: BSD, see LICENSE for more details.
+"""
+
+from raven.utils.encoding import to_string, to_unicode
+from raven.utils.serializer.manager import register
+from types import ClassType, TypeType
+from uuid import UUID
+
+
+def has_sentry_metadata(value):
+ try:
+ return callable(value.__getattribute__('__sentry__'))
+ except:
+ return False
+
+
+class Serializer(object):
+ types = ()
+
+ def __init__(self, manager):
+ self.manager = manager
+
+ def can(self, value):
+ return isinstance(value, self.types)
+
+ def serialize(self, value):
+ return value
+
+ def recurse(self, value):
+ return self.manager.transform(value)
+
+
+@register
+class IterableSerializer(Serializer):
+ types = (tuple, list, set, frozenset)
+
+ def serialize(self, value):
+ try:
+ return type(value)(self.recurse(o) for o in value)
+ except Exception:
+ # We may be dealing with something like a namedtuple
+ class value_type(list):
+ __name__ = type(value).__name__
+ return value_type(self.recurse(o) for o in value)
+
+
+@register
+class UUIDSerializer(Serializer):
+ types = (UUID,)
+
+ def serialize(self, value):
+ return repr(value)
+
+
+@register
+class DictSerializer(Serializer):
+ types = (dict,)
+
+ def serialize(self, value):
+ return dict((to_string(k), self.recurse(v)) for k, v in value.iteritems())
+
+
+@register
+class UnicodeSerializer(Serializer):
+ types = (unicode,)
+
+ def serialize(self, value):
+ return to_unicode(value)
+
+
+@register
+class StringSerializer(Serializer):
+ types = (str,)
+
+ def serialize(self, value):
+ return to_string(value)
+
+
+@register
+class TypeSerializer(Serializer):
+ types = (ClassType, TypeType,)
+
+ def can(self, value):
+ return not super(TypeSerializer, self).can(value) and has_sentry_metadata(value)
+
+ def serialize(self, value):
+ return self.recurse(value.__sentry__())
+
+
+@register
+class BooleanSerializer(Serializer):
+ types = (bool,)
+
+ def serialize(self, value):
+ return bool(value)
+
+
+@register
+class FloatSerializer(Serializer):
+ types = (float,)
+
+ def serialize(self, value):
+ return float(value)
+
+
+@register
+class IntegerSerializer(Serializer):
+ types = (int,)
+
+ def serialize(self, value):
+ return int(value)
+
+
+@register
+class LongSerializer(Serializer):
+ types = (long,)
+
+ def serialize(self, value):
+ return long(value)
diff --git a/raven/utils/serializer/manager.py b/raven/utils/serializer/manager.py
new file mode 100644
index 0000000..62cb815
--- /dev/null
+++ b/raven/utils/serializer/manager.py
@@ -0,0 +1,84 @@
+"""
+raven.utils.serializer.manager
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+:copyright: (c) 2010 by the Sentry Team, see AUTHORS for more details.
+:license: BSD, see LICENSE for more details.
+"""
+
+import logging
+
+logger = logging.getLogger('sentry.errors.serializer')
+
+
+class SerializationManager(object):
+ def __init__(self):
+ self.__registry = []
+ self.__serializers = {}
+
+ @property
+ def serializers(self):
+ # XXX: Would serializers ever need state that we shouldnt cache them?
+ for serializer in self.__registry:
+ yield serializer
+
+ def register(self, serializer):
+ if serializer not in self.__registry:
+ self.__registry.append(serializer)
+ return serializer
+
+
+class Serializer(object):
+ def __init__(self, manager):
+ self.manager = manager
+ self.context = {}
+ self.serializers = []
+ for serializer in manager.serializers:
+ self.serializers.append(serializer(self))
+
+ def transform(self, value):
+ """
+ Primary function which handles recursively transforming
+ values via their serializers
+ """
+ if value is None:
+ return None
+
+ objid = id(value)
+ if objid in self.context:
+ return '<...>'
+ self.context[objid] = 1
+
+ # TODO: do we still need this code? context seems to handle it
+ # if any(value is s for s in self.stack):
+ # ret = 'cycle'
+ # self.stack.append(value)
+
+ try:
+ for serializer in self.serializers:
+ if serializer.can(value):
+ try:
+ return serializer.serialize(value)
+ except Exception, e:
+ logger.exception(e)
+ return u'<BadSerializable: %s>' % type(value)
+
+ # if all else fails, lets use the repr of the object
+ try:
+ return self.transform(repr(value))
+ except Exception, e:
+ logger.exception(e)
+ # It's common case that a model's __unicode__ definition may try to query the database
+ # which if it was not cleaned up correctly, would hit a transaction aborted exception
+ return u'<BadRepr: %s>' % type(value)
+ finally:
+ del self.context[objid]
+
+
+manager = SerializationManager()
+register = manager.register
+
+
+def transform(value, manager=manager):
+ serializer = Serializer(manager)
+ return serializer.transform(value)