diff options
author | David Cramer <dcramer@gmail.com> | 2012-07-05 11:38:10 +0200 |
---|---|---|
committer | David Cramer <dcramer@gmail.com> | 2012-07-05 11:38:10 +0200 |
commit | 1e0e18d1ccf8864ac839ee9abdb6dc1a591278ee (patch) | |
tree | 45c18aaa4af86a3661a20372a1eb3d63d8dea418 | |
parent | 2ca52a31c2b857dbf396c32b567ee3afe839eb44 (diff) | |
download | raven-1e0e18d1ccf8864ac839ee9abdb6dc1a591278ee.tar.gz |
Initial work on extendable serializers
-rw-r--r-- | raven/base.py | 5 | ||||
-rw-r--r-- | raven/contrib/django/models.py | 5 | ||||
-rw-r--r-- | raven/contrib/django/serializers.py | 24 | ||||
-rw-r--r-- | raven/utils/encoding.py | 70 | ||||
-rw-r--r-- | raven/utils/serializer/__init__.py | 11 | ||||
-rw-r--r-- | raven/utils/serializer/base.py | 124 | ||||
-rw-r--r-- | raven/utils/serializer/manager.py | 84 |
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) |