api: added store_exception_api for remote exception storage.

- this will be used in the new indexer that could now report indexing error right back into RhodeCode.
This commit is contained in:
Marcin Kuzminski 2018-12-18 12:05:03 +01:00
parent 1e446c9382
commit 03fcdf8f0b
3 changed files with 145 additions and 8 deletions

View file

@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2010-2018 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
# (only), as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# This program is dual-licensed. If you wish to learn more about the
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/
import pytest
from rhodecode.api.tests.utils import build_data, api_call, assert_ok, assert_error
@pytest.mark.usefixtures("testuser_api", "app")
class TestStoreException(object):
def test_store_exception_invalid_json(self):
id_, params = build_data(self.apikey, 'store_exception',
exc_data_json='XXX,{')
response = api_call(self.app, params)
expected = 'Failed to parse JSON data from exc_data_json field. ' \
'Please make sure it contains a valid JSON.'
assert_error(id_, expected, given=response.body)
def test_store_exception_missing_json_params_json(self):
id_, params = build_data(self.apikey, 'store_exception',
exc_data_json='{"foo":"bar"}')
response = api_call(self.app, params)
expected = "Missing exc_traceback, or exc_type_name in " \
"exc_data_json field. Missing: 'exc_traceback'"
assert_error(id_, expected, given=response.body)
def test_store_exception(self):
id_, params = build_data(
self.apikey, 'store_exception',
exc_data_json='{"exc_traceback": "invalid", "exc_type_name":"ValueError"}')
response = api_call(self.app, params)
exc_id = response.json['result']['exc_id']
expected = {
'exc_id': exc_id,
'exc_url': 'http://example.com/_admin/settings/exceptions/{}'.format(exc_id)
}
assert_ok(id_, expected, given=response.body)

View file

@ -30,6 +30,8 @@ from rhodecode.api.utils import (
from rhodecode.lib.utils import repo2db_mapper
from rhodecode.lib import system_info
from rhodecode.lib import user_sessions
from rhodecode.lib import exc_tracking
from rhodecode.lib.ext_json import json
from rhodecode.lib.utils2 import safe_int
from rhodecode.model.db import UserIpMap
from rhodecode.model.scm import ScmModel
@ -293,7 +295,7 @@ def get_method(request, apiuser, pattern=Optional('*')):
:param apiuser: This is filled automatically from the |authtoken|.
:type apiuser: AuthUser
:param pattern: pattern to match method names against
:type older_then: Optional("*")
:type pattern: Optional("*")
Example output:
@ -349,3 +351,64 @@ def get_method(request, apiuser, pattern=Optional('*')):
args_desc.append(func_kwargs)
return matches.keys() + args_desc
@jsonrpc_method()
def store_exception(request, apiuser, exc_data_json, prefix=Optional('rhodecode')):
"""
Stores sent exception inside the built-in exception tracker in |RCE| server.
This command can only be run using an |authtoken| with admin rights to
the specified repository.
This command takes the following options:
:param apiuser: This is filled automatically from the |authtoken|.
:type apiuser: AuthUser
:param exc_data_json: JSON data with exception e.g
{"exc_traceback": "Value `1` is not allowed", "exc_type_name": "ValueError"}
:type exc_data_json: JSON data
:param prefix: prefix for error type, e.g 'rhodecode', 'vcsserver', 'rhodecode-tools'
:type prefix: Optional("rhodecode")
Example output:
.. code-block:: bash
id : <id_given_in_input>
"result": {
"exc_id": 139718459226384,
"exc_url": "http://localhost:8080/_admin/settings/exceptions/139718459226384"
}
error : null
"""
if not has_superadmin_permission(apiuser):
raise JSONRPCForbidden()
prefix = Optional.extract(prefix)
exc_id = exc_tracking.generate_id()
try:
exc_data = json.loads(exc_data_json)
except Exception:
log.error('Failed to parse JSON: %r', exc_data_json)
raise JSONRPCError('Failed to parse JSON data from exc_data_json field. '
'Please make sure it contains a valid JSON.')
try:
exc_traceback = exc_data['exc_traceback']
exc_type_name = exc_data['exc_type_name']
except KeyError as err:
raise JSONRPCError('Missing exc_traceback, or exc_type_name '
'in exc_data_json field. Missing: {}'.format(err))
exc_tracking._store_exception(
exc_id=exc_id, exc_traceback=exc_traceback,
exc_type_name=exc_type_name, prefix=prefix)
exc_url = request.route_url(
'admin_settings_exception_tracker_show', exception_id=exc_id)
return {'exc_id': exc_id, 'exc_url': exc_url}

View file

@ -67,14 +67,13 @@ def get_exc_store():
return _exc_store_path
def _store_exception(exc_id, exc_info, prefix):
exc_type, exc_value, exc_traceback = exc_info
tb = ''.join(traceback.format_exception(
exc_type, exc_value, exc_traceback, None))
def _store_exception(exc_id, exc_type_name, exc_traceback, prefix):
"""
Low level function to store exception in the exception tracker
"""
exc_type_name = exc_type.__name__
exc_store_path = get_exc_store()
exc_data, org_data = exc_serialize(exc_id, tb, exc_type_name)
exc_data, org_data = exc_serialize(exc_id, exc_traceback, exc_type_name)
exc_pref_id = '{}_{}_{}'.format(exc_id, prefix, org_data['exc_timestamp'])
if not os.path.isdir(exc_store_path):
os.makedirs(exc_store_path)
@ -84,6 +83,16 @@ def _store_exception(exc_id, exc_info, prefix):
log.debug('Stored generated exception %s as: %s', exc_id, stored_exc_path)
def _prepare_exception(exc_info):
exc_type, exc_value, exc_traceback = exc_info
exc_type_name = exc_type.__name__
tb = ''.join(traceback.format_exception(
exc_type, exc_value, exc_traceback, None))
return exc_type_name, tb
def store_exception(exc_id, exc_info, prefix=global_prefix):
"""
Example usage::
@ -93,7 +102,9 @@ def store_exception(exc_id, exc_info, prefix=global_prefix):
"""
try:
_store_exception(exc_id=exc_id, exc_info=exc_info, prefix=prefix)
exc_type_name, exc_traceback = _prepare_exception(exc_info)
_store_exception(exc_id=exc_id, exc_type_name=exc_type_name,
exc_traceback=exc_traceback, prefix=prefix)
except Exception:
log.exception('Failed to store exception `%s` information', exc_id)
# there's no way this can fail, it will crash server badly if it does.
@ -149,3 +160,7 @@ def delete_exception(exc_id, prefix=global_prefix):
log.exception('Failed to remove exception `%s` information', exc_id)
# there's no way this can fail, it will crash server badly if it does.
pass
def generate_id():
return id(object())