##// END OF EJS Templates
api: support permanent logs in sentry api
ergo -
Show More
@@ -1,1703 +1,1593 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # AppEnlight Enterprise Edition, including its added features, Support
19 19 # services, and proprietary license terms, please see
20 20 # https://rhodecode.com/licenses/
21 21
22 22 import copy
23 23 import logging
24 24 import mock
25 25 import pyramid
26 26 import pytest
27 27 import sqlalchemy as sa
28 28 import webob
29 29
30 30 from datetime import datetime
31 31 from pyramid import testing
32 32
33 33
34 34 from appenlight.models import DBSession
35 35 from appenlight.lib.ext_json import json
36 36
37 37
38 38 log = logging.getLogger(__name__)
39 39
40 40
41 41 class DummyContext(object):
42 42 pass
43 43
44 44
45 45 @pytest.mark.usefixtures('base_app')
46 46 class BasicTest(object):
47 47 pass
48 48
49 49
50 50 @pytest.mark.usefixtures('base_app')
51 51 class TestMigration(object):
52 52 def test_migration(self):
53 53 assert 1 == 1
54 54
55 55
56 class TestAPIReports_0_4_Validation(object):
57 @pytest.mark.parametrize('dummy_json', ['', {}, [], None])
58 def test_no_payload(self, dummy_json):
59 import colander
60 from appenlight.validators import ReportListSchema_0_4
61 utcnow = datetime.utcnow()
62 schema = ReportListSchema_0_4().bind(utcnow=utcnow)
63 with pytest.raises(colander.Invalid):
64 schema.deserialize(dummy_json)
65
66 def test_minimal_payload(self, report_04_schema):
67 dummy_json = [{}]
68 import colander
69 from appenlight.validators import ReportListSchema_0_4
70 utcnow = datetime.utcnow()
71 schema = ReportListSchema_0_4().bind(utcnow=utcnow)
72 with pytest.raises(colander.Invalid):
73 schema.deserialize(dummy_json)
74
75 def test_minimal_payload(self):
76 from appenlight.validators import ReportListSchema_0_4
77 dummy_json = [{'report_details': [{}]}]
78 utcnow = datetime.utcnow()
79 schema = ReportListSchema_0_4().bind(utcnow=utcnow)
80 deserialized = schema.deserialize(dummy_json)
81
82 expected_deserialization = [
83 {'error_type': '',
84 'language': 'unknown',
85 'report_details': [
86 {'username': '',
87 'traceback': None,
88 'extra': None,
89 'frameinfo': None,
90 'url': '',
91 'ip': None,
92 'start_time': utcnow,
93 'group_string': None,
94 'request': {},
95 'request_stats': None,
96 'end_time': None,
97 'request_id': '',
98 'message': '',
99 'slow_calls': [],
100 'user_agent': ''}],
101 'server': 'unknown',
102 'occurences': 1,
103 'priority': 5,
104 'view_name': '',
105 'client': 'unknown',
106 'http_status': 200,
107 'error': '',
108 'tags': None}
109 ]
110 assert deserialized == expected_deserialization
111
112 def test_full_payload(self):
113 import appenlight.tests.payload_examples as payload_examples
114 from appenlight.validators import ReportListSchema_0_4
115 utcnow = datetime.utcnow()
116 schema = ReportListSchema_0_4().bind(utcnow=utcnow)
117 PYTHON_PAYLOAD = copy.deepcopy(payload_examples.PYTHON_PAYLOAD_0_4)
118 utcnow = datetime.utcnow()
119 PYTHON_PAYLOAD["tags"] = [("foo", 1), ("action", "test"), ("baz", 1.1),
120 ("date",
121 utcnow.strftime('%Y-%m-%dT%H:%M:%S.0'))]
122 dummy_json = [PYTHON_PAYLOAD]
123
124 deserialized = schema.deserialize(dummy_json)
125 assert deserialized[0]['error'] == PYTHON_PAYLOAD['error']
126 assert deserialized[0]['language'] == PYTHON_PAYLOAD['language']
127 assert deserialized[0]['server'] == PYTHON_PAYLOAD['server']
128 assert deserialized[0]['priority'] == PYTHON_PAYLOAD['priority']
129 assert deserialized[0]['view_name'] == PYTHON_PAYLOAD['view_name']
130 assert deserialized[0]['client'] == PYTHON_PAYLOAD['client']
131 assert deserialized[0]['http_status'] == PYTHON_PAYLOAD['http_status']
132 assert deserialized[0]['error'] == PYTHON_PAYLOAD['error']
133 assert deserialized[0]['occurences'] == PYTHON_PAYLOAD['occurences']
134 first_detail = deserialized[0]['report_details'][0]
135 payload_detail = PYTHON_PAYLOAD['report_details'][0]
136 assert first_detail['username'] == payload_detail['username']
137 assert first_detail['traceback'] == payload_detail['traceback']
138 assert first_detail['url'] == payload_detail['url']
139 assert first_detail['ip'] == payload_detail['ip']
140 assert first_detail['start_time'].strftime('%Y-%m-%dT%H:%M:%S.0') == \
141 payload_detail['start_time']
142 assert first_detail['ip'] == payload_detail['ip']
143 assert first_detail['group_string'] is None
144 assert first_detail['request_stats'] == payload_detail['request_stats']
145 assert first_detail['end_time'].strftime('%Y-%m-%dT%H:%M:%S.0') == \
146 payload_detail['end_time']
147 assert first_detail['request_id'] == payload_detail['request_id']
148 assert first_detail['message'] == payload_detail['message']
149 assert first_detail['user_agent'] == payload_detail['user_agent']
150 slow_call = first_detail['slow_calls'][0]
151 expected_slow_call = payload_detail['slow_calls'][0]
152 assert slow_call['start'].strftime('%Y-%m-%dT%H:%M:%S.0') == \
153 expected_slow_call['start']
154 assert slow_call['end'].strftime('%Y-%m-%dT%H:%M:%S.0') == \
155 expected_slow_call['end']
156 assert slow_call['statement'] == expected_slow_call['statement']
157 assert slow_call['parameters'] == expected_slow_call['parameters']
158 assert slow_call['type'] == expected_slow_call['type']
159 assert slow_call['subtype'] == expected_slow_call['subtype']
160 assert slow_call['location'] == ''
161 assert deserialized[0]['tags'] == [
162 ('foo', 1), ('action', 'test'),
163 ('baz', 1.1), ('date', utcnow.strftime('%Y-%m-%dT%H:%M:%S.0'))]
164
165
166 56 class TestSentryProto_7(object):
167 57 def test_log_payload(self):
168 58 import appenlight.tests.payload_examples as payload_examples
169 59 from appenlight.lib.enums import ParsedSentryEventType
170 60 from appenlight.lib.utils.sentry import parse_sentry_event
171 61 event_dict, event_type = parse_sentry_event(
172 62 payload_examples.SENTRY_LOG_PAYLOAD_7)
173 63 assert ParsedSentryEventType.LOG == event_type
174 64 assert event_dict['log_level'] == 'CRITICAL'
175 65 assert event_dict['message'] == 'TEST from django logging'
176 66 assert event_dict['namespace'] == 'testlogger'
177 67 assert event_dict['request_id'] == '9a6172f2e6d2444582f83a6c333d9cfb'
178 68 assert event_dict['server'] == 'ergo-virtual-machine'
179 69 assert event_dict['date'] == datetime.utcnow().date().strftime(
180 70 '%Y-%m-%dT%H:%M:%SZ')
181 71 tags = [('site', 'example.com'),
182 72 ('sys.argv', ["'manage.py'", "'runserver'"]),
183 73 ('price', 6),
184 74 ('tag', "'extra'"),
185 75 ('dupa', True),
186 76 ('project', 'sentry'),
187 77 ('sentry_culprit', 'testlogger in index'),
188 78 ('sentry_language', 'python'),
189 79 ('sentry_release', 'test')]
190 80 assert sorted(event_dict['tags']) == sorted(tags)
191 81
192 82 def test_report_payload(self):
193 83 import appenlight.tests.payload_examples as payload_examples
194 84 from appenlight.lib.enums import ParsedSentryEventType
195 85 from appenlight.lib.utils.sentry import parse_sentry_event
196 86 utcnow = datetime.utcnow().date().strftime('%Y-%m-%dT%H:%M:%SZ')
197 87 event_dict, event_type = parse_sentry_event(
198 88 payload_examples.SENTRY_PYTHON_PAYLOAD_7)
199 89 assert ParsedSentryEventType.ERROR_REPORT == event_type
200 90 assert event_dict['client'] == 'sentry'
201 91 assert event_dict[
202 92 'error'] == 'Exception: test 500 ' \
203 93 '\u0142\xf3\u201c\u0107\u201c\u0107\u017c\u0105'
204 94 assert event_dict['language'] == 'python'
205 95 assert event_dict['ip'] == '127.0.0.1'
206 96 assert event_dict['request_id'] == '9fae652c8c1c4d6a8eee09260f613a98'
207 97 assert event_dict['server'] == 'ergo-virtual-machine'
208 98 assert event_dict['start_time'] == utcnow
209 99 assert event_dict['url'] == 'http://127.0.0.1:8000/error'
210 100 assert event_dict['user_agent'] == 'Mozilla/5.0 (X11; Linux x86_64) ' \
211 101 'AppleWebKit/537.36 (KHTML, ' \
212 102 'like Gecko) Chrome/47.0.2526.106 ' \
213 103 'Safari/537.36'
214 104 assert event_dict['view_name'] == 'djangoapp.views in error'
215 105 tags = [('site', 'example.com'), ('sentry_release', 'test')]
216 106 assert sorted(event_dict['tags']) == sorted(tags)
217 107 extra = [('sys.argv', ["'manage.py'", "'runserver'"]),
218 108 ('project', 'sentry')]
219 109 assert sorted(event_dict['extra']) == sorted(extra)
220 110 request = event_dict['request']
221 111 assert request['url'] == 'http://127.0.0.1:8000/error'
222 112 assert request['cookies'] == {'appenlight': 'X'}
223 113 assert request['data'] is None
224 114 assert request['method'] == 'GET'
225 115 assert request['query_string'] == ''
226 116 assert request['env'] == {'REMOTE_ADDR': '127.0.0.1',
227 117 'SERVER_NAME': 'localhost',
228 118 'SERVER_PORT': '8000'}
229 119 assert request['headers'] == {
230 120 'Accept': 'text/html,application/xhtml+xml,'
231 121 'application/xml;q=0.9,image/webp,*/*;q=0.8',
232 122 'Accept-Encoding': 'gzip, deflate, sdch',
233 123 'Accept-Language': 'en-US,en;q=0.8,pl;q=0.6',
234 124 'Connection': 'keep-alive',
235 125 'Content-Length': '',
236 126 'Content-Type': 'text/plain',
237 127 'Cookie': 'appenlight=X',
238 128 'Dnt': '1',
239 129 'Host': '127.0.0.1:8000',
240 130 'Upgrade-Insecure-Requests': '1',
241 131 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) '
242 132 'AppleWebKit/537.36 (KHTML, like Gecko) '
243 133 'Chrome/47.0.2526.106 Safari/537.36'}
244 134 traceback = event_dict['traceback']
245 135 assert traceback[0]['cline'] == 'response = wrapped_callback(request, ' \
246 136 '*callback_args, **callback_kwargs)'
247 137 assert traceback[0]['file'] == 'django/core/handlers/base.py'
248 138 assert traceback[0]['fn'] == 'get_response'
249 139 assert traceback[0]['line'] == 111
250 140 assert traceback[0]['module'] == 'django.core.handlers.base'
251 141
252 142 assert traceback[1]['cline'] == "raise Exception(u'test 500 " \
253 143 "\u0142\xf3\u201c\u0107\u201c\u0107" \
254 144 "\u017c\u0105')"
255 145 assert traceback[1]['file'] == 'djangoapp/views.py'
256 146 assert traceback[1]['fn'] == 'error'
257 147 assert traceback[1]['line'] == 84
258 148 assert traceback[1]['module'] == 'djangoapp.views'
259 149 assert sorted(traceback[1]['vars']) == sorted([
260 150 ('c',
261 151 '<sqlite3.Cursor object at 0x7fe7c82af8f0>'),
262 152 ('request',
263 153 '<WSGIRequest at 0x140633490316304>'),
264 154 ('conn',
265 155 '<sqlite3.Connection object at 0x7fe7c8b23bf8>')])
266 156
267 157
268 158 class TestAPIReports_0_5_Validation(object):
269 159 @pytest.mark.parametrize('dummy_json', ['', {}, [], None])
270 160 def test_no_payload(self, dummy_json):
271 161 import colander
272 162 from appenlight.validators import ReportListSchema_0_5
273 163 utcnow = datetime.utcnow()
274 164 schema = ReportListSchema_0_5().bind(utcnow=utcnow)
275 165 with pytest.raises(colander.Invalid):
276 166 schema.deserialize(dummy_json)
277 167
278 168 def test_minimal_payload(self):
279 169 dummy_json = [{}]
280 170 import colander
281 171 from appenlight.validators import ReportListSchema_0_5
282 172 utcnow = datetime.utcnow()
283 173 schema = ReportListSchema_0_5().bind(utcnow=utcnow)
284 174 with pytest.raises(colander.Invalid):
285 175 schema.deserialize(dummy_json)
286 176
287 177 def test_minimal_payload(self):
288 178 dummy_json = [{'report_details': [{}]}]
289 179 from appenlight.validators import ReportListSchema_0_5
290 180 utcnow = datetime.utcnow()
291 181 schema = ReportListSchema_0_5().bind(utcnow=utcnow)
292 182
293 183 deserialized = schema.deserialize(dummy_json)
294 184
295 185 expected_deserialization = [
296 186 {'language': 'unknown',
297 187 'server': 'unknown',
298 188 'occurences': 1,
299 189 'priority': 5,
300 190 'view_name': '',
301 191 'client': 'unknown',
302 192 'http_status': 200,
303 193 'error': '',
304 194 'tags': None,
305 195 'username': '',
306 196 'traceback': None,
307 197 'extra': None,
308 198 'url': '',
309 199 'ip': None,
310 200 'start_time': utcnow,
311 201 'group_string': None,
312 202 'request': {},
313 203 'request_stats': None,
314 204 'end_time': None,
315 205 'request_id': '',
316 206 'message': '',
317 207 'slow_calls': [],
318 208 'user_agent': ''
319 209 }
320 210 ]
321 211 assert deserialized == expected_deserialization
322 212
323 213 def test_full_payload(self):
324 214 import appenlight.tests.payload_examples as payload_examples
325 215 from appenlight.validators import ReportListSchema_0_5
326 216 PYTHON_PAYLOAD = copy.deepcopy(payload_examples.PYTHON_PAYLOAD_0_5)
327 217 utcnow = datetime.utcnow()
328 218 schema = ReportListSchema_0_5().bind(utcnow=utcnow)
329 219 PYTHON_PAYLOAD["tags"] = [("foo", 1), ("action", "test"), ("baz", 1.1),
330 220 ("date",
331 221 utcnow.strftime('%Y-%m-%dT%H:%M:%S.0'))]
332 222 dummy_json = [PYTHON_PAYLOAD]
333 223 deserialized = schema.deserialize(dummy_json)[0]
334 224 assert deserialized['error'] == PYTHON_PAYLOAD['error']
335 225 assert deserialized['language'] == PYTHON_PAYLOAD['language']
336 226 assert deserialized['server'] == PYTHON_PAYLOAD['server']
337 227 assert deserialized['priority'] == PYTHON_PAYLOAD['priority']
338 228 assert deserialized['view_name'] == PYTHON_PAYLOAD['view_name']
339 229 assert deserialized['client'] == PYTHON_PAYLOAD['client']
340 230 assert deserialized['http_status'] == PYTHON_PAYLOAD['http_status']
341 231 assert deserialized['error'] == PYTHON_PAYLOAD['error']
342 232 assert deserialized['occurences'] == PYTHON_PAYLOAD['occurences']
343 233 assert deserialized['username'] == PYTHON_PAYLOAD['username']
344 234 assert deserialized['traceback'] == PYTHON_PAYLOAD['traceback']
345 235 assert deserialized['url'] == PYTHON_PAYLOAD['url']
346 236 assert deserialized['ip'] == PYTHON_PAYLOAD['ip']
347 237 assert deserialized['start_time'].strftime('%Y-%m-%dT%H:%M:%S.0') == \
348 238 PYTHON_PAYLOAD['start_time']
349 239 assert deserialized['ip'] == PYTHON_PAYLOAD['ip']
350 240 assert deserialized['group_string'] is None
351 241 assert deserialized['request_stats'] == PYTHON_PAYLOAD['request_stats']
352 242 assert deserialized['end_time'].strftime('%Y-%m-%dT%H:%M:%S.0') == \
353 243 PYTHON_PAYLOAD['end_time']
354 244 assert deserialized['request_id'] == PYTHON_PAYLOAD['request_id']
355 245 assert deserialized['message'] == PYTHON_PAYLOAD['message']
356 246 assert deserialized['user_agent'] == PYTHON_PAYLOAD['user_agent']
357 247 assert deserialized['slow_calls'][0]['start'].strftime(
358 248 '%Y-%m-%dT%H:%M:%S.0') == PYTHON_PAYLOAD['slow_calls'][0][
359 249 'start']
360 250 assert deserialized['slow_calls'][0]['end'].strftime(
361 251 '%Y-%m-%dT%H:%M:%S.0') == PYTHON_PAYLOAD['slow_calls'][0][
362 252 'end']
363 253 assert deserialized['slow_calls'][0]['statement'] == \
364 254 PYTHON_PAYLOAD['slow_calls'][0]['statement']
365 255 assert deserialized['slow_calls'][0]['parameters'] == \
366 256 PYTHON_PAYLOAD['slow_calls'][0]['parameters']
367 257 assert deserialized['slow_calls'][0]['type'] == \
368 258 PYTHON_PAYLOAD['slow_calls'][0]['type']
369 259 assert deserialized['slow_calls'][0]['subtype'] == \
370 260 PYTHON_PAYLOAD['slow_calls'][0]['subtype']
371 261 assert deserialized['slow_calls'][0]['location'] == ''
372 262 assert deserialized['tags'] == [
373 263 ('foo', 1), ('action', 'test'),
374 264 ('baz', 1.1), ('date', utcnow.strftime('%Y-%m-%dT%H:%M:%S.0'))]
375 265
376 266
377 267 @pytest.mark.usefixtures('log_schema')
378 268 class TestAPILogsValidation(object):
379 269 @pytest.mark.parametrize('dummy_json', ['', {}, [], None])
380 270 def test_no_payload(self, dummy_json, log_schema):
381 271 import colander
382 272
383 273 with pytest.raises(colander.Invalid):
384 274 log_schema.deserialize(dummy_json)
385 275
386 276 def test_minimal_payload(self, log_schema):
387 277 dummy_json = [{}]
388 278 deserialized = log_schema.deserialize(dummy_json)[0]
389 279 expected = {'log_level': 'UNKNOWN',
390 280 'namespace': '',
391 281 'server': 'unknown',
392 282 'request_id': '',
393 283 'primary_key': None,
394 284 'date': datetime.utcnow(),
395 285 'message': '',
396 286 'tags': None}
397 287 assert deserialized['log_level'] == expected['log_level']
398 288 assert deserialized['message'] == expected['message']
399 289 assert deserialized['namespace'] == expected['namespace']
400 290 assert deserialized['request_id'] == expected['request_id']
401 291 assert deserialized['server'] == expected['server']
402 292 assert deserialized['tags'] == expected['tags']
403 293 assert deserialized['primary_key'] == expected['primary_key']
404 294
405 295 def test_normal_payload(self, log_schema):
406 296 import appenlight.tests.payload_examples as payload_examples
407 297 deserialized = log_schema.deserialize(payload_examples.LOG_EXAMPLES)[0]
408 298 expected = payload_examples.LOG_EXAMPLES[0]
409 299 assert deserialized['log_level'] == expected['log_level']
410 300 assert deserialized['message'] == expected['message']
411 301 assert deserialized['namespace'] == expected['namespace']
412 302 assert deserialized['request_id'] == expected['request_id']
413 303 assert deserialized['server'] == expected['server']
414 304 assert deserialized['date'].strftime('%Y-%m-%dT%H:%M:%S.%f') == \
415 305 expected['date']
416 306 assert deserialized['tags'][0][0] == "tag_name"
417 307 assert deserialized['tags'][0][1] == "tag_value"
418 308 assert deserialized['tags'][1][0] == "tag_name2"
419 309 assert deserialized['tags'][1][1] == 2
420 310
421 311 def test_normal_payload_date_without_microseconds(self, log_schema):
422 312 import appenlight.tests.payload_examples as payload_examples
423 313 LOG_EXAMPLE = copy.deepcopy(payload_examples.LOG_EXAMPLES)
424 314 LOG_EXAMPLE[0]['date'] = datetime.utcnow().strftime(
425 315 '%Y-%m-%dT%H:%M:%S')
426 316 deserialized = log_schema.deserialize(LOG_EXAMPLE)
427 317 assert deserialized[0]['date'].strftime('%Y-%m-%dT%H:%M:%S') == \
428 318 LOG_EXAMPLE[0]['date']
429 319
430 320 def test_normal_payload_date_without_seconds(self, log_schema):
431 321 import appenlight.tests.payload_examples as payload_examples
432 322 LOG_EXAMPLE = copy.deepcopy(payload_examples.LOG_EXAMPLES)
433 323 LOG_EXAMPLE[0]['date'] = datetime.utcnow().date().strftime(
434 324 '%Y-%m-%dT%H:%M')
435 325 deserialized = log_schema.deserialize(LOG_EXAMPLE)
436 326 assert deserialized[0]['date'].strftime('%Y-%m-%dT%H:%M') == \
437 327 LOG_EXAMPLE[0]['date']
438 328
439 329 def test_payload_empty_date(self, log_schema):
440 330 import appenlight.tests.payload_examples as payload_examples
441 331 LOG_EXAMPLE = copy.deepcopy(payload_examples.LOG_EXAMPLES)
442 332 LOG_EXAMPLE[0]['date'] = None
443 333 deserialized = log_schema.deserialize(LOG_EXAMPLE)
444 334 assert deserialized[0]['date'].strftime('%Y-%m-%dT%H:%M') is not None
445 335
446 336 def test_payload_no_date(self, log_schema):
447 337 import appenlight.tests.payload_examples as payload_examples
448 338 LOG_EXAMPLE = copy.deepcopy(payload_examples.LOG_EXAMPLES)
449 339 LOG_EXAMPLE[0].pop('date', None)
450 340 deserialized = log_schema.deserialize(LOG_EXAMPLE)
451 341 assert deserialized[0]['date'].strftime('%Y-%m-%dT%H:%M') is not None
452 342
453 343
454 344 @pytest.mark.usefixtures('general_metrics_schema')
455 345 class TestAPIGeneralMetricsValidation(object):
456 346 @pytest.mark.parametrize('dummy_json', ['', {}, [], None])
457 347 def test_no_payload(self, dummy_json, general_metrics_schema):
458 348 import colander
459 349
460 350 with pytest.raises(colander.Invalid):
461 351 general_metrics_schema.deserialize(dummy_json)
462 352
463 353 def test_minimal_payload(self, general_metrics_schema):
464 dummy_json = [{}]
354 dummy_json = [{'tags': [['counter_a', 15.5], ['counter_b', 63]]}]
465 355 deserialized = general_metrics_schema.deserialize(dummy_json)[0]
466 356 expected = {'namespace': '',
467 357 'server_name': 'unknown',
468 'tags': None,
358 'tags': [('counter_a', 15.5), ('counter_b', 63)],
469 359 'timestamp': datetime.utcnow()}
470 360 assert deserialized['namespace'] == expected['namespace']
471 361 assert deserialized['server_name'] == expected['server_name']
472 362 assert deserialized['tags'] == expected['tags']
473 363
474 364 def test_normal_payload(self, general_metrics_schema):
475 365 import appenlight.tests.payload_examples as payload_examples
476 366 dummy_json = [payload_examples.METRICS_PAYLOAD]
477 367 deserialized = general_metrics_schema.deserialize(dummy_json)[0]
478 368 expected = {'namespace': 'some.monitor',
479 369 'server_name': 'server.name',
480 370 'tags': [('usage_foo', 15.5), ('usage_bar', 63)],
481 371 'timestamp': datetime.utcnow()}
482 372 assert deserialized['namespace'] == expected['namespace']
483 373 assert deserialized['server_name'] == expected['server_name']
484 374 assert deserialized['tags'] == expected['tags']
485 375
486 376
487 377 @pytest.mark.usefixtures('request_metrics_schema')
488 378 class TestAPIRequestMetricsValidation(object):
489 379 @pytest.mark.parametrize('dummy_json', ['', {}, [], None])
490 380 def test_no_payload(self, dummy_json, request_metrics_schema):
491 381 import colander
492 382
493 383 with pytest.raises(colander.Invalid):
494 384 print(request_metrics_schema.deserialize(dummy_json))
495 385
496 386 def test_normal_payload(self, request_metrics_schema):
497 387 import appenlight.tests.payload_examples as payload_examples
498 388 dummy_json = payload_examples.REQUEST_METRICS_EXAMPLES
499 389 deserialized = request_metrics_schema.deserialize(dummy_json)[0]
500 390 expected = {'metrics': [('dir/module:func',
501 391 {'custom': 0.0,
502 392 'custom_calls': 0.0,
503 393 'main': 0.01664,
504 394 'nosql': 0.00061,
505 395 'nosql_calls': 23.0,
506 396 'remote': 0.0,
507 397 'remote_calls': 0.0,
508 398 'requests': 1,
509 399 'sql': 0.00105,
510 400 'sql_calls': 2.0,
511 401 'tmpl': 0.0,
512 402 'tmpl_calls': 0.0}),
513 403 ('SomeView.function',
514 404 {'custom': 0.0,
515 405 'custom_calls': 0.0,
516 406 'main': 0.647261,
517 407 'nosql': 0.306554,
518 408 'nosql_calls': 140.0,
519 409 'remote': 0.0,
520 410 'remote_calls': 0.0,
521 411 'requests': 28,
522 412 'sql': 0.0,
523 413 'sql_calls': 0.0,
524 414 'tmpl': 0.0,
525 415 'tmpl_calls': 0.0})],
526 416 'server': 'some.server.hostname',
527 417 'timestamp': datetime.utcnow()}
528 418 assert deserialized['server'] == expected['server']
529 419 metric = deserialized['metrics'][0]
530 420 expected_metric = expected['metrics'][0]
531 421 assert metric[0] == expected_metric[0]
532 422 assert sorted(metric[1].items()) == sorted(expected_metric[1].items())
533 423
534 424
535 425 @pytest.mark.usefixtures('default_application')
536 426 @pytest.mark.usefixtures('base_app', 'with_migrations', 'clean_tables')
537 427 class TestAPIReportsView(object):
538 428 def test_no_json_payload(self, default_application):
539 429 import colander
540 430 from appenlight.models.services.application import ApplicationService
541 431 from appenlight.views.api import reports_create
542 432
543 433 context = DummyContext()
544 434 context.resource = ApplicationService.by_id(1)
545 435 request = testing.DummyRequest(
546 436 headers={'Content-Type': 'application/json'})
547 437 request.unsafe_json_body = ''
548 438 request.context = context
549 439 route = mock.Mock()
550 440 route.name = 'api_reports'
551 441 request.matched_route = route
552 442 with pytest.raises(colander.Invalid):
553 443 response = reports_create(request)
554 444
555 445 def test_single_proper_json_0_5_payload(self):
556 446 import appenlight.tests.payload_examples as payload_examples
557 447 from appenlight.views.api import reports_create
558 448 from appenlight.models.services.application import ApplicationService
559 449 from appenlight.models.report_group import ReportGroup
560 450 route = mock.Mock()
561 451 route.name = 'api_reports'
562 452 request = pyramid.threadlocal.get_current_request()
563 453 context = DummyContext()
564 454 context.resource = ApplicationService.by_id(1)
565 455 request.context = context
566 456 request.matched_route = route
567 457 PYTHON_PAYLOAD = payload_examples.PYTHON_PAYLOAD_0_5
568 458 request.unsafe_json_body = [copy.deepcopy(PYTHON_PAYLOAD)]
569 459 reports_create(request)
570 460 query = DBSession.query(ReportGroup)
571 461 report = query.first()
572 462 assert query.count() == 1
573 463 assert report.total_reports == 1
574 464
575 465 def test_grouping_0_5(self):
576 466 import appenlight.tests.payload_examples as payload_examples
577 467 from appenlight.views.api import reports_create
578 468 from appenlight.models.services.application import ApplicationService
579 469 from appenlight.models.report_group import ReportGroup
580 470 route = mock.Mock()
581 471 route.name = 'api_reports'
582 472 request = pyramid.threadlocal.get_current_request()
583 473 context = DummyContext()
584 474 context.resource = ApplicationService.by_id(1)
585 475 request.context = context
586 476 request.matched_route = route
587 477 PYTHON_PAYLOAD = payload_examples.PYTHON_PAYLOAD_0_5
588 478 request.unsafe_json_body = [copy.deepcopy(PYTHON_PAYLOAD),
589 479 copy.deepcopy(PYTHON_PAYLOAD)]
590 480 reports_create(request)
591 481 query = DBSession.query(ReportGroup)
592 482 report = query.first()
593 483 assert query.count() == 1
594 484 assert report.total_reports == 2
595 485
596 486 def test_grouping_different_reports_0_5(self):
597 487 import appenlight.tests.payload_examples as payload_examples
598 488 from appenlight.views.api import reports_create
599 489 from appenlight.models.services.application import ApplicationService
600 490 from appenlight.models.report_group import ReportGroup
601 491 route = mock.Mock()
602 492 route.name = 'api_reports'
603 493 request = pyramid.threadlocal.get_current_request()
604 494 context = DummyContext()
605 495 context.resource = ApplicationService.by_id(1)
606 496 request.context = context
607 497 request.matched_route = route
608 498 PYTHON_PAYLOAD = payload_examples.PYTHON_PAYLOAD_0_5
609 499 PARSED_REPORT_404 = payload_examples.PARSED_REPORT_404
610 500 request.unsafe_json_body = [copy.deepcopy(PYTHON_PAYLOAD),
611 501 copy.deepcopy(PARSED_REPORT_404)]
612 502 reports_create(request)
613 503 query = DBSession.query(ReportGroup)
614 504 report = query.first()
615 505 assert query.count() == 2
616 506 assert report.total_reports == 1
617 507
618 508
619 509 @pytest.mark.usefixtures('default_application')
620 510 @pytest.mark.usefixtures('base_app', 'with_migrations', 'clean_tables')
621 511 class TestAirbrakeXMLView(object):
622 512
623 513 def test_normal_payload_parsing(self):
624 514 import datetime
625 515 import defusedxml.ElementTree as ElementTree
626 516 import appenlight.tests.payload_examples as payload_examples
627 517 from appenlight.lib.utils.airbrake import parse_airbrake_xml
628 518 from appenlight.validators import ReportListSchema_0_5
629 519
630 520 context = DummyContext()
631 521 request = testing.DummyRequest(
632 522 headers={'Content-Type': 'application/xml'})
633 523 request.context = context
634 524 request.context.possibly_public = False
635 525 root = ElementTree.fromstring(payload_examples.AIRBRAKE_RUBY_EXAMPLE)
636 526 request.context.airbrake_xml_etree = root
637 527 error_dict = parse_airbrake_xml(request)
638 528 schema = ReportListSchema_0_5().bind(utcnow=datetime.datetime.utcnow())
639 529 deserialized_report = schema.deserialize([error_dict])[0]
640 530 assert deserialized_report['client'] == 'Airbrake Notifier'
641 531 assert deserialized_report['error'] == 'NameError: undefined local variable or method `sdfdfdf\' for #<#<Class:0x000000039a8b90>:0x00000002c53df0>'
642 532 assert deserialized_report['http_status'] == 500
643 533 assert deserialized_report['language'] == 'unknown'
644 534 assert deserialized_report['message'] == ''
645 535 assert deserialized_report['occurences'] == 1
646 536 assert deserialized_report['priority'] == 5
647 537 d_request = deserialized_report['request']
648 538 assert d_request['GET'] == {'test': '1234'}
649 539 assert d_request['action_dispatch.request.parameters'] == {
650 540 'action': 'index',
651 541 'controller': 'welcome',
652 542 'test': '1234'}
653 543 assert deserialized_report['request_id'] == 'c11b2267f3ad8b00a1768cae35559fa1'
654 544 assert deserialized_report['server'] == 'ergo-desktop'
655 545 assert deserialized_report['traceback'][0] == {
656 546 'cline': 'block in start_thread',
657 547 'file': '/home/ergo/.rbenv/versions/1.9.3-p327/lib/ruby/1.9.1/webrick/server.rb',
658 548 'fn': 'block in start_thread',
659 549 'line': '191',
660 550 'module': '',
661 551 'vars': {}}
662 552 assert deserialized_report['traceback'][-1] == {
663 553 'cline': '_app_views_welcome_index_html_erb___2570061166873166679_31748940',
664 554 'file': '[PROJECT_ROOT]/app/views/welcome/index.html.erb',
665 555 'fn': '_app_views_welcome_index_html_erb___2570061166873166679_31748940',
666 556 'line': '3',
667 557 'module': '',
668 558 'vars': {}}
669 559 assert deserialized_report['url'] == 'http://0.0.0.0:3000/welcome/index?test=1234'
670 560 assert deserialized_report['view_name'] == 'welcome:index'
671 561
672 562 def test_normal_payload_view(self):
673 563 import defusedxml.ElementTree as ElementTree
674 564 import appenlight.tests.payload_examples as payload_examples
675 565
676 566 from appenlight.models.services.application import ApplicationService
677 567 from appenlight.views.api import airbrake_xml_compat
678 568
679 569 context = DummyContext()
680 570 context.resource = ApplicationService.by_id(1)
681 571 request = testing.DummyRequest(
682 572 headers={'Content-Type': 'application/xml'})
683 573 request.context = context
684 574 request.context.possibly_public = False
685 575 root = ElementTree.fromstring(payload_examples.AIRBRAKE_RUBY_EXAMPLE)
686 576 request.context.airbrake_xml_etree = root
687 577 route = mock.Mock()
688 578 route.name = 'api_airbrake'
689 579 request.matched_route = route
690 580 result = airbrake_xml_compat(request)
691 581 assert '<notice><id>' in result
692 582
693 583
694 584 @pytest.mark.usefixtures('default_application')
695 585 @pytest.mark.usefixtures('base_app', 'with_migrations', 'clean_tables')
696 586 class TestAPILogView(object):
697 587 def test_no_json_payload(self, base_app):
698 588 import colander
699 589 from appenlight.models.services.application import ApplicationService
700 590 from appenlight.views.api import logs_create
701 591
702 592 context = DummyContext()
703 593 context.resource = ApplicationService.by_id(1)
704 594 request = testing.DummyRequest(
705 595 headers={'Content-Type': 'application/json'})
706 596 request.context = context
707 597 request.registry = base_app.registry
708 598 request.unsafe_json_body = ''
709 599 route = mock.Mock()
710 600 route.name = 'api_logs'
711 601 request.matched_route = route
712 602 with pytest.raises(colander.Invalid):
713 603 response = logs_create(request)
714 604
715 605 def test_single_json_payload(self):
716 606 import appenlight.tests.payload_examples as payload_examples
717 607 from appenlight.models.log import Log
718 608 from appenlight.views.api import logs_create
719 609 from appenlight.models.services.application import ApplicationService
720 610 route = mock.Mock()
721 611 route.name = 'api_logs'
722 612 request = pyramid.threadlocal.get_current_request()
723 613 context = DummyContext()
724 614 context.resource = ApplicationService.by_id(1)
725 615 request.context = context
726 616 request.matched_route = route
727 617 request.unsafe_json_body = [copy.deepcopy(
728 618 payload_examples.LOG_EXAMPLES[0])]
729 619 logs_create(request)
730 620 query = DBSession.query(Log)
731 621 log = query.first()
732 622 assert query.count() == 1
733 623 assert log.message == "OMG ValueError happened"
734 624
735 625 def test_multiple_json_payload(self):
736 626 import appenlight.tests.payload_examples as payload_examples
737 627 from appenlight.models.log import Log
738 628 from appenlight.views.api import logs_create
739 629 from appenlight.models.services.application import ApplicationService
740 630 route = mock.Mock()
741 631 route.name = 'api_logs'
742 632 request = pyramid.threadlocal.get_current_request()
743 633 context = DummyContext()
744 634 context.resource = ApplicationService.by_id(1)
745 635 request.context = context
746 636 request.matched_route = route
747 637 LOG_PAYLOAD = payload_examples.LOG_EXAMPLES[0]
748 638 LOG_PAYLOAD2 = payload_examples.LOG_EXAMPLES[1]
749 639 request.unsafe_json_body = copy.deepcopy([LOG_PAYLOAD, LOG_PAYLOAD2])
750 640 logs_create(request)
751 641 query = DBSession.query(Log).order_by(sa.asc(Log.log_id))
752 642 assert query.count() == 2
753 643 assert query[0].message == "OMG ValueError happened"
754 644 assert query[1].message == "OMG ValueError happened2"
755 645
756 646 def test_public_key_rewriting(self):
757 647 import appenlight.tests.payload_examples as payload_examples
758 648 from appenlight.models.log import Log
759 649 from appenlight.views.api import logs_create
760 650 from appenlight.models.services.application import ApplicationService
761 651 route = mock.Mock()
762 652 route.name = 'api_logs'
763 653 request = pyramid.threadlocal.get_current_request()
764 654 context = DummyContext()
765 655 context.resource = ApplicationService.by_id(1)
766 656 request.context = context
767 657 request.matched_route = route
768 658
769 659 LOG_PAYLOAD = copy.deepcopy(payload_examples.LOG_EXAMPLES[0])
770 660 LOG_PAYLOAD2 = copy.deepcopy(payload_examples.LOG_EXAMPLES[1])
771 661 LOG_PAYLOAD['primary_key'] = 'X2'
772 662 LOG_PAYLOAD2['primary_key'] = 'X2'
773 663 request.unsafe_json_body = [LOG_PAYLOAD, LOG_PAYLOAD2]
774 664 logs_create(request)
775 665
776 666 query = DBSession.query(Log).order_by(sa.asc(Log.log_id))
777 667 assert query.count() == 1
778 668 assert query[0].message == "OMG ValueError happened2"
779 669
780 670 @pytest.mark.usefixtures('default_application')
781 671 @pytest.mark.usefixtures('base_app', 'with_migrations', 'clean_tables')
782 672 class TestAPIGeneralMetricsView(object):
783 673 def test_no_json_payload(self, base_app):
784 674 import colander
785 675 from appenlight.models.services.application import ApplicationService
786 676 from appenlight.views.api import general_metrics_create
787 677 route = mock.Mock()
788 678 route.name = 'api_general_metrics'
789 679 context = DummyContext()
790 680 context.resource = ApplicationService.by_id(1)
791 681 request = testing.DummyRequest(
792 682 headers={'Content-Type': 'application/json'})
793 683 request.context = context
794 684 request.registry = base_app.registry
795 685 request.unsafe_json_body = ''
796 686 request.matched_route = route
797 687 with pytest.raises(colander.Invalid):
798 688 general_metrics_create(request)
799 689
800 690 def test_single_json_payload(self):
801 691 import appenlight.tests.payload_examples as payload_examples
802 692 from appenlight.models.metric import Metric
803 693 from appenlight.views.api import general_metrics_create
804 694 from appenlight.models.services.application import ApplicationService
805 695 route = mock.Mock()
806 696 route.name = 'api_general_metric'
807 697 request = pyramid.threadlocal.get_current_request()
808 698 request.matched_route = route
809 699 context = DummyContext()
810 700 context.resource = ApplicationService.by_id(1)
811 701 request.context = context
812 702 request.unsafe_json_body = payload_examples.METRICS_PAYLOAD
813 703 general_metrics_create(request)
814 704 query = DBSession.query(Metric)
815 705 metric = query.first()
816 706 assert query.count() == 1
817 707 assert metric.namespace == 'some.monitor'
818 708
819 709 def test_multiple_json_payload(self):
820 710 import appenlight.tests.payload_examples as payload_examples
821 711 from appenlight.models.metric import Metric
822 712 from appenlight.views.api import general_metrics_create
823 713 from appenlight.models.services.application import ApplicationService
824 714 route = mock.Mock()
825 715 route.name = 'api_general_metrics'
826 716 request = pyramid.threadlocal.get_current_request()
827 717 request.matched_route = route
828 718 context = DummyContext()
829 719 context.resource = ApplicationService.by_id(1)
830 720 request.context = context
831 721 request.unsafe_json_body = [
832 722 copy.deepcopy(payload_examples.METRICS_PAYLOAD),
833 723 copy.deepcopy(payload_examples.METRICS_PAYLOAD),
834 724 ]
835 725 general_metrics_create(request)
836 726 query = DBSession.query(Metric)
837 727 metric = query.first()
838 728 assert query.count() == 2
839 729 assert metric.namespace == 'some.monitor'
840 730
841 731
842 732 class TestGroupingMessageReplacements(object):
843 733 def replace_default_repr_python(self):
844 734 test_str = '''
845 735 ConnectionError: ConnectionError((<urllib3.connection.HTTPConnection object at 0x7f87a0ba9fd0>, 'Connection to domain.gr timed out. (connect timeout=10)')) caused by: ConnectTimeoutError((<urllib3.connection.HTTPConnection object at 0x7f87a0ba9fd0>, 'Connection to domain.gr timed out. (connect timeout=10)'))
846 736 '''
847 737 regex = r'<(.*?) object at (.*?)>'
848 738
849 739
850 740 class TestRulesKeyGetter(object):
851 741 def test_default_dict_getter_top_key(self):
852 742 from appenlight.lib.rule import Rule
853 743 struct = {
854 744 "a": {
855 745 "b": 'b',
856 746 "c": {
857 747 "d": 'd',
858 748 "g": {
859 749 "h": 'h'
860 750 }
861 751 },
862 752 "e": 'e'
863 753 },
864 754 "f": 'f'
865 755 }
866 756 result = Rule.default_dict_struct_getter(struct, "a")
867 757 assert result == struct['a']
868 758
869 759 def test_default_dict_getter_sub_key(self):
870 760 from appenlight.lib.rule import Rule
871 761 struct = {
872 762 "a": {
873 763 "b": 'b',
874 764 "c": {
875 765 "d": 'd',
876 766 "g": {
877 767 "h": 'h'
878 768 }
879 769 },
880 770 "e": 'e'
881 771 },
882 772 "f": 'f'
883 773 }
884 774 result = Rule.default_dict_struct_getter(struct, 'a:b')
885 775 assert result == struct['a']['b']
886 776 result = Rule.default_dict_struct_getter(struct, 'a:c:d')
887 777 assert result == struct['a']['c']['d']
888 778
889 779 def test_default_obj_getter_top_key(self):
890 780 from appenlight.lib.rule import Rule
891 781 class TestStruct(object):
892 782 def __init__(self, a, b):
893 783 self.a = a
894 784 self.b = b
895 785
896 786 struct = TestStruct(a='a',
897 787 b=TestStruct(a='x', b='y'))
898 788 result = Rule.default_obj_struct_getter(struct, "a")
899 789 assert result == struct.a
900 790
901 791 def test_default_obj_getter_sub_key(self):
902 792 from appenlight.lib.rule import Rule
903 793 class TestStruct(object):
904 794 def __init__(self, name, a, b):
905 795 self.name = name
906 796 self.a = a
907 797 self.b = b
908 798
909 799 def __repr__(self):
910 800 return '<obj {}>'.format(self.name)
911 801
912 802 c = TestStruct('c', a=5, b='z')
913 803 b = TestStruct('b', a=c, b='y')
914 804 struct = TestStruct('a', a='a', b=b)
915 805 result = Rule.default_obj_struct_getter(struct, 'b:b')
916 806 assert result == struct.b.b
917 807 result = Rule.default_obj_struct_getter(struct, 'b:a:b')
918 808 assert result == struct.b.a.b
919 809
920 810
921 811 @pytest.mark.usefixtures('report_type_matrix')
922 812 class TestRulesParsing():
923 813 @pytest.mark.parametrize("op, struct_value, test_value, match_result", [
924 814 ('eq', 500, 500, True),
925 815 ('eq', 600, 500, False),
926 816 ('eq', 300, 500, False),
927 817 ('eq', "300", 500, False),
928 818 ('eq', "600", 500, False),
929 819 ('eq', "500", 500, True),
930 820 ('ne', 500, 500, False),
931 821 ('ne', 600, 500, True),
932 822 ('ne', 300, 500, True),
933 823 ('ne', "300", 500, True),
934 824 ('ne', "600", 500, True),
935 825 ('ne', "500", 500, False),
936 826 ('ge', 500, 500, True),
937 827 ('ge', 600, 500, True),
938 828 ('ge', 499, 500, False),
939 829 ('gt', 499, 500, False),
940 830 ('gt', 500, 500, False),
941 831 ('gt', 501, 500, True),
942 832 ('le', 499, 500, True),
943 833 ('le', 500, 500, True),
944 834 ('le', 501, 500, False),
945 835 ('lt', 499, 500, True),
946 836 ('lt', 500, 500, False),
947 837 ('lt', 501, 500, False),
948 838 ])
949 839 def test_single_op_int(self, op, struct_value, test_value, match_result,
950 840 report_type_matrix):
951 841 from appenlight.lib.rule import Rule
952 842 rule_config = {
953 843 "op": op,
954 844 "field": "http_status",
955 845 "value": test_value
956 846 }
957 847 rule = Rule(rule_config, report_type_matrix)
958 848
959 849 data = {
960 850 "http_status": struct_value
961 851 }
962 852 assert rule.match(data) is match_result
963 853
964 854 @pytest.mark.parametrize("op, struct_value, test_value, match_result", [
965 855 ('ge', "500.01", 500, True),
966 856 ('ge', "500.01", 500.02, False),
967 857 ('le', "500.01", 500.02, True)
968 858 ])
969 859 def test_single_op_float(self, op, struct_value, test_value, match_result,
970 860 report_type_matrix):
971 861 from appenlight.lib.rule import Rule
972 862 rule_config = {
973 863 "op": op,
974 864 "field": "duration",
975 865 "value": test_value
976 866 }
977 867 rule = Rule(rule_config, report_type_matrix)
978 868
979 869 data = {
980 870 "duration": struct_value
981 871 }
982 872 assert rule.match(data) is match_result
983 873
984 874 @pytest.mark.parametrize("op, struct_value, test_value, match_result", [
985 875 ('contains', 'foo bar baz', 'foo', True),
986 876 ('contains', 'foo bar baz', 'bar', True),
987 877 ('contains', 'foo bar baz', 'dupa', False),
988 878 ('startswith', 'foo bar baz', 'foo', True),
989 879 ('startswith', 'foo bar baz', 'bar', False),
990 880 ('endswith', 'foo bar baz', 'baz', True),
991 881 ('endswith', 'foo bar baz', 'bar', False),
992 882 ])
993 883 def test_single_op_string(self, op, struct_value, test_value,
994 884 match_result, report_type_matrix):
995 885 from appenlight.lib.rule import Rule
996 886 rule_config = {
997 887 "op": op,
998 888 "field": "error",
999 889 "value": test_value
1000 890 }
1001 891 rule = Rule(rule_config, report_type_matrix)
1002 892
1003 893 data = {
1004 894 "error": struct_value
1005 895 }
1006 896 assert rule.match(data) is match_result
1007 897
1008 898 @pytest.mark.parametrize("field, value, s_type", [
1009 899 ('field_unicode', 500, str),
1010 900 ('field_unicode', 500.0, str),
1011 901 ('field_unicode', "500", str),
1012 902 ('field_int', "500", int),
1013 903 ('field_int', 500, int),
1014 904 ('field_int', 500.0, int),
1015 905 ('field_float', "500", float),
1016 906 ('field_float', 500, float),
1017 907 ('field_float', 500.0, float),
1018 908 ])
1019 909 def test_type_normalization(self, field, value, s_type):
1020 910 from appenlight.lib.rule import Rule
1021 911 type_matrix = {
1022 912 'field_unicode': {"type": 'unicode'},
1023 913 'field_float': {"type": 'float'},
1024 914 'field_int': {"type": 'int'},
1025 915 }
1026 916
1027 917 rule = Rule({}, type_matrix)
1028 918 n_value = rule.normalized_type(field, value)
1029 919 assert isinstance(n_value, s_type) is True
1030 920
1031 921
1032 922 @pytest.mark.usefixtures('report_type_matrix')
1033 923 class TestNestedRuleParsing():
1034 924
1035 925 @pytest.mark.parametrize("data, result", [
1036 926 ({"http_status": 501, "group": {"priority": 7, "occurences": 11}},
1037 927 False),
1038 928 ({"http_status": 101, "group": {"priority": 7, "occurences": 11}},
1039 929 False),
1040 930 ({"http_status": 500, "group": {"priority": 1, "occurences": 11}},
1041 931 False),
1042 932 ({"http_status": 101, "group": {"priority": 3, "occurences": 5}},
1043 933 True),
1044 934 ])
1045 935 def test_NOT_rule(self, data, result, report_type_matrix):
1046 936 from appenlight.lib.rule import Rule
1047 937 rule_config = {
1048 938 "field": "__NOT__",
1049 939 "rules": [
1050 940 {
1051 941 "op": "ge",
1052 942 "field": "group:occurences",
1053 943 "value": "10"
1054 944 },
1055 945 {
1056 946 "op": "ge",
1057 947 "field": "group:priority",
1058 948 "value": "4"
1059 949 }
1060 950 ]
1061 951 }
1062 952
1063 953 rule = Rule(rule_config, report_type_matrix)
1064 954 assert rule.match(data) is result
1065 955
1066 956 @pytest.mark.parametrize("data, result", [
1067 957 ({"http_status": 501, "group": {"priority": 7, "occurences": 11}},
1068 958 True),
1069 959 ({"http_status": 101, "group": {"priority": 7, "occurences": 11}},
1070 960 True),
1071 961 ({"http_status": 500, "group": {"priority": 1, "occurences": 1}},
1072 962 True),
1073 963 ({"http_status": 101, "group": {"priority": 3, "occurences": 11}},
1074 964 False),
1075 965 ])
1076 966 def test_nested_OR_AND_rule(self, data, result, report_type_matrix):
1077 967 from appenlight.lib.rule import Rule
1078 968 rule_config = {
1079 969 "field": "__OR__",
1080 970 "rules": [
1081 971 {
1082 972 "field": "__AND__",
1083 973 "rules": [
1084 974 {
1085 975 "op": "ge",
1086 976 "field": "group:occurences",
1087 977 "value": "10"
1088 978 },
1089 979 {
1090 980 "op": "ge",
1091 981 "field": "group:priority",
1092 982 "value": "4"
1093 983 }
1094 984 ]
1095 985 },
1096 986 {
1097 987 "op": "eq",
1098 988 "field": "http_status",
1099 989 "value": "500"
1100 990 }
1101 991 ]
1102 992 }
1103 993
1104 994 rule = Rule(rule_config, report_type_matrix)
1105 995 assert rule.match(data) is result
1106 996
1107 997 @pytest.mark.parametrize("data, result", [
1108 998 ({"http_status": 501, "group": {"priority": 7, "occurences": 11}},
1109 999 True),
1110 1000 ({"http_status": 101, "group": {"priority": 7, "occurences": 11}},
1111 1001 True),
1112 1002 ({"http_status": 500, "group": {"priority": 1, "occurences": 1}},
1113 1003 True),
1114 1004 ({"http_status": 101, "group": {"priority": 3, "occurences": 1}},
1115 1005 False),
1116 1006 ])
1117 1007 def test_nested_OR_OR_rule(self, data, result, report_type_matrix):
1118 1008 from appenlight.lib.rule import Rule
1119 1009 rule_config = {
1120 1010 "field": "__OR__",
1121 1011 "rules": [
1122 1012 {"field": "__OR__",
1123 1013 "rules": [
1124 1014 {"op": "ge",
1125 1015 "field": "group:occurences",
1126 1016 "value": "10"
1127 1017 },
1128 1018 {"op": "ge",
1129 1019 "field": "group:priority",
1130 1020 "value": "4"
1131 1021 }
1132 1022 ]
1133 1023 },
1134 1024 {"op": "eq",
1135 1025 "field": "http_status",
1136 1026 "value": "500"
1137 1027 }
1138 1028 ]
1139 1029 }
1140 1030
1141 1031 rule = Rule(rule_config, report_type_matrix)
1142 1032 assert rule.match(data) is result
1143 1033
1144 1034 @pytest.mark.parametrize("data, result", [
1145 1035 ({"http_status": 500, "group": {"priority": 7, "occurences": 11}},
1146 1036 True),
1147 1037 ({"http_status": 101, "group": {"priority": 7, "occurences": 11}},
1148 1038 False),
1149 1039 ({"http_status": 500, "group": {"priority": 1, "occurences": 1}},
1150 1040 False),
1151 1041 ({"http_status": 101, "group": {"priority": 3, "occurences": 1}},
1152 1042 False),
1153 1043 ])
1154 1044 def test_nested_AND_AND_rule(self, data, result, report_type_matrix):
1155 1045 from appenlight.lib.rule import Rule
1156 1046 rule_config = {
1157 1047 "field": "__AND__",
1158 1048 "rules": [
1159 1049 {"field": "__AND__",
1160 1050 "rules": [
1161 1051 {"op": "ge",
1162 1052 "field": "group:occurences",
1163 1053 "value": "10"
1164 1054 },
1165 1055 {"op": "ge",
1166 1056 "field": "group:priority",
1167 1057 "value": "4"
1168 1058 }]
1169 1059 },
1170 1060 {"op": "eq",
1171 1061 "field": "http_status",
1172 1062 "value": "500"
1173 1063 }
1174 1064 ]
1175 1065 }
1176 1066
1177 1067 rule = Rule(rule_config, report_type_matrix)
1178 1068 assert rule.match(data) is result
1179 1069
1180 1070 @pytest.mark.parametrize("data, result", [
1181 1071 ({"http_status": 500, "group": {"priority": 7, "occurences": 11},
1182 1072 "url_path": '/test/register', "error": "foo test bar"}, True),
1183 1073 ({"http_status": 500, "group": {"priority": 7, "occurences": 11},
1184 1074 "url_path": '/test/register', "error": "foo INVALID bar"}, False),
1185 1075 ])
1186 1076 def test_nested_AND_AND_AND_rule(self, data, result, report_type_matrix):
1187 1077 from appenlight.lib.rule import Rule
1188 1078 rule_config = {
1189 1079 "field": "__AND__",
1190 1080 "rules": [
1191 1081 {"field": "__AND__",
1192 1082 "rules": [
1193 1083 {"op": "ge",
1194 1084 "field": "group:occurences",
1195 1085 "value": "10"
1196 1086 },
1197 1087 {"field": "__AND__",
1198 1088 "rules": [
1199 1089 {"op": "endswith",
1200 1090 "field": "url_path",
1201 1091 "value": "register"},
1202 1092 {"op": "contains",
1203 1093 "field": "error",
1204 1094 "value": "test"}]}]
1205 1095 },
1206 1096 {"op": "eq",
1207 1097 "field": "http_status",
1208 1098 "value": "500"
1209 1099 }
1210 1100 ]
1211 1101 }
1212 1102
1213 1103 rule = Rule(rule_config, report_type_matrix)
1214 1104 assert rule.match(data) is result
1215 1105
1216 1106 @pytest.mark.parametrize("data, result", [
1217 1107 ({"http_status": 500, "group": {"priority": 7, "occurences": 11},
1218 1108 "url_path": 6, "error": 3}, False),
1219 1109 ({"http_status": 500, "group": {"priority": 7, "occurences": 11},
1220 1110 "url_path": '/test/register', "error": "foo INVALID bar"}, True),
1221 1111 ])
1222 1112 def test_nested_AND_AND_OR_rule(self, data, result, report_type_matrix):
1223 1113 from appenlight.lib.rule import Rule
1224 1114 rule_config = {
1225 1115 "field": "__AND__",
1226 1116 "rules": [
1227 1117 {"field": "__AND__",
1228 1118 "rules": [
1229 1119 {"op": "ge",
1230 1120 "field": "group:occurences",
1231 1121 "value": "10"
1232 1122 },
1233 1123 {"field": "__OR__",
1234 1124 "rules": [
1235 1125 {"op": "endswith",
1236 1126 "field": "url_path",
1237 1127 "value": "register"
1238 1128 },
1239 1129 {"op": "contains",
1240 1130 "field": "error",
1241 1131 "value": "test"
1242 1132 }]}]
1243 1133 },
1244 1134 {"op": "eq",
1245 1135 "field": "http_status",
1246 1136 "value": "500"
1247 1137 }
1248 1138 ]
1249 1139 }
1250 1140
1251 1141 rule = Rule(rule_config, report_type_matrix)
1252 1142 assert rule.match(data) is result
1253 1143
1254 1144 @pytest.mark.parametrize("op, field, value, should_fail", [
1255 1145 ('eq', 'http_status', "1", False),
1256 1146 ('ne', 'http_status', "1", False),
1257 1147 ('ne', 'http_status', "foo", True),
1258 1148 ('startswith', 'http_status', "1", True),
1259 1149 ('eq', 'group:priority', "1", False),
1260 1150 ('ne', 'group:priority', "1", False),
1261 1151 ('ge', 'group:priority', "1", False),
1262 1152 ('le', 'group:priority', "1", False),
1263 1153 ('startswith', 'group:priority', "1", True),
1264 1154 ('eq', 'url_domain', "1", False),
1265 1155 ('ne', 'url_domain', "1", False),
1266 1156 ('startswith', 'url_domain', "1", False),
1267 1157 ('endswith', 'url_domain', "1", False),
1268 1158 ('contains', 'url_domain', "1", False),
1269 1159 ('ge', 'url_domain', "1", True),
1270 1160 ('eq', 'url_path', "1", False),
1271 1161 ('ne', 'url_path', "1", False),
1272 1162 ('startswith', 'url_path', "1", False),
1273 1163 ('endswith', 'url_path', "1", False),
1274 1164 ('contains', 'url_path', "1", False),
1275 1165 ('ge', 'url_path', "1", True),
1276 1166 ('eq', 'error', "1", False),
1277 1167 ('ne', 'error', "1", False),
1278 1168 ('startswith', 'error', "1", False),
1279 1169 ('endswith', 'error', "1", False),
1280 1170 ('contains', 'error', "1", False),
1281 1171 ('ge', 'error', "1", True),
1282 1172 ('ge', 'url_path', "1", True),
1283 1173 ('eq', 'tags:server_name', "1", False),
1284 1174 ('ne', 'tags:server_name', "1", False),
1285 1175 ('startswith', 'tags:server_name', "1", False),
1286 1176 ('endswith', 'tags:server_name', "1", False),
1287 1177 ('contains', 'tags:server_name', "1", False),
1288 1178 ('ge', 'tags:server_name', "1", True),
1289 1179 ('contains', 'traceback', "1", False),
1290 1180 ('ge', 'traceback', "1", True),
1291 1181 ('eq', 'group:occurences', "1", False),
1292 1182 ('ne', 'group:occurences', "1", False),
1293 1183 ('ge', 'group:occurences', "1", False),
1294 1184 ('le', 'group:occurences', "1", False),
1295 1185 ('contains', 'group:occurences', "1", True),
1296 1186 ])
1297 1187 def test_rule_validation(self, op, field, value, should_fail,
1298 1188 report_type_matrix):
1299 1189 import colander
1300 1190 from appenlight.validators import build_rule_schema
1301 1191 rule_config = {
1302 1192 "op": op,
1303 1193 "field": field,
1304 1194 "value": value
1305 1195 }
1306 1196
1307 1197 schema = build_rule_schema(rule_config, report_type_matrix)
1308 1198 if should_fail:
1309 1199 with pytest.raises(colander.Invalid):
1310 1200 schema.deserialize(rule_config)
1311 1201 else:
1312 1202 schema.deserialize(rule_config)
1313 1203
1314 1204 def test_nested_proper_rule_validation(self, report_type_matrix):
1315 1205 from appenlight.validators import build_rule_schema
1316 1206 rule_config = {
1317 1207 "field": "__AND__",
1318 1208 "rules": [
1319 1209 {
1320 1210 "field": "__AND__",
1321 1211 "rules": [
1322 1212 {
1323 1213 "op": "ge",
1324 1214 "field": "group:occurences",
1325 1215 "value": "10"
1326 1216 },
1327 1217 {
1328 1218 "field": "__OR__",
1329 1219 "rules": [
1330 1220 {
1331 1221 "op": "endswith",
1332 1222 "field": "url_path",
1333 1223 "value": "register"
1334 1224 },
1335 1225 {
1336 1226 "op": "contains",
1337 1227 "field": "error",
1338 1228 "value": "test"
1339 1229 }
1340 1230 ]
1341 1231 }
1342 1232 ]
1343 1233 },
1344 1234 {
1345 1235 "op": "eq",
1346 1236 "field": "http_status",
1347 1237 "value": "500"
1348 1238 }
1349 1239 ]
1350 1240 }
1351 1241
1352 1242 schema = build_rule_schema(rule_config, report_type_matrix)
1353 1243 deserialized = schema.deserialize(rule_config)
1354 1244
1355 1245 def test_nested_bad_rule_validation(self, report_type_matrix):
1356 1246 import colander
1357 1247 from appenlight.validators import build_rule_schema
1358 1248 rule_config = {
1359 1249 "field": "__AND__",
1360 1250 "rules": [
1361 1251 {
1362 1252 "field": "__AND__",
1363 1253 "rules": [
1364 1254 {
1365 1255 "op": "ge",
1366 1256 "field": "group:occurences",
1367 1257 "value": "10"
1368 1258 },
1369 1259 {
1370 1260 "field": "__OR__",
1371 1261 "rules": [
1372 1262 {
1373 1263 "op": "gt",
1374 1264 "field": "url_path",
1375 1265 "value": "register"
1376 1266 },
1377 1267 {
1378 1268 "op": "contains",
1379 1269 "field": "error",
1380 1270 "value": "test"
1381 1271 }
1382 1272 ]
1383 1273 }
1384 1274 ]
1385 1275 },
1386 1276 {
1387 1277 "op": "eq",
1388 1278 "field": "http_status",
1389 1279 "value": "500"
1390 1280 }
1391 1281 ]
1392 1282 }
1393 1283
1394 1284 schema = build_rule_schema(rule_config, report_type_matrix)
1395 1285 with pytest.raises(colander.Invalid):
1396 1286 deserialized = schema.deserialize(rule_config)
1397 1287
1398 1288 def test_config_manipulator(self):
1399 1289 from appenlight.lib.rule import Rule
1400 1290 type_matrix = {
1401 1291 'a': {"type": 'int',
1402 1292 "ops": ('eq', 'ne', 'ge', 'le',)},
1403 1293 'b': {"type": 'int',
1404 1294 "ops": ('eq', 'ne', 'ge', 'le',)},
1405 1295 }
1406 1296 rule_config = {
1407 1297 "field": "__OR__",
1408 1298 "rules": [
1409 1299 {
1410 1300 "field": "__OR__",
1411 1301 "rules": [
1412 1302 {
1413 1303 "op": "ge",
1414 1304 "field": "a",
1415 1305 "value": "10"
1416 1306 }
1417 1307 ]
1418 1308 },
1419 1309 {
1420 1310 "op": "eq",
1421 1311 "field": "b",
1422 1312 "value": "500"
1423 1313 }
1424 1314 ]
1425 1315 }
1426 1316
1427 1317 def rule_manipulator(rule):
1428 1318 if 'value' in rule.config:
1429 1319 rule.config['value'] = "1"
1430 1320
1431 1321 rule = Rule(rule_config, type_matrix,
1432 1322 config_manipulator=rule_manipulator)
1433 1323 rule.match({"a": 1,
1434 1324 "b": "2"})
1435 1325 assert rule.config['rules'][0]['rules'][0]['value'] == "1"
1436 1326 assert rule.config['rules'][1]['value'] == "1"
1437 1327 assert rule.type_matrix["b"]['type'] == "int"
1438 1328
1439 1329 def test_dynamic_config_manipulator(self):
1440 1330 from appenlight.lib.rule import Rule
1441 1331 rule_config = {
1442 1332 "field": "__OR__",
1443 1333 "rules": [
1444 1334 {
1445 1335 "field": "__OR__",
1446 1336 "rules": [
1447 1337 {
1448 1338 "op": "ge",
1449 1339 "field": "a",
1450 1340 "value": "10"
1451 1341 }
1452 1342 ]
1453 1343 },
1454 1344 {
1455 1345 "op": "eq",
1456 1346 "field": "b",
1457 1347 "value": "500"
1458 1348 }
1459 1349 ]
1460 1350 }
1461 1351
1462 1352 def rule_manipulator(rule):
1463 1353 rule.type_matrix = {
1464 1354 'a': {"type": 'int',
1465 1355 "ops": ('eq', 'ne', 'ge', 'le',)},
1466 1356 'b': {"type": 'unicode',
1467 1357 "ops": ('eq', 'ne', 'ge', 'le',)},
1468 1358 }
1469 1359
1470 1360 if 'value' in rule.config:
1471 1361 if rule.config['field'] == 'a':
1472 1362 rule.config['value'] = "1"
1473 1363 elif rule.config['field'] == 'b':
1474 1364 rule.config['value'] = "2"
1475 1365
1476 1366 rule = Rule(rule_config, {},
1477 1367 config_manipulator=rule_manipulator)
1478 1368 rule.match({"a": 11,
1479 1369 "b": "55"})
1480 1370 assert rule.config['rules'][0]['rules'][0]['value'] == "1"
1481 1371 assert rule.config['rules'][1]['value'] == "2"
1482 1372 assert rule.type_matrix["b"]['type'] == "unicode"
1483 1373
1484 1374
1485 1375 @pytest.mark.usefixtures('base_app', 'with_migrations')
1486 1376 class TestViewsWithForms(object):
1487 1377 def test_bad_csrf(self):
1488 1378 from appenlight.forms import CSRFException
1489 1379 from appenlight.views.index import register
1490 1380 post_data = {'dupa': 'dupa'}
1491 1381 request = testing.DummyRequest(post=post_data)
1492 1382 request.POST = webob.multidict.MultiDict(request.POST)
1493 1383 with pytest.raises(CSRFException):
1494 1384 register(request)
1495 1385
1496 1386 def test_proper_csrf(self):
1497 1387 from appenlight.views.index import register
1498 1388 request = pyramid.threadlocal.get_current_request()
1499 1389 post_data = {'dupa': 'dupa',
1500 1390 'csrf_token': request.session.get_csrf_token()}
1501 1391 request = testing.DummyRequest(post=post_data)
1502 1392 request.POST = webob.multidict.MultiDict(request.POST)
1503 1393 result = register(request)
1504 1394 assert result['form'].errors['email'][0] == 'This field is required.'
1505 1395
1506 1396
1507 1397 @pytest.mark.usefixtures('base_app', 'with_migrations', 'default_data')
1508 1398 class TestRegistration(object):
1509 1399 def test_invalid_form(self):
1510 1400 from appenlight.views.index import register
1511 1401 request = pyramid.threadlocal.get_current_request()
1512 1402 post_data = {'user_name': '',
1513 1403 'user_password': '',
1514 1404 'email': '',
1515 1405 'csrf_token': request.session.get_csrf_token()}
1516 1406 request = testing.DummyRequest(post=post_data)
1517 1407 request.POST = webob.multidict.MultiDict(request.POST)
1518 1408 result = register(request)
1519 1409 assert result['form'].errors['user_name'][0] == \
1520 1410 'This field is required.'
1521 1411
1522 1412 def test_valid_form(self):
1523 1413 from appenlight.views.index import register
1524 1414 from ziggurat_foundations.models.services.user import UserService
1525 1415 request = pyramid.threadlocal.get_current_request()
1526 1416 post_data = {'user_name': 'foo',
1527 1417 'user_password': 'barr',
1528 1418 'email': 'test@test.foo',
1529 1419 'csrf_token': request.session.get_csrf_token()}
1530 1420 request = testing.DummyRequest(post=post_data)
1531 1421 request.add_flash_to_headers = mock.Mock()
1532 1422 request.POST = webob.multidict.MultiDict(request.POST)
1533 1423 assert UserService.by_user_name('foo') is None
1534 1424 register(request)
1535 1425 user = UserService.by_user_name('foo')
1536 1426 assert user.user_name == 'foo'
1537 1427 assert len(user.user_password) == 60
1538 1428
1539 1429
1540 1430 @pytest.mark.usefixtures('base_app', 'with_migrations', 'clean_tables',
1541 1431 'default_user')
1542 1432 class TestApplicationCreation(object):
1543 1433 def test_wrong_data(self):
1544 1434 import appenlight.views.applications as applications
1545 1435 from ziggurat_foundations.models.services.user import UserService
1546 1436 request = pyramid.threadlocal.get_current_request()
1547 1437 request.user = UserService.by_user_name('testuser')
1548 1438 request.unsafe_json_body = {}
1549 1439 request.headers['X-XSRF-TOKEN'] = request.session.get_csrf_token()
1550 1440 response = applications.application_create(request)
1551 1441 assert response.code == 422
1552 1442
1553 1443 def test_proper_data(self):
1554 1444 import appenlight.views.applications as applications
1555 1445 from ziggurat_foundations.models.services.user import UserService
1556 1446
1557 1447 request = pyramid.threadlocal.get_current_request()
1558 1448 request.user = UserService.by_user_name('testuser')
1559 1449 request.unsafe_json_body = {"resource_name": "app name",
1560 1450 "domains": "foo"}
1561 1451 request.headers['X-XSRF-TOKEN'] = request.session.get_csrf_token()
1562 1452 app_dict = applications.application_create(request)
1563 1453 assert app_dict['public_key'] is not None
1564 1454 assert app_dict['api_key'] is not None
1565 1455 assert app_dict['resource_name'] == 'app name'
1566 1456 assert app_dict['owner_group_id'] is None
1567 1457 assert app_dict['resource_id'] is not None
1568 1458 assert app_dict['default_grouping'] == 'url_traceback'
1569 1459 assert app_dict['possible_permissions'] == ('view', 'update_reports')
1570 1460 assert app_dict['slow_report_threshold'] == 10
1571 1461 assert app_dict['owner_user_name'] == 'testuser'
1572 1462 assert app_dict['owner_user_id'] == request.user.id
1573 1463 assert app_dict['domains'] is 'foo'
1574 1464 assert app_dict['postprocessing_rules'] == []
1575 1465 assert app_dict['error_report_threshold'] == 10
1576 1466 assert app_dict['allow_permanent_storage'] is False
1577 1467 assert app_dict['resource_type'] == 'application'
1578 1468 assert app_dict['current_permissions'] == []
1579 1469
1580 1470
1581 1471 @pytest.mark.usefixtures('default_application')
1582 1472 @pytest.mark.usefixtures('base_app', 'with_migrations', 'clean_tables')
1583 1473 class TestAPISentryView(object):
1584 1474 def test_no_payload(self, default_application):
1585 1475 import colander
1586 1476 from appenlight.models.services.application import ApplicationService
1587 1477 from appenlight.views.api import sentry_compat
1588 1478 from appenlight.lib.request import JSONException
1589 1479
1590 1480 context = DummyContext()
1591 1481 context.resource = ApplicationService.by_id(1)
1592 1482 request = testing.DummyRequest(
1593 1483 headers={'Content-Type': 'application/json'})
1594 1484 request.unsafe_json_body = ''
1595 1485 request.context = context
1596 1486 route = mock.Mock()
1597 1487 route.name = 'api_sentry'
1598 1488 request.matched_route = route
1599 1489 with pytest.raises(JSONException):
1600 1490 sentry_compat(request)
1601 1491
1602 1492 def test_java_client_payload(self):
1603 1493 from appenlight.views.api import sentry_compat
1604 1494 from appenlight.models.services.application import ApplicationService
1605 1495 from appenlight.models.report_group import ReportGroup
1606 1496 route = mock.Mock()
1607 1497 route.name = 'api_sentry'
1608 1498 request = pyramid.threadlocal.get_current_request()
1609 1499 context = DummyContext()
1610 1500 context.resource = ApplicationService.by_id(1)
1611 1501 request.context = context
1612 1502 request.matched_route = route
1613 1503 request.body = b'eJy1UmFr2zAQ/S0T+7BCLOzYThp/C6xjG6SDLd/GCBf57Ki' \
1614 1504 b'RJSHJJiXkv+/UlC7p2kAZA33Ru6f33t1pz3BAHVayZhWr87' \
1615 1505 b'JMs+I6q3MsrifFep2vc1iXM1HMpgBTNmIdeg8tEvlmJ9AGa' \
1616 1506 b'fQ7goOkQoDOUmGcZpMkLZO0WGZFRadMiaHIR1EVnTMu3k3b' \
1617 1507 b'oiMgqJrXpgOpOVjLLTiPkWAVhMa4jih3MAAholfWyUDAksz' \
1618 1508 b'm1iopICbg8fWH52B8VWXZVYwHrWfV/jBipD2gW2no8CFMa5' \
1619 1509 b'JButCDSjoQG6mR6LgLDojPPn/7sbydL25ep34HGl+y3DiE+' \
1620 1510 b'lH0xXBXjMzFBsXW99SS7pWKYXRw91zqgK4BgZ4/DZVVP/cs' \
1621 1511 b'3NuzSZPfAKqP2Cdj4tw7U/cKH0fEFeiWQFqE2FIHAmMPjaN' \
1622 1512 b'Y/kHvbzY/JqdHUq9o/KxqQHkcsabX4piDuT4aK+pXG1ZNi/' \
1623 1513 b'IwOpEyruXC1LiB3vPO3BmOOxTUCIqv5LIg5H12oh9cf0l+P' \
1624 1514 b'MvP5P8kddgoFIEvMGzM5cRSD2aLJ6qTdHKm6nv9pPcRFba0' \
1625 1515 b'Kd0eleeCFuGN+9JZ9TaXIn/V5JYMBvxXg3L6PwzSE4dkfOb' \
1626 1516 b'w7CtfWmP85SdCs8OvA53fUV19cg=='
1627 1517 sentry_compat(request)
1628 1518 query = DBSession.query(ReportGroup)
1629 1519 report = query.first()
1630 1520 assert query.count() == 1
1631 1521 assert report.total_reports == 1
1632 1522
1633 1523 def test_ruby_client_payload(self):
1634 1524 from appenlight.views.api import sentry_compat
1635 1525 from appenlight.models.services.application import ApplicationService
1636 1526 from appenlight.models.report_group import ReportGroup
1637 1527 from appenlight.tests.payload_examples import SENTRY_RUBY_ENCODED
1638 1528 route = mock.Mock()
1639 1529 route.name = 'api_sentry'
1640 1530 request = testing.DummyRequest(
1641 1531 headers={'Content-Type': 'application/octet-stream',
1642 1532 'User-Agent': 'sentry-ruby/1.0.0',
1643 1533 'X-Sentry-Auth': 'Sentry sentry_version=5, '
1644 1534 'sentry_client=raven-ruby/1.0.0, '
1645 1535 'sentry_timestamp=1462378483, '
1646 1536 'sentry_key=xxx, sentry_secret=xxx'
1647 1537 })
1648 1538 context = DummyContext()
1649 1539 context.resource = ApplicationService.by_id(1)
1650 1540 request.context = context
1651 1541 request.matched_route = route
1652 1542 request.body = SENTRY_RUBY_ENCODED
1653 1543 sentry_compat(request)
1654 1544 query = DBSession.query(ReportGroup)
1655 1545 report = query.first()
1656 1546 assert query.count() == 1
1657 1547 assert report.total_reports == 1
1658 1548
1659 1549 def test_python_client_decoded_payload(self):
1660 1550 from appenlight.views.api import sentry_compat
1661 1551 from appenlight.models.services.application import ApplicationService
1662 1552 from appenlight.models.report_group import ReportGroup
1663 1553 from appenlight.tests.payload_examples import SENTRY_PYTHON_PAYLOAD_7
1664 1554 route = mock.Mock()
1665 1555 route.name = 'api_sentry'
1666 1556 request = pyramid.threadlocal.get_current_request()
1667 1557 context = DummyContext()
1668 1558 context.resource = ApplicationService.by_id(1)
1669 1559 request.context = context
1670 1560 request.matched_route = route
1671 1561 request.body = json.dumps(SENTRY_PYTHON_PAYLOAD_7).encode('utf8')
1672 1562 sentry_compat(request)
1673 1563 query = DBSession.query(ReportGroup)
1674 1564 report = query.first()
1675 1565 assert query.count() == 1
1676 1566 assert report.total_reports == 1
1677 1567
1678 1568 def test_python_client_encoded_payload(self):
1679 1569 from appenlight.views.api import sentry_compat
1680 1570 from appenlight.models.services.application import ApplicationService
1681 1571 from appenlight.models.report_group import ReportGroup
1682 1572 from appenlight.tests.payload_examples import SENTRY_PYTHON_ENCODED
1683 1573 route = mock.Mock()
1684 1574 route.name = 'api_sentry'
1685 1575 request = testing.DummyRequest(
1686 1576 headers={'Content-Type': 'application/octet-stream',
1687 1577 'Content-Encoding': 'deflate',
1688 1578 'User-Agent': 'sentry-ruby/1.0.0',
1689 1579 'X-Sentry-Auth': 'Sentry sentry_version=5, '
1690 1580 'sentry_client=raven-ruby/1.0.0, '
1691 1581 'sentry_timestamp=1462378483, '
1692 1582 'sentry_key=xxx, sentry_secret=xxx'
1693 1583 })
1694 1584 context = DummyContext()
1695 1585 context.resource = ApplicationService.by_id(1)
1696 1586 request.context = context
1697 1587 request.matched_route = route
1698 1588 request.body = SENTRY_PYTHON_ENCODED
1699 1589 sentry_compat(request)
1700 1590 query = DBSession.query(ReportGroup)
1701 1591 report = query.first()
1702 1592 assert query.count() == 1
1703 1593 assert report.total_reports == 1
@@ -1,743 +1,752 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # AppEnlight Enterprise Edition, including its added features, Support
19 19 # services, and proprietary license terms, please see
20 20 # https://rhodecode.com/licenses/
21 21
22 22 import datetime
23 23
24 24 import colander
25 25 from colander import null
26 26
27 27 # those keywords are here so we can distingush between searching for tags and
28 28 # normal properties of reports/logs
29 29 accepted_search_params = ['resource',
30 30 'request_id',
31 31 'start_date',
32 32 'end_date',
33 33 'page',
34 34 'min_occurences',
35 35 'http_status',
36 36 'priority',
37 37 'error',
38 38 'url_path',
39 39 'url_domain',
40 40 'report_status',
41 41 'min_duration',
42 42 'max_duration',
43 43 'message',
44 44 'level',
45 45 'namespace']
46 46
47 47
48 48 @colander.deferred
49 49 def deferred_utcnow(node, kw):
50 50 return kw['utcnow']
51 51
52 52
53 53 def lowercase_preparer(input_data):
54 54 """
55 55 Transforms a list of string entries to lowercase
56 56 Used in search query validation
57 57 """
58 58 if not input_data:
59 59 return input_data
60 60 return [x.lower() for x in input_data]
61 61
62 62
63 63 def shortener_factory(cutoff_size=32):
64 64 """
65 65 Limits the input data to specific character count
66 66 :arg cutoff_cutoff_size How much characters to store
67 67
68 68 """
69 69
70 70 def shortener(input_data):
71 71 if not input_data:
72 72 return input_data
73 73 else:
74 74 if isinstance(input_data, str):
75 75 return input_data[:cutoff_size]
76 76 else:
77 77 return input_data
78 78
79 79 return shortener
80 80
81 81
82 82 def cast_to_unicode_or_null(value):
83 83 if value is not colander.null:
84 84 return str(value)
85 85 return None
86 86
87 87
88 88 class NonTZDate(colander.DateTime):
89 89 """ Returns null for incorrect date format - also removes tz info"""
90 90
91 91 def deserialize(self, node, cstruct):
92 92 # disabled for now
93 93 # if cstruct and isinstance(cstruct, str):
94 94 # if ':' not in cstruct:
95 95 # cstruct += ':0.0'
96 96 # if '.' not in cstruct:
97 97 # cstruct += '.0'
98 98 value = super(NonTZDate, self).deserialize(node, cstruct)
99 99 if value:
100 100 return value.replace(tzinfo=None)
101 101 return value
102 102
103 103
104 104 class UnknownType(object):
105 105 """
106 106 Universal type that will accept a deserialized JSON object and store it unaltered
107 107 """
108 108
109 109 def serialize(self, node, appstruct):
110 110 if appstruct is null:
111 111 return null
112 112 return appstruct
113 113
114 114 def deserialize(self, node, cstruct):
115 115 if cstruct is null:
116 116 return null
117 117 return cstruct
118 118
119 119 def cstruct_children(self):
120 120 return []
121 121
122 122
123 123 # SLOW REPORT SCHEMA
124 124
125 125 def rewrite_type(input_data):
126 126 """
127 127 Fix for legacy appenlight clients
128 128 """
129 129 if input_data == 'remote_call':
130 130 return 'remote'
131 131 return input_data
132 132
133 133
134 134 class ExtraTupleSchema(colander.TupleSchema):
135 135 name = colander.SchemaNode(colander.String(),
136 136 validator=colander.Length(1, 64))
137 137 value = colander.SchemaNode(UnknownType(),
138 138 preparer=shortener_factory(512),
139 139 missing=None)
140 140
141 141
142 142 class ExtraSchemaList(colander.SequenceSchema):
143 143 tag = ExtraTupleSchema()
144 144 missing = None
145 145
146 146
147 147 class TagsTupleSchema(colander.TupleSchema):
148 148 name = colander.SchemaNode(colander.String(),
149 149 validator=colander.Length(1, 128))
150 150 value = colander.SchemaNode(UnknownType(),
151 151 preparer=shortener_factory(128),
152 152 missing=None)
153 153
154 154
155 155 class TagSchemaList(colander.SequenceSchema):
156 156 tag = TagsTupleSchema()
157 157 missing = None
158 158
159 159
160 160 class NumericTagsTupleSchema(colander.TupleSchema):
161 161 name = colander.SchemaNode(colander.String(),
162 162 validator=colander.Length(1, 128))
163 163 value = colander.SchemaNode(colander.Float(), missing=0)
164 164
165 165
166 166 class NumericTagSchemaList(colander.SequenceSchema):
167 167 tag = NumericTagsTupleSchema()
168 168 missing = None
169 169
170 170
171 171 class SlowCallSchema(colander.MappingSchema):
172 172 """
173 173 Validates slow call format in slow call list
174 174 """
175 175 start = colander.SchemaNode(NonTZDate())
176 176 end = colander.SchemaNode(NonTZDate())
177 177 statement = colander.SchemaNode(colander.String(), missing='')
178 178 parameters = colander.SchemaNode(UnknownType(), missing=None)
179 179 type = colander.SchemaNode(
180 180 colander.String(),
181 181 preparer=rewrite_type,
182 182 validator=colander.OneOf(
183 183 ['tmpl', 'sql', 'nosql', 'remote', 'unknown', 'custom']),
184 184 missing='unknown')
185 185 subtype = colander.SchemaNode(colander.String(),
186 186 validator=colander.Length(1, 16),
187 187 missing='unknown')
188 188 location = colander.SchemaNode(colander.String(),
189 189 validator=colander.Length(1, 255),
190 190 missing='')
191 191
192 192
193 193 def limited_date(node, value):
194 194 """ checks to make sure that the value is not older/newer than 2h """
195 hours = 2
196 min_time = datetime.datetime.utcnow() - datetime.timedelta(hours=72)
197 max_time = datetime.datetime.utcnow() + datetime.timedelta(hours=2)
195 past_hours = 72
196 future_hours = 2
197 min_time = datetime.datetime.utcnow() - datetime.timedelta(
198 hours=past_hours)
199 max_time = datetime.datetime.utcnow() + datetime.timedelta(
200 hours=future_hours)
198 201 if min_time > value:
199 msg = '%r is older from current UTC time by ' + str(hours) + ' hours.'
200 msg += ' Ask administrator to enable permanent logging for ' \
202 msg = '%r is older from current UTC time by ' + str(past_hours)
203 msg += ' hours. Ask administrator to enable permanent logging for ' \
201 204 'your application to store logs with dates in past.'
202 205 raise colander.Invalid(node, msg % value)
203 206 if max_time < value:
204 msg = '%r is newer from current UTC time by ' + str(hours) + ' hours'
205 msg += ' Ask administrator to enable permanent logging for ' \
207 msg = '%r is newer from current UTC time by ' + str(future_hours)
208 msg += ' hours. Ask administrator to enable permanent logging for ' \
206 209 'your application to store logs with dates in future.'
207 210 raise colander.Invalid(node, msg % value)
208 211
209 212
210 213 class SlowCallListSchema(colander.SequenceSchema):
211 214 """
212 215 Validates list of individual slow calls
213 216 """
214 217 slow_call = SlowCallSchema()
215 218
216 219
217 220 class RequestStatsSchema(colander.MappingSchema):
218 221 """
219 222 Validates format of requests statistics dictionary
220 223 """
221 224 main = colander.SchemaNode(colander.Float(), validator=colander.Range(0),
222 225 missing=0)
223 226 sql = colander.SchemaNode(colander.Float(), validator=colander.Range(0),
224 227 missing=0)
225 228 nosql = colander.SchemaNode(colander.Float(), validator=colander.Range(0),
226 229 missing=0)
227 230 remote = colander.SchemaNode(colander.Float(), validator=colander.Range(0),
228 231 missing=0)
229 232 tmpl = colander.SchemaNode(colander.Float(), validator=colander.Range(0),
230 233 missing=0)
231 234 custom = colander.SchemaNode(colander.Float(), validator=colander.Range(0),
232 235 missing=0)
233 236 sql_calls = colander.SchemaNode(colander.Float(),
234 237 validator=colander.Range(0),
235 238 missing=0)
236 239 nosql_calls = colander.SchemaNode(colander.Float(),
237 240 validator=colander.Range(0),
238 241 missing=0)
239 242 remote_calls = colander.SchemaNode(colander.Float(),
240 243 validator=colander.Range(0),
241 244 missing=0)
242 245 tmpl_calls = colander.SchemaNode(colander.Float(),
243 246 validator=colander.Range(0),
244 247 missing=0)
245 248 custom_calls = colander.SchemaNode(colander.Float(),
246 249 validator=colander.Range(0),
247 250 missing=0)
248 251
249 252
250 253 class FrameInfoVarSchema(colander.SequenceSchema):
251 254 """
252 255 Validates format of frame variables of a traceback
253 256 """
254 257 vars = colander.SchemaNode(UnknownType(),
255 258 validator=colander.Length(2, 2))
256 259
257 260
258 261 class FrameInfoSchema(colander.MappingSchema):
259 262 """
260 263 Validates format of a traceback line
261 264 """
262 265 cline = colander.SchemaNode(colander.String(), missing='')
263 266 module = colander.SchemaNode(colander.String(), missing='')
264 267 line = colander.SchemaNode(colander.String(), missing='')
265 268 file = colander.SchemaNode(colander.String(), missing='')
266 269 fn = colander.SchemaNode(colander.String(), missing='')
267 270 vars = FrameInfoVarSchema()
268 271
269 272
270 273 class FrameInfoListSchema(colander.SequenceSchema):
271 274 """
272 275 Validates format of list of traceback lines
273 276 """
274 277 frame = colander.SchemaNode(UnknownType())
275 278
276 279
277 280 class ReportDetailBaseSchema(colander.MappingSchema):
278 281 """
279 282 Validates format of report - ie. request parameters and stats for a request in report group
280 283 """
281 284 username = colander.SchemaNode(colander.String(),
282 285 preparer=[shortener_factory(255),
283 286 lambda x: x or ''],
284 287 missing='')
285 288 request_id = colander.SchemaNode(colander.String(),
286 289 preparer=shortener_factory(40),
287 290 missing='')
288 291 url = colander.SchemaNode(colander.String(),
289 292 preparer=shortener_factory(1024), missing='')
290 293 ip = colander.SchemaNode(colander.String(), preparer=shortener_factory(39),
291 294 missing=None)
292 295 start_time = colander.SchemaNode(NonTZDate(), validator=limited_date,
293 296 missing=deferred_utcnow)
294 297 end_time = colander.SchemaNode(NonTZDate(), validator=limited_date,
295 298 missing=None)
296 299 user_agent = colander.SchemaNode(colander.String(),
297 300 preparer=[shortener_factory(512),
298 301 lambda x: x or ''],
299 302 missing='')
300 303 message = colander.SchemaNode(colander.String(),
301 304 preparer=shortener_factory(2048),
302 305 missing='')
303 306 group_string = colander.SchemaNode(colander.String(),
304 307 validator=colander.Length(1, 512),
305 308 missing=None)
306 309 request_stats = RequestStatsSchema(missing=None)
307 310 request = colander.SchemaNode(colander.Mapping(unknown='preserve'),
308 311 missing={})
309 312 traceback = FrameInfoListSchema(missing=None)
310 313 slow_calls = SlowCallListSchema(missing=[])
311 314 extra = ExtraSchemaList()
312 315
313 316
314 class ReportDetailSchema_0_4(ReportDetailBaseSchema):
315 frameinfo = FrameInfoListSchema(missing=None)
316
317
318 317 class ReportDetailSchema_0_5(ReportDetailBaseSchema):
319 318 pass
320 319
321 320
322 class ReportDetailListSchema(colander.SequenceSchema):
323 """
324 Validates format of list of reports
325 """
326 report_detail = ReportDetailSchema_0_4()
327 validator = colander.Length(1)
321 class ReportDetailSchemaPermissiveDate_0_5(ReportDetailSchema_0_5):
322 start_time = colander.SchemaNode(NonTZDate(), missing=deferred_utcnow)
323 end_time = colander.SchemaNode(NonTZDate(), missing=None)
328 324
329 325
330 326 class ReportSchemaBase(colander.MappingSchema):
331 327 """
332 328 Validates format of report group
333 329 """
334 330 client = colander.SchemaNode(colander.String(),
335 331 preparer=lambda x: x or 'unknown')
336 332 server = colander.SchemaNode(
337 333 colander.String(),
338 334 preparer=[
339 335 lambda x: x.lower() if x else 'unknown', shortener_factory(128)],
340 336 missing='unknown')
341 337 priority = colander.SchemaNode(colander.Int(),
342 338 preparer=[lambda x: x or 5],
343 339 validator=colander.Range(1, 10),
344 340 missing=5)
345 341 language = colander.SchemaNode(colander.String(), missing='unknown')
346 342 error = colander.SchemaNode(colander.String(),
347 343 preparer=shortener_factory(512),
348 344 missing='')
349 345 view_name = colander.SchemaNode(colander.String(),
350 346 preparer=[shortener_factory(128),
351 347 lambda x: x or ''],
352 348 missing='')
353 349 http_status = colander.SchemaNode(colander.Int(),
354 350 preparer=[lambda x: x or 200],
355 351 validator=colander.Range(1))
356 352
357 353 occurences = colander.SchemaNode(colander.Int(),
358 354 validator=colander.Range(1, 99999999999),
359 355 missing=1)
360 356 tags = TagSchemaList()
361 357
362 358
363 359 class ReportSchema_0_5(ReportSchemaBase, ReportDetailSchema_0_5):
364 360 pass
365 361
366 362
363 class ReportSchemaPermissiveDate_0_5(ReportSchemaBase,
364 ReportDetailSchemaPermissiveDate_0_5):
365 pass
366
367
367 368 class ReportListSchema_0_5(colander.SequenceSchema):
368 369 """
369 370 Validates format of list of report groups
370 371 """
371 372 report = ReportSchema_0_5()
372 373 validator = colander.Length(1)
373 374
374 375
376 class ReportListPermissiveDateSchema_0_5(colander.SequenceSchema):
377 """
378 Validates format of list of report groups
379 """
380 report = ReportSchemaPermissiveDate_0_5()
381 validator = colander.Length(1)
382
383
375 384 class LogSchema(colander.MappingSchema):
376 385 """
377 386 Validates format if individual log entry
378 387 """
379 388 primary_key = colander.SchemaNode(UnknownType(),
380 389 preparer=[cast_to_unicode_or_null,
381 390 shortener_factory(128)],
382 391 missing=None)
383 392 log_level = colander.SchemaNode(colander.String(),
384 393 preparer=shortener_factory(10),
385 394 missing='UNKNOWN')
386 395 message = colander.SchemaNode(colander.String(),
387 396 preparer=shortener_factory(4096),
388 397 missing='')
389 398 namespace = colander.SchemaNode(colander.String(),
390 399 preparer=shortener_factory(128),
391 400 missing='')
392 401 request_id = colander.SchemaNode(colander.String(),
393 402 preparer=shortener_factory(40),
394 403 missing='')
395 404 server = colander.SchemaNode(colander.String(),
396 405 preparer=shortener_factory(128),
397 406 missing='unknown')
398 407 date = colander.SchemaNode(NonTZDate(),
399 408 validator=limited_date,
400 409 missing=deferred_utcnow)
401 410 tags = TagSchemaList()
402 411
403 412
404 413 class LogSchemaPermanent(LogSchema):
405 414 date = colander.SchemaNode(NonTZDate(),
406 415 missing=deferred_utcnow)
407 416 permanent = colander.SchemaNode(colander.Boolean(), missing=False)
408 417
409 418
410 419 class LogListSchema(colander.SequenceSchema):
411 420 """
412 421 Validates format of list of log entries
413 422 """
414 423 log = LogSchema()
415 424 validator = colander.Length(1)
416 425
417 426
418 427 class LogListPermanentSchema(colander.SequenceSchema):
419 428 """
420 429 Validates format of list of log entries
421 430 """
422 431 log = LogSchemaPermanent()
423 432 validator = colander.Length(1)
424 433
425 434
426 435 class ViewRequestStatsSchema(RequestStatsSchema):
427 436 requests = colander.SchemaNode(colander.Integer(),
428 437 validator=colander.Range(0),
429 438 missing=0)
430 439
431 440
432 441 class ViewMetricTupleSchema(colander.TupleSchema):
433 442 """
434 443 Validates list of views and their corresponding request stats object ie:
435 444 ["dir/module:func",{"custom": 0.0..}]
436 445 """
437 446 view_name = colander.SchemaNode(colander.String(),
438 447 preparer=[shortener_factory(128),
439 448 lambda x: x or 'unknown'],
440 449 missing='unknown')
441 450 metrics = ViewRequestStatsSchema()
442 451
443 452
444 453 class ViewMetricListSchema(colander.SequenceSchema):
445 454 """
446 455 Validates view breakdown stats objects list
447 456 {metrics key of server/time object}
448 457 """
449 458 view_tuple = ViewMetricTupleSchema()
450 459 validator = colander.Length(1)
451 460
452 461
453 462 class ViewMetricSchema(colander.MappingSchema):
454 463 """
455 464 Validates server/timeinterval object, ie:
456 465 {server/time object}
457 466
458 467 """
459 468 timestamp = colander.SchemaNode(NonTZDate(),
460 469 validator=limited_date,
461 470 missing=None)
462 471 server = colander.SchemaNode(colander.String(),
463 472 preparer=[shortener_factory(128),
464 473 lambda x: x or 'unknown'],
465 474 missing='unknown')
466 475 metrics = ViewMetricListSchema()
467 476
468 477
469 478 class GeneralMetricSchema(colander.MappingSchema):
470 479 """
471 480 Validates universal metric schema
472 481
473 482 """
474 483 namespace = colander.SchemaNode(colander.String(), missing='',
475 484 preparer=shortener_factory(128))
476 485
477 486 server_name = colander.SchemaNode(colander.String(),
478 487 preparer=[shortener_factory(128),
479 488 lambda x: x or 'unknown'],
480 489 missing='unknown')
481 490 timestamp = colander.SchemaNode(NonTZDate(), validator=limited_date,
482 491 missing=deferred_utcnow)
483 492 tags = TagSchemaList(missing=colander.required)
484 493
485 494
486 495 class GeneralMetricsListSchema(colander.SequenceSchema):
487 496 metric = GeneralMetricSchema()
488 497 validator = colander.Length(1)
489 498
490 499
491 500 class MetricsListSchema(colander.SequenceSchema):
492 501 """
493 502 Validates list of metrics objects ie:
494 503 [{server/time object}, ] part
495 504
496 505
497 506 """
498 507 metric = ViewMetricSchema()
499 508 validator = colander.Length(1)
500 509
501 510
502 511 class StringToAppList(object):
503 512 """
504 513 Returns validated list of application ids from user query and
505 514 set of applications user is allowed to look at
506 515 transform string to list containing single integer
507 516 """
508 517
509 518 def serialize(self, node, appstruct):
510 519 if appstruct is null:
511 520 return null
512 521 return appstruct
513 522
514 523 def deserialize(self, node, cstruct):
515 524 if cstruct is null:
516 525 return null
517 526
518 527 apps = set([int(a) for a in node.bindings['resources']])
519 528
520 529 if isinstance(cstruct, str):
521 530 cstruct = [cstruct]
522 531
523 532 cstruct = [int(a) for a in cstruct]
524 533
525 534 valid_apps = list(apps.intersection(set(cstruct)))
526 535 if valid_apps:
527 536 return valid_apps
528 537 return null
529 538
530 539 def cstruct_children(self):
531 540 return []
532 541
533 542
534 543 @colander.deferred
535 544 def possible_applications_validator(node, kw):
536 545 possible_apps = [int(a) for a in kw['resources']]
537 546 return colander.All(colander.ContainsOnly(possible_apps),
538 547 colander.Length(1))
539 548
540 549
541 550 @colander.deferred
542 551 def possible_applications(node, kw):
543 552 return [int(a) for a in kw['resources']]
544 553
545 554
546 555 @colander.deferred
547 556 def today_start(node, kw):
548 557 return datetime.datetime.utcnow().replace(second=0, microsecond=0,
549 558 minute=0,
550 559 hour=0)
551 560
552 561
553 562 @colander.deferred
554 563 def today_end(node, kw):
555 564 return datetime.datetime.utcnow().replace(second=0, microsecond=0,
556 565 minute=59, hour=23)
557 566
558 567
559 568 @colander.deferred
560 569 def old_start(node, kw):
561 570 t_delta = datetime.timedelta(days=90)
562 571 return datetime.datetime.utcnow().replace(second=0, microsecond=0,
563 572 minute=0,
564 573 hour=0) - t_delta
565 574
566 575
567 576 @colander.deferred
568 577 def today_end(node, kw):
569 578 return datetime.datetime.utcnow().replace(second=0, microsecond=0,
570 579 minute=59, hour=23)
571 580
572 581
573 582 class PermissiveDate(colander.DateTime):
574 583 """ Returns null for incorrect date format - also removes tz info"""
575 584
576 585 def deserialize(self, node, cstruct):
577 586 if not cstruct:
578 587 return null
579 588
580 589 try:
581 590 result = colander.iso8601.parse_date(
582 591 cstruct, default_timezone=self.default_tzinfo)
583 592 except colander.iso8601.ParseError:
584 593 return null
585 594 return result.replace(tzinfo=None)
586 595
587 596
588 597 class LogSearchSchema(colander.MappingSchema):
589 598 def schema_type(self, **kw):
590 599 return colander.Mapping(unknown='preserve')
591 600
592 601 resource = colander.SchemaNode(StringToAppList(),
593 602 validator=possible_applications_validator,
594 603 missing=possible_applications)
595 604
596 605 message = colander.SchemaNode(colander.Sequence(accept_scalar=True),
597 606 colander.SchemaNode(colander.String()),
598 607 missing=None)
599 608 level = colander.SchemaNode(colander.Sequence(accept_scalar=True),
600 609 colander.SchemaNode(colander.String()),
601 610 preparer=lowercase_preparer,
602 611 missing=None)
603 612 namespace = colander.SchemaNode(colander.Sequence(accept_scalar=True),
604 613 colander.SchemaNode(colander.String()),
605 614 preparer=lowercase_preparer,
606 615 missing=None)
607 616 request_id = colander.SchemaNode(colander.Sequence(accept_scalar=True),
608 617 colander.SchemaNode(colander.String()),
609 618 preparer=lowercase_preparer,
610 619 missing=None)
611 620 start_date = colander.SchemaNode(PermissiveDate(),
612 621 missing=None)
613 622 end_date = colander.SchemaNode(PermissiveDate(),
614 623 missing=None)
615 624 page = colander.SchemaNode(colander.Integer(),
616 625 validator=colander.Range(min=1),
617 626 missing=1)
618 627
619 628
620 629 class ReportSearchSchema(colander.MappingSchema):
621 630 def schema_type(self, **kw):
622 631 return colander.Mapping(unknown='preserve')
623 632
624 633 resource = colander.SchemaNode(StringToAppList(),
625 634 validator=possible_applications_validator,
626 635 missing=possible_applications)
627 636 request_id = colander.SchemaNode(colander.Sequence(accept_scalar=True),
628 637 colander.SchemaNode(colander.String()),
629 638 missing=None)
630 639 start_date = colander.SchemaNode(PermissiveDate(),
631 640 missing=None)
632 641 end_date = colander.SchemaNode(PermissiveDate(),
633 642 missing=None)
634 643 page = colander.SchemaNode(colander.Integer(),
635 644 validator=colander.Range(min=1),
636 645 missing=1)
637 646
638 647 min_occurences = colander.SchemaNode(
639 648 colander.Sequence(accept_scalar=True),
640 649 colander.SchemaNode(colander.Integer()),
641 650 missing=None)
642 651
643 652 http_status = colander.SchemaNode(colander.Sequence(accept_scalar=True),
644 653 colander.SchemaNode(colander.Integer()),
645 654 missing=None)
646 655 priority = colander.SchemaNode(colander.Sequence(accept_scalar=True),
647 656 colander.SchemaNode(colander.Integer()),
648 657 missing=None)
649 658 error = colander.SchemaNode(colander.Sequence(accept_scalar=True),
650 659 colander.SchemaNode(colander.String()),
651 660 missing=None)
652 661 url_path = colander.SchemaNode(colander.Sequence(accept_scalar=True),
653 662 colander.SchemaNode(colander.String()),
654 663 missing=None)
655 664 url_domain = colander.SchemaNode(colander.Sequence(accept_scalar=True),
656 665 colander.SchemaNode(colander.String()),
657 666 missing=None)
658 667 report_status = colander.SchemaNode(colander.Sequence(accept_scalar=True),
659 668 colander.SchemaNode(colander.String()),
660 669 missing=None)
661 670 min_duration = colander.SchemaNode(colander.Sequence(accept_scalar=True),
662 671 colander.SchemaNode(colander.Float()),
663 672 missing=None)
664 673 max_duration = colander.SchemaNode(colander.Sequence(accept_scalar=True),
665 674 colander.SchemaNode(colander.Float()),
666 675 missing=None)
667 676
668 677
669 678 class TagSchema(colander.MappingSchema):
670 679 """
671 680 Used in log search
672 681 """
673 682 name = colander.SchemaNode(colander.String(),
674 683 validator=colander.Length(1, 32))
675 684 value = colander.SchemaNode(colander.Sequence(accept_scalar=True),
676 685 colander.SchemaNode(colander.String(),
677 686 validator=colander.Length(
678 687 1, 128)),
679 688 missing=None)
680 689 op = colander.SchemaNode(colander.String(),
681 690 validator=colander.Length(1, 128),
682 691 missing=None)
683 692
684 693
685 694 class TagListSchema(colander.SequenceSchema):
686 695 tag = TagSchema()
687 696
688 697
689 698 class RuleFieldType(object):
690 699 """ Validator which succeeds if the value passed to it is one of
691 700 a fixed set of values """
692 701
693 702 def __init__(self, cast_to):
694 703 self.cast_to = cast_to
695 704
696 705 def __call__(self, node, value):
697 706 try:
698 707 if self.cast_to == 'int':
699 708 int(value)
700 709 elif self.cast_to == 'float':
701 710 float(value)
702 711 elif self.cast_to == 'unicode':
703 712 str(value)
704 713 except:
705 714 raise colander.Invalid(node,
706 715 "Can't cast {} to {}".format(
707 716 value, self.cast_to))
708 717
709 718
710 719 def build_rule_schema(ruleset, check_matrix):
711 720 """
712 721 Accepts ruleset and a map of fields/possible operations and builds
713 722 validation class
714 723 """
715 724
716 725 schema = colander.SchemaNode(colander.Mapping())
717 726 schema.add(colander.SchemaNode(colander.String(), name='field'))
718 727
719 728 if ruleset['field'] in ['__AND__', '__OR__', '__NOT__']:
720 729 subrules = colander.SchemaNode(colander.Tuple(), name='rules')
721 730 for rule in ruleset['rules']:
722 731 subrules.add(build_rule_schema(rule, check_matrix))
723 732 schema.add(subrules)
724 733 else:
725 734 op_choices = check_matrix[ruleset['field']]['ops']
726 735 cast_to = check_matrix[ruleset['field']]['type']
727 736 schema.add(colander.SchemaNode(colander.String(),
728 737 validator=colander.OneOf(op_choices),
729 738 name='op'))
730 739
731 740 schema.add(colander.SchemaNode(colander.String(),
732 741 name='value',
733 742 validator=RuleFieldType(cast_to)))
734 743 return schema
735 744
736 745
737 746 class ConfigTypeSchema(colander.MappingSchema):
738 747 type = colander.SchemaNode(colander.String(), missing=None)
739 748 config = colander.SchemaNode(UnknownType(), missing=None)
740 749
741 750
742 751 class MappingListSchema(colander.SequenceSchema):
743 752 config = colander.SchemaNode(UnknownType())
@@ -1,422 +1,427 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # AppEnlight Enterprise Edition, including its added features, Support
19 19 # services, and proprietary license terms, please see
20 20 # https://rhodecode.com/licenses/
21 21
22 22 import base64
23 23 import io
24 24 import datetime
25 25 import json
26 26 import logging
27 27 import urllib.request, urllib.parse, urllib.error
28 28 import zlib
29 29
30 30 from gzip import GzipFile
31 31 from pyramid.view import view_config
32 32 from pyramid.httpexceptions import HTTPBadRequest
33 33
34 34 import appenlight.celery.tasks as tasks
35 35 from appenlight.lib.api import rate_limiting, check_cors
36 36 from appenlight.lib.enums import ParsedSentryEventType
37 37 from appenlight.lib.utils import parse_proto
38 38 from appenlight.lib.utils.airbrake import parse_airbrake_xml
39 39 from appenlight.lib.utils.date_utils import convert_date
40 40 from appenlight.lib.utils.sentry import parse_sentry_event
41 41 from appenlight.lib.request import JSONException
42 42 from appenlight.validators import (LogListSchema,
43 43 MetricsListSchema,
44 44 GeneralMetricsListSchema,
45 45 GeneralMetricSchema,
46 46 LogListPermanentSchema,
47 47 ReportListSchema_0_5,
48 48 LogSchema,
49 49 LogSchemaPermanent,
50 50 ReportSchema_0_5)
51 51
52 52 log = logging.getLogger(__name__)
53 53
54 54
55 55 @view_config(route_name='api_logs', renderer='string', permission='create',
56 56 require_csrf=False)
57 57 @view_config(route_name='api_log', renderer='string', permission='create',
58 58 require_csrf=False)
59 59 def logs_create(request):
60 60 """
61 61 Endpoint for log aggregation
62 62 """
63 63 application = request.context.resource
64 64 if request.method.upper() == 'OPTIONS':
65 65 return check_cors(request, application)
66 66 else:
67 67 check_cors(request, application, should_return=False)
68 68
69 69 params = dict(request.params.copy())
70 70 proto_version = parse_proto(params.get('protocol_version', ''))
71 71 payload = request.unsafe_json_body
72 72 sequence_accepted = request.matched_route.name == 'api_logs'
73 73
74 74 if sequence_accepted:
75 75 if application.allow_permanent_storage:
76 76 schema = LogListPermanentSchema().bind(
77 77 utcnow=datetime.datetime.utcnow())
78 78 else:
79 79 schema = LogListSchema().bind(
80 80 utcnow=datetime.datetime.utcnow())
81 81 else:
82 82 if application.allow_permanent_storage:
83 83 schema = LogSchemaPermanent().bind(
84 84 utcnow=datetime.datetime.utcnow())
85 85 else:
86 86 schema = LogSchema().bind(
87 87 utcnow=datetime.datetime.utcnow())
88 88
89 89 deserialized_logs = schema.deserialize(payload)
90 90 if sequence_accepted is False:
91 91 deserialized_logs = [deserialized_logs]
92 92
93 93 rate_limiting(request, application, 'per_application_logs_rate_limit',
94 94 len(deserialized_logs))
95 95
96 96 # pprint.pprint(deserialized_logs)
97 97
98 98 # we need to split those out so we can process the pkey ones one by one
99 99 non_pkey_logs = [log_dict for log_dict in deserialized_logs
100 100 if not log_dict['primary_key']]
101 101 pkey_dict = {}
102 102 # try to process the logs as best as we can and group together to reduce
103 103 # the amount of
104 104 for log_dict in deserialized_logs:
105 105 if log_dict['primary_key']:
106 106 key = (log_dict['primary_key'], log_dict['namespace'],)
107 107 if not key in pkey_dict:
108 108 pkey_dict[key] = []
109 109 pkey_dict[key].append(log_dict)
110 110
111 111 if non_pkey_logs:
112 112 log.debug('%s non-pkey logs received: %s' % (application,
113 113 len(non_pkey_logs)))
114 114 tasks.add_logs.delay(application.resource_id, params, non_pkey_logs)
115 115 if pkey_dict:
116 116 logs_to_insert = []
117 117 for primary_key_tuple, payload in pkey_dict.items():
118 118 sorted_logs = sorted(payload, key=lambda x: x['date'])
119 119 logs_to_insert.append(sorted_logs[-1])
120 120 log.debug('%s pkey logs received: %s' % (application,
121 121 len(logs_to_insert)))
122 122 tasks.add_logs.delay(application.resource_id, params, logs_to_insert)
123 123
124 124 log.info('LOG call %s %s client:%s' % (
125 125 application, proto_version, request.headers.get('user_agent')))
126 126 return 'OK: Logs accepted'
127 127
128 128
129 129 @view_config(route_name='api_request_stats', renderer='string',
130 130 permission='create', require_csrf=False)
131 131 @view_config(route_name='api_metrics', renderer='string',
132 132 permission='create', require_csrf=False)
133 133 def request_metrics_create(request):
134 134 """
135 135 Endpoint for performance metrics, aggregates view performance stats
136 136 and converts them to general metric row
137 137 """
138 138 application = request.context.resource
139 139 if request.method.upper() == 'OPTIONS':
140 140 return check_cors(request, application)
141 141 else:
142 142 check_cors(request, application, should_return=False)
143 143
144 144 params = dict(request.params.copy())
145 145 proto_version = parse_proto(params.get('protocol_version', ''))
146 146
147 147 payload = request.unsafe_json_body
148 148 schema = MetricsListSchema()
149 149 dataset = schema.deserialize(payload)
150 150
151 151 rate_limiting(request, application, 'per_application_metrics_rate_limit',
152 152 len(dataset))
153 153
154 154 # looping report data
155 155 metrics = {}
156 156 for metric in dataset:
157 157 server_name = metric.get('server', '').lower() or 'unknown'
158 158 start_interval = convert_date(metric['timestamp'])
159 159 start_interval = start_interval.replace(second=0, microsecond=0)
160 160
161 161 for view_name, view_metrics in metric['metrics']:
162 162 key = '%s%s%s' % (metric['server'], start_interval, view_name)
163 163 if start_interval not in metrics:
164 164 metrics[key] = {"requests": 0, "main": 0, "sql": 0,
165 165 "nosql": 0, "remote": 0, "tmpl": 0,
166 166 "custom": 0, 'sql_calls': 0,
167 167 'nosql_calls': 0,
168 168 'remote_calls': 0, 'tmpl_calls': 0,
169 169 'custom_calls': 0,
170 170 "start_interval": start_interval,
171 171 "server_name": server_name,
172 172 "view_name": view_name
173 173 }
174 174 metrics[key]["requests"] += int(view_metrics['requests'])
175 175 metrics[key]["main"] += round(view_metrics['main'], 5)
176 176 metrics[key]["sql"] += round(view_metrics['sql'], 5)
177 177 metrics[key]["nosql"] += round(view_metrics['nosql'], 5)
178 178 metrics[key]["remote"] += round(view_metrics['remote'], 5)
179 179 metrics[key]["tmpl"] += round(view_metrics['tmpl'], 5)
180 180 metrics[key]["custom"] += round(view_metrics.get('custom', 0.0),
181 181 5)
182 182 metrics[key]["sql_calls"] += int(
183 183 view_metrics.get('sql_calls', 0))
184 184 metrics[key]["nosql_calls"] += int(
185 185 view_metrics.get('nosql_calls', 0))
186 186 metrics[key]["remote_calls"] += int(
187 187 view_metrics.get('remote_calls', 0))
188 188 metrics[key]["tmpl_calls"] += int(
189 189 view_metrics.get('tmpl_calls', 0))
190 190 metrics[key]["custom_calls"] += int(
191 191 view_metrics.get('custom_calls', 0))
192 192
193 193 if not metrics[key]["requests"]:
194 194 # fix this here because validator can't
195 195 metrics[key]["requests"] = 1
196 196 # metrics dict is being built to minimize
197 197 # the amount of queries used
198 198 # in case we get multiple rows from same minute
199 199
200 200 normalized_metrics = []
201 201 for metric in metrics.values():
202 202 new_metric = {
203 203 'namespace': 'appenlight.request_metric',
204 204 'timestamp': metric.pop('start_interval'),
205 205 'server_name': metric['server_name'],
206 206 'tags': list(metric.items())
207 207 }
208 208 normalized_metrics.append(new_metric)
209 209
210 210 tasks.add_metrics.delay(application.resource_id, params,
211 211 normalized_metrics, proto_version)
212 212
213 213 log.info('REQUEST METRICS call {} {} client:{}'.format(
214 214 application.resource_name, proto_version,
215 215 request.headers.get('user_agent')))
216 216 return 'OK: request metrics accepted'
217 217
218 218
219 219 @view_config(route_name='api_general_metrics', renderer='string',
220 220 permission='create', require_csrf=False)
221 221 @view_config(route_name='api_general_metric', renderer='string',
222 222 permission='create', require_csrf=False)
223 223 def general_metrics_create(request):
224 224 """
225 225 Endpoint for general metrics aggregation
226 226 """
227 227 application = request.context.resource
228 228 if request.method.upper() == 'OPTIONS':
229 229 return check_cors(request, application)
230 230 else:
231 231 check_cors(request, application, should_return=False)
232 232
233 233 params = dict(request.params.copy())
234 234 proto_version = parse_proto(params.get('protocol_version', ''))
235 235 payload = request.unsafe_json_body
236 236 sequence_accepted = request.matched_route.name == 'api_general_metrics'
237 237 if sequence_accepted:
238 238 schema = GeneralMetricsListSchema().bind(
239 239 utcnow=datetime.datetime.utcnow())
240 240 else:
241 241 schema = GeneralMetricSchema().bind(utcnow=datetime.datetime.utcnow())
242 242
243 243 deserialized_metrics = schema.deserialize(payload)
244 244 if sequence_accepted is False:
245 245 deserialized_metrics = [deserialized_metrics]
246 246
247 247 rate_limiting(request, application, 'per_application_metrics_rate_limit',
248 248 len(deserialized_metrics))
249 249
250 250 tasks.add_metrics.delay(application.resource_id, params,
251 251 deserialized_metrics, proto_version)
252 252
253 253 log.info('METRICS call {} {} client:{}'.format(
254 254 application.resource_name, proto_version,
255 255 request.headers.get('user_agent')))
256 256 return 'OK: Metrics accepted'
257 257
258 258
259 259 @view_config(route_name='api_reports', renderer='string', permission='create',
260 260 require_csrf=False)
261 261 @view_config(route_name='api_slow_reports', renderer='string',
262 262 permission='create', require_csrf=False)
263 263 @view_config(route_name='api_report', renderer='string', permission='create',
264 264 require_csrf=False)
265 265 def reports_create(request):
266 266 """
267 267 Endpoint for exception and slowness reports
268 268 """
269 269 # route_url('reports')
270 270 application = request.context.resource
271 271 if request.method.upper() == 'OPTIONS':
272 272 return check_cors(request, application)
273 273 else:
274 274 check_cors(request, application, should_return=False)
275 275 params = dict(request.params.copy())
276 276 proto_version = parse_proto(params.get('protocol_version', ''))
277 277 payload = request.unsafe_json_body
278 278 sequence_accepted = request.matched_route.name == 'api_reports'
279 279
280 280 if sequence_accepted:
281 281 schema = ReportListSchema_0_5().bind(
282 282 utcnow=datetime.datetime.utcnow())
283 283 else:
284 284 schema = ReportSchema_0_5().bind(
285 285 utcnow=datetime.datetime.utcnow())
286 286
287 287 deserialized_reports = schema.deserialize(payload)
288 288 if sequence_accepted is False:
289 289 deserialized_reports = [deserialized_reports]
290 290 if deserialized_reports:
291 291 rate_limiting(request, application,
292 292 'per_application_reports_rate_limit',
293 293 len(deserialized_reports))
294 294
295 295 # pprint.pprint(deserialized_reports)
296 296 tasks.add_reports.delay(application.resource_id, params,
297 297 deserialized_reports)
298 298 log.info('REPORT call %s, %s client:%s' % (
299 299 application,
300 300 proto_version,
301 301 request.headers.get('user_agent'))
302 302 )
303 303 return 'OK: Reports accepted'
304 304
305 305
306 306 @view_config(route_name='api_airbrake', renderer='string', permission='create',
307 307 require_csrf=False)
308 308 def airbrake_xml_compat(request):
309 309 """
310 310 Airbrake compatible endpoint for XML reports
311 311 """
312 312 application = request.context.resource
313 313 if request.method.upper() == 'OPTIONS':
314 314 return check_cors(request, application)
315 315 else:
316 316 check_cors(request, application, should_return=False)
317 317
318 318 params = request.params.copy()
319 319
320 320 error_dict = parse_airbrake_xml(request)
321 321 schema = ReportListSchema_0_5().bind(utcnow=datetime.datetime.utcnow())
322 322 deserialized_reports = schema.deserialize([error_dict])
323 323 rate_limiting(request, application, 'per_application_reports_rate_limit',
324 324 len(deserialized_reports))
325 325
326 326 tasks.add_reports.delay(application.resource_id, params,
327 327 deserialized_reports)
328 328 log.info('%s AIRBRAKE call for application %s, api_ver:%s client:%s' % (
329 329 500, application.resource_name,
330 330 request.params.get('protocol_version', 'unknown'),
331 331 request.headers.get('user_agent'))
332 332 )
333 333 return '<notice><id>no-id</id><url>%s</url></notice>' % \
334 334 request.registry.settings['mailing.app_url']
335 335
336 336
337 337 def decompress_gzip(data):
338 338 try:
339 339 fp = io.StringIO(data)
340 340 with GzipFile(fileobj=fp) as f:
341 341 return f.read()
342 342 except Exception as exc:
343 343 raise
344 344 log.error(exc)
345 345 raise HTTPBadRequest()
346 346
347 347
348 348 def decompress_zlib(data):
349 349 try:
350 350 return zlib.decompress(data)
351 351 except Exception as exc:
352 352 raise
353 353 log.error(exc)
354 354 raise HTTPBadRequest()
355 355
356 356
357 357 def decode_b64(data):
358 358 try:
359 359 return base64.b64decode(data)
360 360 except Exception as exc:
361 361 raise
362 362 log.error(exc)
363 363 raise HTTPBadRequest()
364 364
365 365
366 366 @view_config(route_name='api_sentry', renderer='string', permission='create',
367 367 require_csrf=False)
368 368 @view_config(route_name='api_sentry_slash', renderer='string',
369 369 permission='create', require_csrf=False)
370 370 def sentry_compat(request):
371 371 """
372 372 Sentry compatible endpoint
373 373 """
374 374 application = request.context.resource
375 375 if request.method.upper() == 'OPTIONS':
376 376 return check_cors(request, application)
377 377 else:
378 378 check_cors(request, application, should_return=False)
379 379
380 380 # handle various report encoding
381 381 content_encoding = request.headers.get('Content-Encoding')
382 382 content_type = request.headers.get('Content-Type')
383 383 if content_encoding == 'gzip':
384 384 body = decompress_gzip(request.body)
385 385 elif content_encoding == 'deflate':
386 386 body = decompress_zlib(request.body)
387 387 else:
388 388 body = request.body
389 389 # attempt to fix string before decoding for stupid clients
390 390 if content_type == 'application/x-www-form-urlencoded':
391 391 body = urllib.parse.unquote(body.decode('utf8'))
392 392 check_char = '{' if isinstance(body, str) else b'{'
393 393 if not body.startswith(check_char):
394 394 try:
395 395 body = decode_b64(body)
396 396 body = decompress_zlib(body)
397 397 except Exception as exc:
398 398 log.info(exc)
399 399
400 400 try:
401 401 json_body = json.loads(body.decode('utf8'))
402 402 except ValueError:
403 403 raise JSONException("Incorrect JSON")
404 404
405 405 event, event_type = parse_sentry_event(json_body)
406 406
407 407 if event_type == ParsedSentryEventType.LOG:
408 schema = LogSchema().bind(utcnow=datetime.datetime.utcnow())
408 if application.allow_permanent_storage:
409 schema = LogSchemaPermanent().bind(
410 utcnow=datetime.datetime.utcnow())
411 else:
412 schema = LogSchema().bind(
413 utcnow=datetime.datetime.utcnow())
409 414 deserialized_logs = schema.deserialize(event)
410 415 non_pkey_logs = [deserialized_logs]
411 416 log.debug('%s non-pkey logs received: %s' % (application,
412 417 len(non_pkey_logs)))
413 418 tasks.add_logs.delay(application.resource_id, {}, non_pkey_logs)
414 419 if event_type == ParsedSentryEventType.ERROR_REPORT:
415 420 schema = ReportSchema_0_5().bind(utcnow=datetime.datetime.utcnow())
416 421 deserialized_reports = [schema.deserialize(event)]
417 422 rate_limiting(request, application,
418 423 'per_application_reports_rate_limit',
419 424 len(deserialized_reports))
420 425 tasks.add_reports.delay(application.resource_id, {},
421 426 deserialized_reports)
422 427 return 'OK: Events accepted'
General Comments 0
You need to be logged in to leave comments. Login now