vcs-server: expose remote tracebacks from http backend using the Pyro4AwareFormatter.

This commit is contained in:
Marcin Kuzminski 2016-12-21 12:43:47 +01:00
parent 4a415909c6
commit 90deb7d32c
6 changed files with 49 additions and 23 deletions

View file

@ -18,6 +18,7 @@
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/
import sys
import logging
@ -73,16 +74,35 @@ class Pyro4AwareFormatter(logging.Formatter):
def formatException(self, ei):
ex_type, ex_value, ex_tb = ei
if hasattr(ex_value, '_pyroTraceback'):
# johbo: Avoiding to import pyro4 until we get an exception
# which actually has a remote traceback. This avoids issues
# when gunicorn is used with gevent, since the logging would
# trigger an import of Pyro4 before the patches of gevent
# are applied.
import Pyro4.util
return ''.join(
Pyro4.util.getPyroTraceback(ex_type, ex_value, ex_tb))
return logging.Formatter.formatException(self, ei)
local_tb = logging.Formatter.formatException(self, ei)
if hasattr(ex_value, '_vcs_server_traceback'):
def formatRemoteTraceback(remote_tb_lines):
result = ["\n +--- This exception occured remotely on VCSServer - Remote traceback:\n\n"]
result.append(remote_tb_lines)
result.append("\n +--- End of remote traceback\n")
return result
try:
if ex_type is not None and ex_value is None and ex_tb is None:
# possible old (3.x) call syntax where caller is only providing exception object
if type(ex_type) is not type:
raise TypeError(
"invalid argument: ex_type should be an exception type, or just supply no arguments at all")
if ex_type is None and ex_tb is None:
ex_type, ex_value, ex_tb = sys.exc_info()
remote_tb = getattr(ex_value, "_vcs_server_traceback", None)
if remote_tb:
remote_tb = formatRemoteTraceback(remote_tb)
return local_tb + ''.join(remote_tb)
finally:
# clean up cycle to traceback, to allow proper GC
del ex_type, ex_value, ex_tb
return local_tb
class ColorFormatter(Pyro4AwareFormatter):

View file

@ -75,7 +75,7 @@ def wrap_in_appenlight_if_enabled(app, settings, appenlight_client=None):
class RemoteTracebackTracker(object):
"""
Utility middleware which forwards Pyro4 remote traceback information.
Utility middleware which forwards VCSServer remote traceback information.
"""
def __init__(self, app):
@ -85,7 +85,7 @@ class RemoteTracebackTracker(object):
try:
return self.application(environ, start_response)
except Exception as e:
if hasattr(e, '_pyroTraceback'):
if hasattr(e, '_vcs_server_traceback'):
track_extra_information(
environ, 'remote_traceback', ''.join(e._pyroTraceback))
environ, 'remote_traceback', e._vcs_server_traceback)
raise

View file

@ -218,6 +218,12 @@ def _remote_call(url, payload, exceptions_map, session):
exc._vcs_kind = error['_vcs_kind']
except KeyError:
pass
try:
exc._vcs_server_traceback = error['traceback']
except KeyError:
pass
raise exc
return response.get('result')

View file

@ -185,10 +185,10 @@ def map_vcs_exceptions(func):
# The error middleware adds information if it finds
# __traceback_info__ in a frame object. This way the remote
# traceback information is made available in error reports.
remote_tb = getattr(e, '_pyroTraceback', None)
remote_tb = getattr(e, '_vcs_server_traceback', None)
if remote_tb:
__traceback_info__ = (
'Found Pyro4 remote traceback information:\n\n' +
'Found VCSServer remote traceback information:\n\n' +
'\n'.join(remote_tb))
# Avoid that remote_tb also appears in the frame

View file

@ -46,6 +46,7 @@ function registerRCRoutes() {
pyroutes.register('files_home', '/%(repo_name)s/files/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
pyroutes.register('files_history_home', '/%(repo_name)s/history/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
pyroutes.register('files_authors_home', '/%(repo_name)s/authors/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
pyroutes.register('files_annotate_home', '/%(repo_name)s/annotate/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
pyroutes.register('files_archive_home', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
pyroutes.register('files_nodelist_home', '/%(repo_name)s/nodelist/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
pyroutes.register('files_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);

View file

@ -1331,22 +1331,21 @@ def pytest_runtest_makereport(item, call):
"""
Adding the remote traceback if the exception has this information.
Pyro4 attaches this information as the attribute `_pyroTraceback`
Pyro4 attaches this information as the attribute `_vcs_server_traceback`
to the exception instance.
"""
outcome = yield
report = outcome.get_result()
if call.excinfo:
_add_pyro_remote_traceback(report, call.excinfo.value)
_add_vcsserver_remote_traceback(report, call.excinfo.value)
def _add_pyro_remote_traceback(report, exc):
pyro_traceback = getattr(exc, '_pyroTraceback', None)
def _add_vcsserver_remote_traceback(report, exc):
vcsserver_traceback = getattr(exc, '_vcs_server_traceback', None)
if pyro_traceback:
traceback = ''.join(pyro_traceback)
section = 'Pyro4 remote traceback ' + report.when
report.sections.append((section, traceback))
if vcsserver_traceback:
section = 'VCSServer remote traceback ' + report.when
report.sections.append((section, vcsserver_traceback))
@pytest.fixture(scope='session')