##// END OF EJS Templates
api: added store_exception_api for remote exception storage....
marcink -
r3317:a029d28f default
parent child Browse files
Show More
@@ -0,0 +1,59 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21
22 import pytest
23
24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok, assert_error
25
26
27 @pytest.mark.usefixtures("testuser_api", "app")
28 class TestStoreException(object):
29
30 def test_store_exception_invalid_json(self):
31 id_, params = build_data(self.apikey, 'store_exception',
32 exc_data_json='XXX,{')
33 response = api_call(self.app, params)
34
35 expected = 'Failed to parse JSON data from exc_data_json field. ' \
36 'Please make sure it contains a valid JSON.'
37 assert_error(id_, expected, given=response.body)
38
39 def test_store_exception_missing_json_params_json(self):
40 id_, params = build_data(self.apikey, 'store_exception',
41 exc_data_json='{"foo":"bar"}')
42 response = api_call(self.app, params)
43
44 expected = "Missing exc_traceback, or exc_type_name in " \
45 "exc_data_json field. Missing: 'exc_traceback'"
46 assert_error(id_, expected, given=response.body)
47
48 def test_store_exception(self):
49 id_, params = build_data(
50 self.apikey, 'store_exception',
51 exc_data_json='{"exc_traceback": "invalid", "exc_type_name":"ValueError"}')
52 response = api_call(self.app, params)
53 exc_id = response.json['result']['exc_id']
54
55 expected = {
56 'exc_id': exc_id,
57 'exc_url': 'http://example.com/_admin/settings/exceptions/{}'.format(exc_id)
58 }
59 assert_ok(id_, expected, given=response.body)
@@ -30,6 +30,8 b' from rhodecode.api.utils import ('
30 from rhodecode.lib.utils import repo2db_mapper
30 from rhodecode.lib.utils import repo2db_mapper
31 from rhodecode.lib import system_info
31 from rhodecode.lib import system_info
32 from rhodecode.lib import user_sessions
32 from rhodecode.lib import user_sessions
33 from rhodecode.lib import exc_tracking
34 from rhodecode.lib.ext_json import json
33 from rhodecode.lib.utils2 import safe_int
35 from rhodecode.lib.utils2 import safe_int
34 from rhodecode.model.db import UserIpMap
36 from rhodecode.model.db import UserIpMap
35 from rhodecode.model.scm import ScmModel
37 from rhodecode.model.scm import ScmModel
@@ -293,7 +295,7 b' def get_method(request, apiuser, pattern'
293 :param apiuser: This is filled automatically from the |authtoken|.
295 :param apiuser: This is filled automatically from the |authtoken|.
294 :type apiuser: AuthUser
296 :type apiuser: AuthUser
295 :param pattern: pattern to match method names against
297 :param pattern: pattern to match method names against
296 :type older_then: Optional("*")
298 :type pattern: Optional("*")
297
299
298 Example output:
300 Example output:
299
301
@@ -349,3 +351,64 b' def get_method(request, apiuser, pattern'
349 args_desc.append(func_kwargs)
351 args_desc.append(func_kwargs)
350
352
351 return matches.keys() + args_desc
353 return matches.keys() + args_desc
354
355
356 @jsonrpc_method()
357 def store_exception(request, apiuser, exc_data_json, prefix=Optional('rhodecode')):
358 """
359 Stores sent exception inside the built-in exception tracker in |RCE| server.
360
361 This command can only be run using an |authtoken| with admin rights to
362 the specified repository.
363
364 This command takes the following options:
365
366 :param apiuser: This is filled automatically from the |authtoken|.
367 :type apiuser: AuthUser
368
369 :param exc_data_json: JSON data with exception e.g
370 {"exc_traceback": "Value `1` is not allowed", "exc_type_name": "ValueError"}
371 :type exc_data_json: JSON data
372
373 :param prefix: prefix for error type, e.g 'rhodecode', 'vcsserver', 'rhodecode-tools'
374 :type prefix: Optional("rhodecode")
375
376 Example output:
377
378 .. code-block:: bash
379
380 id : <id_given_in_input>
381 "result": {
382 "exc_id": 139718459226384,
383 "exc_url": "http://localhost:8080/_admin/settings/exceptions/139718459226384"
384 }
385 error : null
386 """
387 if not has_superadmin_permission(apiuser):
388 raise JSONRPCForbidden()
389
390 prefix = Optional.extract(prefix)
391 exc_id = exc_tracking.generate_id()
392
393 try:
394 exc_data = json.loads(exc_data_json)
395 except Exception:
396 log.error('Failed to parse JSON: %r', exc_data_json)
397 raise JSONRPCError('Failed to parse JSON data from exc_data_json field. '
398 'Please make sure it contains a valid JSON.')
399
400 try:
401 exc_traceback = exc_data['exc_traceback']
402 exc_type_name = exc_data['exc_type_name']
403 except KeyError as err:
404 raise JSONRPCError('Missing exc_traceback, or exc_type_name '
405 'in exc_data_json field. Missing: {}'.format(err))
406
407 exc_tracking._store_exception(
408 exc_id=exc_id, exc_traceback=exc_traceback,
409 exc_type_name=exc_type_name, prefix=prefix)
410
411 exc_url = request.route_url(
412 'admin_settings_exception_tracker_show', exception_id=exc_id)
413 return {'exc_id': exc_id, 'exc_url': exc_url}
414
@@ -67,14 +67,13 b' def get_exc_store():'
67 return _exc_store_path
67 return _exc_store_path
68
68
69
69
70 def _store_exception(exc_id, exc_info, prefix):
70 def _store_exception(exc_id, exc_type_name, exc_traceback, prefix):
71 exc_type, exc_value, exc_traceback = exc_info
71 """
72 tb = ''.join(traceback.format_exception(
72 Low level function to store exception in the exception tracker
73 exc_type, exc_value, exc_traceback, None))
73 """
74
74
75 exc_type_name = exc_type.__name__
76 exc_store_path = get_exc_store()
75 exc_store_path = get_exc_store()
77 exc_data, org_data = exc_serialize(exc_id, tb, exc_type_name)
76 exc_data, org_data = exc_serialize(exc_id, exc_traceback, exc_type_name)
78 exc_pref_id = '{}_{}_{}'.format(exc_id, prefix, org_data['exc_timestamp'])
77 exc_pref_id = '{}_{}_{}'.format(exc_id, prefix, org_data['exc_timestamp'])
79 if not os.path.isdir(exc_store_path):
78 if not os.path.isdir(exc_store_path):
80 os.makedirs(exc_store_path)
79 os.makedirs(exc_store_path)
@@ -84,6 +83,16 b' def _store_exception(exc_id, exc_info, p'
84 log.debug('Stored generated exception %s as: %s', exc_id, stored_exc_path)
83 log.debug('Stored generated exception %s as: %s', exc_id, stored_exc_path)
85
84
86
85
86 def _prepare_exception(exc_info):
87 exc_type, exc_value, exc_traceback = exc_info
88 exc_type_name = exc_type.__name__
89
90 tb = ''.join(traceback.format_exception(
91 exc_type, exc_value, exc_traceback, None))
92
93 return exc_type_name, tb
94
95
87 def store_exception(exc_id, exc_info, prefix=global_prefix):
96 def store_exception(exc_id, exc_info, prefix=global_prefix):
88 """
97 """
89 Example usage::
98 Example usage::
@@ -93,7 +102,9 b' def store_exception(exc_id, exc_info, pr'
93 """
102 """
94
103
95 try:
104 try:
96 _store_exception(exc_id=exc_id, exc_info=exc_info, prefix=prefix)
105 exc_type_name, exc_traceback = _prepare_exception(exc_info)
106 _store_exception(exc_id=exc_id, exc_type_name=exc_type_name,
107 exc_traceback=exc_traceback, prefix=prefix)
97 except Exception:
108 except Exception:
98 log.exception('Failed to store exception `%s` information', exc_id)
109 log.exception('Failed to store exception `%s` information', exc_id)
99 # there's no way this can fail, it will crash server badly if it does.
110 # there's no way this can fail, it will crash server badly if it does.
@@ -149,3 +160,7 b' def delete_exception(exc_id, prefix=glob'
149 log.exception('Failed to remove exception `%s` information', exc_id)
160 log.exception('Failed to remove exception `%s` information', exc_id)
150 # there's no way this can fail, it will crash server badly if it does.
161 # there's no way this can fail, it will crash server badly if it does.
151 pass
162 pass
163
164
165 def generate_id():
166 return id(object())
General Comments 0
You need to be logged in to leave comments. Login now