##// END OF EJS Templates
vcs-server: expose remote tracebacks from http backend using the Pyro4AwareFormatter.
marcink -
r1257:edb7f6bf default
parent child
Show More
@@ -18,6 +18,7
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import sys
21 import logging
22 import logging
22
23
23
24
@@ -73,16 +74,35 class Pyro4AwareFormatter(logging.Format
73
74
74 def formatException(self, ei):
75 def formatException(self, ei):
75 ex_type, ex_value, ex_tb = ei
76 ex_type, ex_value, ex_tb = ei
76 if hasattr(ex_value, '_pyroTraceback'):
77
77 # johbo: Avoiding to import pyro4 until we get an exception
78 local_tb = logging.Formatter.formatException(self, ei)
78 # which actually has a remote traceback. This avoids issues
79 if hasattr(ex_value, '_vcs_server_traceback'):
79 # when gunicorn is used with gevent, since the logging would
80
80 # trigger an import of Pyro4 before the patches of gevent
81 def formatRemoteTraceback(remote_tb_lines):
81 # are applied.
82 result = ["\n +--- This exception occured remotely on VCSServer - Remote traceback:\n\n"]
82 import Pyro4.util
83 result.append(remote_tb_lines)
83 return ''.join(
84 result.append("\n +--- End of remote traceback\n")
84 Pyro4.util.getPyroTraceback(ex_type, ex_value, ex_tb))
85 return result
85 return logging.Formatter.formatException(self, ei)
86
87 try:
88 if ex_type is not None and ex_value is None and ex_tb is None:
89 # possible old (3.x) call syntax where caller is only providing exception object
90 if type(ex_type) is not type:
91 raise TypeError(
92 "invalid argument: ex_type should be an exception type, or just supply no arguments at all")
93 if ex_type is None and ex_tb is None:
94 ex_type, ex_value, ex_tb = sys.exc_info()
95
96 remote_tb = getattr(ex_value, "_vcs_server_traceback", None)
97
98 if remote_tb:
99 remote_tb = formatRemoteTraceback(remote_tb)
100 return local_tb + ''.join(remote_tb)
101 finally:
102 # clean up cycle to traceback, to allow proper GC
103 del ex_type, ex_value, ex_tb
104
105 return local_tb
86
106
87
107
88 class ColorFormatter(Pyro4AwareFormatter):
108 class ColorFormatter(Pyro4AwareFormatter):
@@ -75,7 +75,7 def wrap_in_appenlight_if_enabled(app, s
75
75
76 class RemoteTracebackTracker(object):
76 class RemoteTracebackTracker(object):
77 """
77 """
78 Utility middleware which forwards Pyro4 remote traceback information.
78 Utility middleware which forwards VCSServer remote traceback information.
79 """
79 """
80
80
81 def __init__(self, app):
81 def __init__(self, app):
@@ -85,7 +85,7 class RemoteTracebackTracker(object):
85 try:
85 try:
86 return self.application(environ, start_response)
86 return self.application(environ, start_response)
87 except Exception as e:
87 except Exception as e:
88 if hasattr(e, '_pyroTraceback'):
88 if hasattr(e, '_vcs_server_traceback'):
89 track_extra_information(
89 track_extra_information(
90 environ, 'remote_traceback', ''.join(e._pyroTraceback))
90 environ, 'remote_traceback', e._vcs_server_traceback)
91 raise
91 raise
@@ -218,6 +218,12 def _remote_call(url, payload, exception
218 exc._vcs_kind = error['_vcs_kind']
218 exc._vcs_kind = error['_vcs_kind']
219 except KeyError:
219 except KeyError:
220 pass
220 pass
221
222 try:
223 exc._vcs_server_traceback = error['traceback']
224 except KeyError:
225 pass
226
221 raise exc
227 raise exc
222 return response.get('result')
228 return response.get('result')
223
229
@@ -185,10 +185,10 def map_vcs_exceptions(func):
185 # The error middleware adds information if it finds
185 # The error middleware adds information if it finds
186 # __traceback_info__ in a frame object. This way the remote
186 # __traceback_info__ in a frame object. This way the remote
187 # traceback information is made available in error reports.
187 # traceback information is made available in error reports.
188 remote_tb = getattr(e, '_pyroTraceback', None)
188 remote_tb = getattr(e, '_vcs_server_traceback', None)
189 if remote_tb:
189 if remote_tb:
190 __traceback_info__ = (
190 __traceback_info__ = (
191 'Found Pyro4 remote traceback information:\n\n' +
191 'Found VCSServer remote traceback information:\n\n' +
192 '\n'.join(remote_tb))
192 '\n'.join(remote_tb))
193
193
194 # Avoid that remote_tb also appears in the frame
194 # Avoid that remote_tb also appears in the frame
@@ -46,6 +46,7 function registerRCRoutes() {
46 pyroutes.register('files_home', '/%(repo_name)s/files/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
46 pyroutes.register('files_home', '/%(repo_name)s/files/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
47 pyroutes.register('files_history_home', '/%(repo_name)s/history/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
47 pyroutes.register('files_history_home', '/%(repo_name)s/history/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
48 pyroutes.register('files_authors_home', '/%(repo_name)s/authors/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
48 pyroutes.register('files_authors_home', '/%(repo_name)s/authors/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
49 pyroutes.register('files_annotate_home', '/%(repo_name)s/annotate/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
49 pyroutes.register('files_archive_home', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
50 pyroutes.register('files_archive_home', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
50 pyroutes.register('files_nodelist_home', '/%(repo_name)s/nodelist/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
51 pyroutes.register('files_nodelist_home', '/%(repo_name)s/nodelist/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
51 pyroutes.register('files_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
52 pyroutes.register('files_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
@@ -1331,22 +1331,21 def pytest_runtest_makereport(item, call
1331 """
1331 """
1332 Adding the remote traceback if the exception has this information.
1332 Adding the remote traceback if the exception has this information.
1333
1333
1334 Pyro4 attaches this information as the attribute `_pyroTraceback`
1334 Pyro4 attaches this information as the attribute `_vcs_server_traceback`
1335 to the exception instance.
1335 to the exception instance.
1336 """
1336 """
1337 outcome = yield
1337 outcome = yield
1338 report = outcome.get_result()
1338 report = outcome.get_result()
1339 if call.excinfo:
1339 if call.excinfo:
1340 _add_pyro_remote_traceback(report, call.excinfo.value)
1340 _add_vcsserver_remote_traceback(report, call.excinfo.value)
1341
1341
1342
1342
1343 def _add_pyro_remote_traceback(report, exc):
1343 def _add_vcsserver_remote_traceback(report, exc):
1344 pyro_traceback = getattr(exc, '_pyroTraceback', None)
1344 vcsserver_traceback = getattr(exc, '_vcs_server_traceback', None)
1345
1345
1346 if pyro_traceback:
1346 if vcsserver_traceback:
1347 traceback = ''.join(pyro_traceback)
1347 section = 'VCSServer remote traceback ' + report.when
1348 section = 'Pyro4 remote traceback ' + report.when
1348 report.sections.append((section, vcsserver_traceback))
1349 report.sections.append((section, traceback))
1350
1349
1351
1350
1352 @pytest.fixture(scope='session')
1351 @pytest.fixture(scope='session')
General Comments 0
You need to be logged in to leave comments. Login now