##// END OF EJS Templates
api: params should be plain dictionary
ergo -
Show More
@@ -1,438 +1,438 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # AppEnlight Enterprise Edition, including its added features, Support
18 # AppEnlight Enterprise Edition, including its added features, Support
19 # services, and proprietary license terms, please see
19 # services, and proprietary license terms, please see
20 # https://rhodecode.com/licenses/
20 # https://rhodecode.com/licenses/
21
21
22 import base64
22 import base64
23 import io
23 import io
24 import datetime
24 import datetime
25 import json
25 import json
26 import logging
26 import logging
27 import urllib.request, urllib.parse, urllib.error
27 import urllib.request, urllib.parse, urllib.error
28 import zlib
28 import zlib
29
29
30 from gzip import GzipFile
30 from gzip import GzipFile
31 from pyramid.view import view_config
31 from pyramid.view import view_config
32 from pyramid.httpexceptions import HTTPBadRequest
32 from pyramid.httpexceptions import HTTPBadRequest
33
33
34 import appenlight.celery.tasks as tasks
34 import appenlight.celery.tasks as tasks
35 from appenlight.lib.api import rate_limiting, check_cors
35 from appenlight.lib.api import rate_limiting, check_cors
36 from appenlight.lib.enums import ParsedSentryEventType
36 from appenlight.lib.enums import ParsedSentryEventType
37 from appenlight.lib.utils import parse_proto
37 from appenlight.lib.utils import parse_proto
38 from appenlight.lib.utils.airbrake import parse_airbrake_xml
38 from appenlight.lib.utils.airbrake import parse_airbrake_xml
39 from appenlight.lib.utils.date_utils import convert_date
39 from appenlight.lib.utils.date_utils import convert_date
40 from appenlight.lib.utils.sentry import parse_sentry_event
40 from appenlight.lib.utils.sentry import parse_sentry_event
41 from appenlight.lib.request import JSONException
41 from appenlight.lib.request import JSONException
42 from appenlight.validators import (LogListSchema,
42 from appenlight.validators import (LogListSchema,
43 MetricsListSchema,
43 MetricsListSchema,
44 GeneralMetricsListSchema,
44 GeneralMetricsListSchema,
45 GeneralMetricsPermanentListSchema,
45 GeneralMetricsPermanentListSchema,
46 GeneralMetricSchema,
46 GeneralMetricSchema,
47 GeneralMetricPermanentSchema,
47 GeneralMetricPermanentSchema,
48 LogListPermanentSchema,
48 LogListPermanentSchema,
49 ReportListSchema_0_5,
49 ReportListSchema_0_5,
50 LogSchema,
50 LogSchema,
51 LogSchemaPermanent,
51 LogSchemaPermanent,
52 ReportSchema_0_5)
52 ReportSchema_0_5)
53
53
54 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
55
55
56
56
57 @view_config(route_name='api_logs', renderer='string', permission='create',
57 @view_config(route_name='api_logs', renderer='string', permission='create',
58 require_csrf=False)
58 require_csrf=False)
59 @view_config(route_name='api_log', renderer='string', permission='create',
59 @view_config(route_name='api_log', renderer='string', permission='create',
60 require_csrf=False)
60 require_csrf=False)
61 def logs_create(request):
61 def logs_create(request):
62 """
62 """
63 Endpoint for log aggregation
63 Endpoint for log aggregation
64 """
64 """
65 application = request.context.resource
65 application = request.context.resource
66 if request.method.upper() == 'OPTIONS':
66 if request.method.upper() == 'OPTIONS':
67 return check_cors(request, application)
67 return check_cors(request, application)
68 else:
68 else:
69 check_cors(request, application, should_return=False)
69 check_cors(request, application, should_return=False)
70
70
71 params = dict(request.params.copy())
71 params = dict(request.params.copy())
72 proto_version = parse_proto(params.get('protocol_version', ''))
72 proto_version = parse_proto(params.get('protocol_version', ''))
73 payload = request.unsafe_json_body
73 payload = request.unsafe_json_body
74 sequence_accepted = request.matched_route.name == 'api_logs'
74 sequence_accepted = request.matched_route.name == 'api_logs'
75
75
76 if sequence_accepted:
76 if sequence_accepted:
77 if application.allow_permanent_storage:
77 if application.allow_permanent_storage:
78 schema = LogListPermanentSchema().bind(
78 schema = LogListPermanentSchema().bind(
79 utcnow=datetime.datetime.utcnow())
79 utcnow=datetime.datetime.utcnow())
80 else:
80 else:
81 schema = LogListSchema().bind(
81 schema = LogListSchema().bind(
82 utcnow=datetime.datetime.utcnow())
82 utcnow=datetime.datetime.utcnow())
83 else:
83 else:
84 if application.allow_permanent_storage:
84 if application.allow_permanent_storage:
85 schema = LogSchemaPermanent().bind(
85 schema = LogSchemaPermanent().bind(
86 utcnow=datetime.datetime.utcnow())
86 utcnow=datetime.datetime.utcnow())
87 else:
87 else:
88 schema = LogSchema().bind(
88 schema = LogSchema().bind(
89 utcnow=datetime.datetime.utcnow())
89 utcnow=datetime.datetime.utcnow())
90
90
91 deserialized_logs = schema.deserialize(payload)
91 deserialized_logs = schema.deserialize(payload)
92 if sequence_accepted is False:
92 if sequence_accepted is False:
93 deserialized_logs = [deserialized_logs]
93 deserialized_logs = [deserialized_logs]
94
94
95 rate_limiting(request, application, 'per_application_logs_rate_limit',
95 rate_limiting(request, application, 'per_application_logs_rate_limit',
96 len(deserialized_logs))
96 len(deserialized_logs))
97
97
98 # pprint.pprint(deserialized_logs)
98 # pprint.pprint(deserialized_logs)
99
99
100 # we need to split those out so we can process the pkey ones one by one
100 # we need to split those out so we can process the pkey ones one by one
101 non_pkey_logs = [log_dict for log_dict in deserialized_logs
101 non_pkey_logs = [log_dict for log_dict in deserialized_logs
102 if not log_dict['primary_key']]
102 if not log_dict['primary_key']]
103 pkey_dict = {}
103 pkey_dict = {}
104 # try to process the logs as best as we can and group together to reduce
104 # try to process the logs as best as we can and group together to reduce
105 # the amount of
105 # the amount of
106 for log_dict in deserialized_logs:
106 for log_dict in deserialized_logs:
107 if log_dict['primary_key']:
107 if log_dict['primary_key']:
108 key = (log_dict['primary_key'], log_dict['namespace'],)
108 key = (log_dict['primary_key'], log_dict['namespace'],)
109 if not key in pkey_dict:
109 if not key in pkey_dict:
110 pkey_dict[key] = []
110 pkey_dict[key] = []
111 pkey_dict[key].append(log_dict)
111 pkey_dict[key].append(log_dict)
112
112
113 if non_pkey_logs:
113 if non_pkey_logs:
114 log.debug('%s non-pkey logs received: %s' % (application,
114 log.debug('%s non-pkey logs received: %s' % (application,
115 len(non_pkey_logs)))
115 len(non_pkey_logs)))
116 tasks.add_logs.delay(application.resource_id, params, non_pkey_logs)
116 tasks.add_logs.delay(application.resource_id, params, non_pkey_logs)
117 if pkey_dict:
117 if pkey_dict:
118 logs_to_insert = []
118 logs_to_insert = []
119 for primary_key_tuple, payload in pkey_dict.items():
119 for primary_key_tuple, payload in pkey_dict.items():
120 sorted_logs = sorted(payload, key=lambda x: x['date'])
120 sorted_logs = sorted(payload, key=lambda x: x['date'])
121 logs_to_insert.append(sorted_logs[-1])
121 logs_to_insert.append(sorted_logs[-1])
122 log.debug('%s pkey logs received: %s' % (application,
122 log.debug('%s pkey logs received: %s' % (application,
123 len(logs_to_insert)))
123 len(logs_to_insert)))
124 tasks.add_logs.delay(application.resource_id, params, logs_to_insert)
124 tasks.add_logs.delay(application.resource_id, params, logs_to_insert)
125
125
126 log.info('LOG call %s %s client:%s' % (
126 log.info('LOG call %s %s client:%s' % (
127 application, proto_version, request.headers.get('user_agent')))
127 application, proto_version, request.headers.get('user_agent')))
128 return 'OK: Logs accepted'
128 return 'OK: Logs accepted'
129
129
130
130
131 @view_config(route_name='api_request_stats', renderer='string',
131 @view_config(route_name='api_request_stats', renderer='string',
132 permission='create', require_csrf=False)
132 permission='create', require_csrf=False)
133 @view_config(route_name='api_metrics', renderer='string',
133 @view_config(route_name='api_metrics', renderer='string',
134 permission='create', require_csrf=False)
134 permission='create', require_csrf=False)
135 def request_metrics_create(request):
135 def request_metrics_create(request):
136 """
136 """
137 Endpoint for performance metrics, aggregates view performance stats
137 Endpoint for performance metrics, aggregates view performance stats
138 and converts them to general metric row
138 and converts them to general metric row
139 """
139 """
140 application = request.context.resource
140 application = request.context.resource
141 if request.method.upper() == 'OPTIONS':
141 if request.method.upper() == 'OPTIONS':
142 return check_cors(request, application)
142 return check_cors(request, application)
143 else:
143 else:
144 check_cors(request, application, should_return=False)
144 check_cors(request, application, should_return=False)
145
145
146 params = dict(request.params.copy())
146 params = dict(request.params.copy())
147 proto_version = parse_proto(params.get('protocol_version', ''))
147 proto_version = parse_proto(params.get('protocol_version', ''))
148
148
149 payload = request.unsafe_json_body
149 payload = request.unsafe_json_body
150 schema = MetricsListSchema()
150 schema = MetricsListSchema()
151 dataset = schema.deserialize(payload)
151 dataset = schema.deserialize(payload)
152
152
153 rate_limiting(request, application, 'per_application_metrics_rate_limit',
153 rate_limiting(request, application, 'per_application_metrics_rate_limit',
154 len(dataset))
154 len(dataset))
155
155
156 # looping report data
156 # looping report data
157 metrics = {}
157 metrics = {}
158 for metric in dataset:
158 for metric in dataset:
159 server_name = metric.get('server', '').lower() or 'unknown'
159 server_name = metric.get('server', '').lower() or 'unknown'
160 start_interval = convert_date(metric['timestamp'])
160 start_interval = convert_date(metric['timestamp'])
161 start_interval = start_interval.replace(second=0, microsecond=0)
161 start_interval = start_interval.replace(second=0, microsecond=0)
162
162
163 for view_name, view_metrics in metric['metrics']:
163 for view_name, view_metrics in metric['metrics']:
164 key = '%s%s%s' % (metric['server'], start_interval, view_name)
164 key = '%s%s%s' % (metric['server'], start_interval, view_name)
165 if start_interval not in metrics:
165 if start_interval not in metrics:
166 metrics[key] = {"requests": 0, "main": 0, "sql": 0,
166 metrics[key] = {"requests": 0, "main": 0, "sql": 0,
167 "nosql": 0, "remote": 0, "tmpl": 0,
167 "nosql": 0, "remote": 0, "tmpl": 0,
168 "custom": 0, 'sql_calls': 0,
168 "custom": 0, 'sql_calls': 0,
169 'nosql_calls': 0,
169 'nosql_calls': 0,
170 'remote_calls': 0, 'tmpl_calls': 0,
170 'remote_calls': 0, 'tmpl_calls': 0,
171 'custom_calls': 0,
171 'custom_calls': 0,
172 "start_interval": start_interval,
172 "start_interval": start_interval,
173 "server_name": server_name,
173 "server_name": server_name,
174 "view_name": view_name
174 "view_name": view_name
175 }
175 }
176 metrics[key]["requests"] += int(view_metrics['requests'])
176 metrics[key]["requests"] += int(view_metrics['requests'])
177 metrics[key]["main"] += round(view_metrics['main'], 5)
177 metrics[key]["main"] += round(view_metrics['main'], 5)
178 metrics[key]["sql"] += round(view_metrics['sql'], 5)
178 metrics[key]["sql"] += round(view_metrics['sql'], 5)
179 metrics[key]["nosql"] += round(view_metrics['nosql'], 5)
179 metrics[key]["nosql"] += round(view_metrics['nosql'], 5)
180 metrics[key]["remote"] += round(view_metrics['remote'], 5)
180 metrics[key]["remote"] += round(view_metrics['remote'], 5)
181 metrics[key]["tmpl"] += round(view_metrics['tmpl'], 5)
181 metrics[key]["tmpl"] += round(view_metrics['tmpl'], 5)
182 metrics[key]["custom"] += round(view_metrics.get('custom', 0.0),
182 metrics[key]["custom"] += round(view_metrics.get('custom', 0.0),
183 5)
183 5)
184 metrics[key]["sql_calls"] += int(
184 metrics[key]["sql_calls"] += int(
185 view_metrics.get('sql_calls', 0))
185 view_metrics.get('sql_calls', 0))
186 metrics[key]["nosql_calls"] += int(
186 metrics[key]["nosql_calls"] += int(
187 view_metrics.get('nosql_calls', 0))
187 view_metrics.get('nosql_calls', 0))
188 metrics[key]["remote_calls"] += int(
188 metrics[key]["remote_calls"] += int(
189 view_metrics.get('remote_calls', 0))
189 view_metrics.get('remote_calls', 0))
190 metrics[key]["tmpl_calls"] += int(
190 metrics[key]["tmpl_calls"] += int(
191 view_metrics.get('tmpl_calls', 0))
191 view_metrics.get('tmpl_calls', 0))
192 metrics[key]["custom_calls"] += int(
192 metrics[key]["custom_calls"] += int(
193 view_metrics.get('custom_calls', 0))
193 view_metrics.get('custom_calls', 0))
194
194
195 if not metrics[key]["requests"]:
195 if not metrics[key]["requests"]:
196 # fix this here because validator can't
196 # fix this here because validator can't
197 metrics[key]["requests"] = 1
197 metrics[key]["requests"] = 1
198 # metrics dict is being built to minimize
198 # metrics dict is being built to minimize
199 # the amount of queries used
199 # the amount of queries used
200 # in case we get multiple rows from same minute
200 # in case we get multiple rows from same minute
201
201
202 normalized_metrics = []
202 normalized_metrics = []
203 for metric in metrics.values():
203 for metric in metrics.values():
204 new_metric = {
204 new_metric = {
205 'namespace': 'appenlight.request_metric',
205 'namespace': 'appenlight.request_metric',
206 'timestamp': metric.pop('start_interval'),
206 'timestamp': metric.pop('start_interval'),
207 'server_name': metric['server_name'],
207 'server_name': metric['server_name'],
208 'tags': list(metric.items())
208 'tags': list(metric.items())
209 }
209 }
210 normalized_metrics.append(new_metric)
210 normalized_metrics.append(new_metric)
211
211
212 tasks.add_metrics.delay(application.resource_id, params,
212 tasks.add_metrics.delay(application.resource_id, params,
213 normalized_metrics, proto_version)
213 normalized_metrics, proto_version)
214
214
215 log.info('REQUEST METRICS call {} {} client:{}'.format(
215 log.info('REQUEST METRICS call {} {} client:{}'.format(
216 application.resource_name, proto_version,
216 application.resource_name, proto_version,
217 request.headers.get('user_agent')))
217 request.headers.get('user_agent')))
218 return 'OK: request metrics accepted'
218 return 'OK: request metrics accepted'
219
219
220
220
221 @view_config(route_name='api_general_metrics', renderer='string',
221 @view_config(route_name='api_general_metrics', renderer='string',
222 permission='create', require_csrf=False)
222 permission='create', require_csrf=False)
223 @view_config(route_name='api_general_metric', renderer='string',
223 @view_config(route_name='api_general_metric', renderer='string',
224 permission='create', require_csrf=False)
224 permission='create', require_csrf=False)
225 def general_metrics_create(request):
225 def general_metrics_create(request):
226 """
226 """
227 Endpoint for general metrics aggregation
227 Endpoint for general metrics aggregation
228 """
228 """
229 application = request.context.resource
229 application = request.context.resource
230 if request.method.upper() == 'OPTIONS':
230 if request.method.upper() == 'OPTIONS':
231 return check_cors(request, application)
231 return check_cors(request, application)
232 else:
232 else:
233 check_cors(request, application, should_return=False)
233 check_cors(request, application, should_return=False)
234
234
235 params = dict(request.params.copy())
235 params = dict(request.params.copy())
236 proto_version = parse_proto(params.get('protocol_version', ''))
236 proto_version = parse_proto(params.get('protocol_version', ''))
237 payload = request.unsafe_json_body
237 payload = request.unsafe_json_body
238 sequence_accepted = request.matched_route.name == 'api_general_metrics'
238 sequence_accepted = request.matched_route.name == 'api_general_metrics'
239 if sequence_accepted:
239 if sequence_accepted:
240 if application.allow_permanent_storage:
240 if application.allow_permanent_storage:
241 schema = GeneralMetricsPermanentListSchema().bind(
241 schema = GeneralMetricsPermanentListSchema().bind(
242 utcnow=datetime.datetime.utcnow())
242 utcnow=datetime.datetime.utcnow())
243 else:
243 else:
244 schema = GeneralMetricsListSchema().bind(
244 schema = GeneralMetricsListSchema().bind(
245 utcnow=datetime.datetime.utcnow())
245 utcnow=datetime.datetime.utcnow())
246 else:
246 else:
247 if application.allow_permanent_storage:
247 if application.allow_permanent_storage:
248 schema = GeneralMetricPermanentSchema().bind(
248 schema = GeneralMetricPermanentSchema().bind(
249 utcnow=datetime.datetime.utcnow())
249 utcnow=datetime.datetime.utcnow())
250 else:
250 else:
251 schema = GeneralMetricSchema().bind(
251 schema = GeneralMetricSchema().bind(
252 utcnow=datetime.datetime.utcnow())
252 utcnow=datetime.datetime.utcnow())
253
253
254 deserialized_metrics = schema.deserialize(payload)
254 deserialized_metrics = schema.deserialize(payload)
255 if sequence_accepted is False:
255 if sequence_accepted is False:
256 deserialized_metrics = [deserialized_metrics]
256 deserialized_metrics = [deserialized_metrics]
257
257
258 rate_limiting(request, application, 'per_application_metrics_rate_limit',
258 rate_limiting(request, application, 'per_application_metrics_rate_limit',
259 len(deserialized_metrics))
259 len(deserialized_metrics))
260
260
261 tasks.add_metrics.delay(application.resource_id, params,
261 tasks.add_metrics.delay(application.resource_id, params,
262 deserialized_metrics, proto_version)
262 deserialized_metrics, proto_version)
263
263
264 log.info('METRICS call {} {} client:{}'.format(
264 log.info('METRICS call {} {} client:{}'.format(
265 application.resource_name, proto_version,
265 application.resource_name, proto_version,
266 request.headers.get('user_agent')))
266 request.headers.get('user_agent')))
267 return 'OK: Metrics accepted'
267 return 'OK: Metrics accepted'
268
268
269
269
270 @view_config(route_name='api_reports', renderer='string', permission='create',
270 @view_config(route_name='api_reports', renderer='string', permission='create',
271 require_csrf=False)
271 require_csrf=False)
272 @view_config(route_name='api_slow_reports', renderer='string',
272 @view_config(route_name='api_slow_reports', renderer='string',
273 permission='create', require_csrf=False)
273 permission='create', require_csrf=False)
274 @view_config(route_name='api_report', renderer='string', permission='create',
274 @view_config(route_name='api_report', renderer='string', permission='create',
275 require_csrf=False)
275 require_csrf=False)
276 def reports_create(request):
276 def reports_create(request):
277 """
277 """
278 Endpoint for exception and slowness reports
278 Endpoint for exception and slowness reports
279 """
279 """
280 # route_url('reports')
280 # route_url('reports')
281 application = request.context.resource
281 application = request.context.resource
282 if request.method.upper() == 'OPTIONS':
282 if request.method.upper() == 'OPTIONS':
283 return check_cors(request, application)
283 return check_cors(request, application)
284 else:
284 else:
285 check_cors(request, application, should_return=False)
285 check_cors(request, application, should_return=False)
286 params = dict(request.params.copy())
286 params = dict(request.params.copy())
287 proto_version = parse_proto(params.get('protocol_version', ''))
287 proto_version = parse_proto(params.get('protocol_version', ''))
288 payload = request.unsafe_json_body
288 payload = request.unsafe_json_body
289 sequence_accepted = request.matched_route.name == 'api_reports'
289 sequence_accepted = request.matched_route.name == 'api_reports'
290
290
291 if sequence_accepted:
291 if sequence_accepted:
292 schema = ReportListSchema_0_5().bind(
292 schema = ReportListSchema_0_5().bind(
293 utcnow=datetime.datetime.utcnow())
293 utcnow=datetime.datetime.utcnow())
294 else:
294 else:
295 schema = ReportSchema_0_5().bind(
295 schema = ReportSchema_0_5().bind(
296 utcnow=datetime.datetime.utcnow())
296 utcnow=datetime.datetime.utcnow())
297
297
298 deserialized_reports = schema.deserialize(payload)
298 deserialized_reports = schema.deserialize(payload)
299 if sequence_accepted is False:
299 if sequence_accepted is False:
300 deserialized_reports = [deserialized_reports]
300 deserialized_reports = [deserialized_reports]
301 if deserialized_reports:
301 if deserialized_reports:
302 rate_limiting(request, application,
302 rate_limiting(request, application,
303 'per_application_reports_rate_limit',
303 'per_application_reports_rate_limit',
304 len(deserialized_reports))
304 len(deserialized_reports))
305
305
306 # pprint.pprint(deserialized_reports)
306 # pprint.pprint(deserialized_reports)
307 tasks.add_reports.delay(application.resource_id, params,
307 tasks.add_reports.delay(application.resource_id, params,
308 deserialized_reports)
308 deserialized_reports)
309 log.info('REPORT call %s, %s client:%s' % (
309 log.info('REPORT call %s, %s client:%s' % (
310 application,
310 application,
311 proto_version,
311 proto_version,
312 request.headers.get('user_agent'))
312 request.headers.get('user_agent'))
313 )
313 )
314 return 'OK: Reports accepted'
314 return 'OK: Reports accepted'
315
315
316
316
317 @view_config(route_name='api_airbrake', renderer='string', permission='create',
317 @view_config(route_name='api_airbrake', renderer='string', permission='create',
318 require_csrf=False)
318 require_csrf=False)
319 def airbrake_xml_compat(request):
319 def airbrake_xml_compat(request):
320 """
320 """
321 Airbrake compatible endpoint for XML reports
321 Airbrake compatible endpoint for XML reports
322 """
322 """
323 application = request.context.resource
323 application = request.context.resource
324 if request.method.upper() == 'OPTIONS':
324 if request.method.upper() == 'OPTIONS':
325 return check_cors(request, application)
325 return check_cors(request, application)
326 else:
326 else:
327 check_cors(request, application, should_return=False)
327 check_cors(request, application, should_return=False)
328
328
329 params = request.params.copy()
329 params = dict(request.params.copy())
330
330
331 error_dict = parse_airbrake_xml(request)
331 error_dict = parse_airbrake_xml(request)
332 schema = ReportListSchema_0_5().bind(utcnow=datetime.datetime.utcnow())
332 schema = ReportListSchema_0_5().bind(utcnow=datetime.datetime.utcnow())
333 deserialized_reports = schema.deserialize([error_dict])
333 deserialized_reports = schema.deserialize([error_dict])
334 rate_limiting(request, application, 'per_application_reports_rate_limit',
334 rate_limiting(request, application, 'per_application_reports_rate_limit',
335 len(deserialized_reports))
335 len(deserialized_reports))
336
336
337 tasks.add_reports.delay(application.resource_id, params,
337 tasks.add_reports.delay(application.resource_id, params,
338 deserialized_reports)
338 deserialized_reports)
339 log.info('%s AIRBRAKE call for application %s, api_ver:%s client:%s' % (
339 log.info('%s AIRBRAKE call for application %s, api_ver:%s client:%s' % (
340 500, application.resource_name,
340 500, application.resource_name,
341 request.params.get('protocol_version', 'unknown'),
341 request.params.get('protocol_version', 'unknown'),
342 request.headers.get('user_agent'))
342 request.headers.get('user_agent'))
343 )
343 )
344 return '<notice><id>no-id</id><url>%s</url></notice>' % \
344 return '<notice><id>no-id</id><url>%s</url></notice>' % \
345 request.registry.settings['mailing.app_url']
345 request.registry.settings['mailing.app_url']
346
346
347
347
348 def decompress_gzip(data):
348 def decompress_gzip(data):
349 try:
349 try:
350 fp = io.StringIO(data)
350 fp = io.StringIO(data)
351 with GzipFile(fileobj=fp) as f:
351 with GzipFile(fileobj=fp) as f:
352 return f.read()
352 return f.read()
353 except Exception as exc:
353 except Exception as exc:
354 raise
354 raise
355 log.error(exc)
355 log.error(exc)
356 raise HTTPBadRequest()
356 raise HTTPBadRequest()
357
357
358
358
359 def decompress_zlib(data):
359 def decompress_zlib(data):
360 try:
360 try:
361 return zlib.decompress(data)
361 return zlib.decompress(data)
362 except Exception as exc:
362 except Exception as exc:
363 raise
363 raise
364 log.error(exc)
364 log.error(exc)
365 raise HTTPBadRequest()
365 raise HTTPBadRequest()
366
366
367
367
368 def decode_b64(data):
368 def decode_b64(data):
369 try:
369 try:
370 return base64.b64decode(data)
370 return base64.b64decode(data)
371 except Exception as exc:
371 except Exception as exc:
372 raise
372 raise
373 log.error(exc)
373 log.error(exc)
374 raise HTTPBadRequest()
374 raise HTTPBadRequest()
375
375
376
376
377 @view_config(route_name='api_sentry', renderer='string', permission='create',
377 @view_config(route_name='api_sentry', renderer='string', permission='create',
378 require_csrf=False)
378 require_csrf=False)
379 @view_config(route_name='api_sentry_slash', renderer='string',
379 @view_config(route_name='api_sentry_slash', renderer='string',
380 permission='create', require_csrf=False)
380 permission='create', require_csrf=False)
381 def sentry_compat(request):
381 def sentry_compat(request):
382 """
382 """
383 Sentry compatible endpoint
383 Sentry compatible endpoint
384 """
384 """
385 application = request.context.resource
385 application = request.context.resource
386 if request.method.upper() == 'OPTIONS':
386 if request.method.upper() == 'OPTIONS':
387 return check_cors(request, application)
387 return check_cors(request, application)
388 else:
388 else:
389 check_cors(request, application, should_return=False)
389 check_cors(request, application, should_return=False)
390
390
391 # handle various report encoding
391 # handle various report encoding
392 content_encoding = request.headers.get('Content-Encoding')
392 content_encoding = request.headers.get('Content-Encoding')
393 content_type = request.headers.get('Content-Type')
393 content_type = request.headers.get('Content-Type')
394 if content_encoding == 'gzip':
394 if content_encoding == 'gzip':
395 body = decompress_gzip(request.body)
395 body = decompress_gzip(request.body)
396 elif content_encoding == 'deflate':
396 elif content_encoding == 'deflate':
397 body = decompress_zlib(request.body)
397 body = decompress_zlib(request.body)
398 else:
398 else:
399 body = request.body
399 body = request.body
400 # attempt to fix string before decoding for stupid clients
400 # attempt to fix string before decoding for stupid clients
401 if content_type == 'application/x-www-form-urlencoded':
401 if content_type == 'application/x-www-form-urlencoded':
402 body = urllib.parse.unquote(body.decode('utf8'))
402 body = urllib.parse.unquote(body.decode('utf8'))
403 check_char = '{' if isinstance(body, str) else b'{'
403 check_char = '{' if isinstance(body, str) else b'{'
404 if not body.startswith(check_char):
404 if not body.startswith(check_char):
405 try:
405 try:
406 body = decode_b64(body)
406 body = decode_b64(body)
407 body = decompress_zlib(body)
407 body = decompress_zlib(body)
408 except Exception as exc:
408 except Exception as exc:
409 log.info(exc)
409 log.info(exc)
410
410
411 try:
411 try:
412 json_body = json.loads(body.decode('utf8'))
412 json_body = json.loads(body.decode('utf8'))
413 except ValueError:
413 except ValueError:
414 raise JSONException("Incorrect JSON")
414 raise JSONException("Incorrect JSON")
415
415
416 event, event_type = parse_sentry_event(json_body)
416 event, event_type = parse_sentry_event(json_body)
417
417
418 if event_type == ParsedSentryEventType.LOG:
418 if event_type == ParsedSentryEventType.LOG:
419 if application.allow_permanent_storage:
419 if application.allow_permanent_storage:
420 schema = LogSchemaPermanent().bind(
420 schema = LogSchemaPermanent().bind(
421 utcnow=datetime.datetime.utcnow())
421 utcnow=datetime.datetime.utcnow())
422 else:
422 else:
423 schema = LogSchema().bind(
423 schema = LogSchema().bind(
424 utcnow=datetime.datetime.utcnow())
424 utcnow=datetime.datetime.utcnow())
425 deserialized_logs = schema.deserialize(event)
425 deserialized_logs = schema.deserialize(event)
426 non_pkey_logs = [deserialized_logs]
426 non_pkey_logs = [deserialized_logs]
427 log.debug('%s non-pkey logs received: %s' % (application,
427 log.debug('%s non-pkey logs received: %s' % (application,
428 len(non_pkey_logs)))
428 len(non_pkey_logs)))
429 tasks.add_logs.delay(application.resource_id, {}, non_pkey_logs)
429 tasks.add_logs.delay(application.resource_id, {}, non_pkey_logs)
430 if event_type == ParsedSentryEventType.ERROR_REPORT:
430 if event_type == ParsedSentryEventType.ERROR_REPORT:
431 schema = ReportSchema_0_5().bind(utcnow=datetime.datetime.utcnow())
431 schema = ReportSchema_0_5().bind(utcnow=datetime.datetime.utcnow())
432 deserialized_reports = [schema.deserialize(event)]
432 deserialized_reports = [schema.deserialize(event)]
433 rate_limiting(request, application,
433 rate_limiting(request, application,
434 'per_application_reports_rate_limit',
434 'per_application_reports_rate_limit',
435 len(deserialized_reports))
435 len(deserialized_reports))
436 tasks.add_reports.delay(application.resource_id, {},
436 tasks.add_reports.delay(application.resource_id, {},
437 deserialized_reports)
437 deserialized_reports)
438 return 'OK: Events accepted'
438 return 'OK: Events accepted'
General Comments 0
You need to be logged in to leave comments. Login now