/
__init__.py
362 lines (314 loc) · 12 KB
/
__init__.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
"""
sentry.utils
~~~~~~~~~~~~
:copyright: (c) 2010 by the Sentry Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
import hmac
import logging
try:
import pkg_resources
except ImportError:
pkg_resources = None
import sys
import uuid
from pprint import pformat
from types import ClassType, TypeType
import django
from django.conf import settings as django_settings
from django.http import HttpRequest
from django.utils.encoding import force_unicode
from django.utils.functional import Promise
from django.utils.hashcompat import md5_constructor, sha_constructor
import sentry
from sentry.conf import settings
_FILTER_CACHE = None
def get_filters():
global _FILTER_CACHE
if _FILTER_CACHE is None:
filters = []
for filter_ in settings.FILTERS:
if filter_.endswith('sentry.filters.SearchFilter'):
continue
module_name, class_name = filter_.rsplit('.', 1)
try:
module = __import__(module_name, {}, {}, class_name)
filter_ = getattr(module, class_name)
except Exception:
logger = logging.getLogger('sentry.errors')
logger.exception('Unable to import %s' % (filter_,))
continue
filters.append(filter_)
_FILTER_CACHE = filters
for f in _FILTER_CACHE:
yield f
def get_db_engine(alias='default'):
has_multidb = django.VERSION >= (1, 2)
if has_multidb:
value = django_settings.DATABASES[alias]['ENGINE']
else:
assert alias == 'default', 'You cannot fetch a database engine other than the default on Django < 1.2'
value = django_settings.DATABASE_ENGINE
return value.rsplit('.', 1)[-1]
def construct_checksum(level=logging.ERROR, class_name='', traceback='', message='', **kwargs):
checksum = md5_constructor(str(level))
checksum.update(class_name or '')
if 'data' in kwargs and kwargs['data'] and '__sentry__' in kwargs['data'] and 'frames' in kwargs['data']['__sentry__']:
frames = kwargs['data']['__sentry__']['frames']
for frame in frames:
checksum.update(frame['module'])
checksum.update(frame['function'])
elif traceback:
traceback = '\n'.join(traceback.split('\n')[:-3])
elif message:
if isinstance(message, unicode):
message = message.encode('utf-8', 'replace')
checksum.update(message)
return checksum.hexdigest()
def varmap(func, var, context=None):
if context is None:
context = {}
objid = id(var)
if objid in context:
return func('<...>')
context[objid] = 1
if isinstance(var, dict):
ret = dict((k, varmap(func, v, context)) for k, v in var.iteritems())
elif isinstance(var, (list, tuple)):
ret = [varmap(func, f, context) for f in var]
else:
ret = func(var)
del context[objid]
return ret
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 = {}
objid = id(value)
if objid in context:
return '<...>'
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 TypeError:
# We may be dealing with a namedtuple
ret = type(value)(transform_rec(o) for o in value[:])
elif isinstance(value, uuid.UUID):
ret = repr(value)
elif isinstance(value, dict):
ret = dict((str(k), transform_rec(v)) for k, v in value.iteritems())
elif isinstance(value, unicode):
ret = to_unicode(value)
elif isinstance(value, str):
try:
ret = str(value.decode('utf-8').encode('utf-8'))
except:
ret = to_unicode(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 not isinstance(value, (int, bool)) and 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 = value
del context[objid]
return ret
def to_unicode(value):
try:
value = unicode(force_unicode(value))
except (UnicodeEncodeError, UnicodeDecodeError):
value = '(Error decoding value)'
except Exception: # in some cases we get a different exception
try:
value = str(repr(type(value)))
except Exception:
value = '(Error decoding value)'
return value
def get_installed_apps():
"""
Generate a list of modules in settings.INSTALLED_APPS.
"""
out = set()
for app in django_settings.INSTALLED_APPS:
out.add(app)
return out
class _Missing(object):
def __repr__(self):
return 'no value'
def __reduce__(self):
return '_missing'
_missing = _Missing()
class cached_property(object):
# This is borrowed from werkzeug : http://bytebucket.org/mitsuhiko/werkzeug-main
"""A decorator that converts a function into a lazy property. The
function wrapped is called the first time to retrieve the result
and then that calculated result is used the next time you access
the value::
class Foo(object):
@cached_property
def foo(self):
# calculate something important here
return 42
The class has to have a `__dict__` in order for this property to
work.
.. versionchanged:: 0.6
the `writeable` attribute and parameter was deprecated. If a
cached property is writeable or not has to be documented now.
For performance reasons the implementation does not honor the
writeable setting and will always make the property writeable.
"""
# implementation detail: this property is implemented as non-data
# descriptor. non-data descriptors are only invoked if there is
# no entry with the same name in the instance's __dict__.
# this allows us to completely get rid of the access function call
# overhead. If one choses to invoke __get__ by hand the property
# will still work as expected because the lookup logic is replicated
# in __get__ for manual invocation.
def __init__(self, func, name=None, doc=None, writeable=False):
if writeable:
from warnings import warn
warn(DeprecationWarning('the writeable argument to the '
'cached property is a noop since 0.6 '
'because the property is writeable '
'by default for performance reasons'))
self.__name__ = name or func.__name__
self.__module__ = func.__module__
self.__doc__ = doc or func.__doc__
self.func = func
def __get__(self, obj, type=None):
if obj is None:
return self
value = obj.__dict__.get(self.__name__, _missing)
if value is _missing:
value = self.func(obj)
obj.__dict__[self.__name__] = value
return value
# We store a cache of module_name->version string to avoid
# continuous imports and lookups of modules
_VERSION_CACHE = {}
def get_versions(module_list=None):
if not module_list:
module_list = django_settings.INSTALLED_APPS + ('django',)
ext_module_list = set()
for m in module_list:
parts = m.split('.')
ext_module_list.update('.'.join(parts[:idx]) for idx in xrange(1, len(parts)+1))
versions = {}
for module_name in ext_module_list:
if module_name not in _VERSION_CACHE:
__import__(module_name)
app = sys.modules[module_name]
if hasattr(app, 'get_version'):
get_version = app.get_version
if callable(get_version):
version = get_version()
else:
version = get_version
elif hasattr(app, 'VERSION'):
version = app.VERSION
elif hasattr(app, '__version__'):
version = app.__version__
elif pkg_resources:
# pull version from pkg_resources if distro exists
try:
version = pkg_resources.get_distribution(module_name).version
except pkg_resources.DistributionNotFound:
version = None
else:
version = None
if isinstance(version, (list, tuple)):
version = '.'.join(str(o) for o in version)
_VERSION_CACHE[module_name] = version
else:
version = _VERSION_CACHE[module_name]
if version is None:
continue
versions[module_name] = version
return versions
def shorten(var):
var = transform(var)
if isinstance(var, basestring) and len(var) > settings.MAX_LENGTH_STRING:
var = var[:settings.MAX_LENGTH_STRING] + '...'
elif isinstance(var, (list, tuple, set, frozenset)) and len(var) > settings.MAX_LENGTH_LIST:
# TODO: we should write a real API for storing some metadata with vars when
# we get around to doing ref storage
# TODO: when we finish the above, we should also implement this for dicts
var = list(var)[:settings.MAX_LENGTH_LIST] + ['...', '(%d more elements)' % (len(var) - settings.MAX_LENGTH_LIST,)]
return var
def is_float(var):
try:
float(var)
except ValueError:
return False
return True
def get_signature(message, timestamp):
return hmac.new(settings.KEY, '%s %s' % (timestamp, message), sha_constructor).hexdigest()
def get_auth_header(signature, timestamp, client):
return 'Sentry sentry_signature=%s, sentry_timestamp=%s, sentry_client=%s' % (
signature,
timestamp,
sentry.VERSION,
)
def parse_auth_header(header):
return dict(map(lambda x: x.strip().split('='), header.split(' ', 1)[1].split(',')))
class MockDjangoRequest(HttpRequest):
GET = {}
POST = {}
META = {}
COOKIES = {}
FILES = {}
raw_post_data = ''
url = ''
path = '/'
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
def __repr__(self):
# Since this is called as part of error handling, we need to be very
# robust against potentially malformed input.
try:
get = pformat(self.GET)
except:
get = '<could not parse>'
try:
post = pformat(self.POST)
except:
post = '<could not parse>'
try:
cookies = pformat(self.COOKIES)
except:
cookies = '<could not parse>'
try:
meta = pformat(self.META)
except:
meta = '<could not parse>'
return '<Request\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s>' % \
(get, post, cookies, meta)
def build_absolute_uri(self): return self.url
def should_mail(group):
if int(group.level) < settings.MAIL_LEVEL:
return False
if settings.MAIL_INCLUDE_LOGGERS is not None and group.logger not in settings.MAIL_INCLUDE_LOGGERS:
return False
if settings.MAIL_EXCLUDE_LOGGERS and group.logger in settings.MAIL_EXCLUDE_LOGGERS:
return False
return True