##// END OF EJS Templates
exc-tracking: use more rich style tracebacks.
super-admin -
r1144:00db014b default
parent child Browse files
Show More
@@ -17,7 +17,6 b''
17 import os
17 import os
18 import sys
18 import sys
19 import tempfile
19 import tempfile
20 import traceback
21 import logging
20 import logging
22 import urllib.parse
21 import urllib.parse
23
22
@@ -27,7 +26,7 b' from vcsserver import exceptions'
27 from vcsserver.exceptions import NoContentException
26 from vcsserver.exceptions import NoContentException
28 from vcsserver.hgcompat import archival
27 from vcsserver.hgcompat import archival
29 from vcsserver.str_utils import safe_bytes
28 from vcsserver.str_utils import safe_bytes
30
29 from vcsserver.lib.exc_tracking import format_exc
31 log = logging.getLogger(__name__)
30 log = logging.getLogger(__name__)
32
31
33
32
@@ -72,12 +71,12 b' def raise_from_original(new_type, org_ex'
72 """
71 """
73 Raise a new exception type with original args and traceback.
72 Raise a new exception type with original args and traceback.
74 """
73 """
75
74 exc_info = sys.exc_info()
76 exc_type, exc_value, exc_traceback = sys.exc_info()
75 exc_type, exc_value, exc_traceback = exc_info
77 new_exc = new_type(*exc_value.args)
76 new_exc = new_type(*exc_value.args)
78
77
79 # store the original traceback into the new exc
78 # store the original traceback into the new exc
80 new_exc._org_exc_tb = traceback.format_tb(exc_traceback)
79 new_exc._org_exc_tb = format_exc(exc_info)
81
80
82 try:
81 try:
83 raise new_exc.with_traceback(exc_traceback)
82 raise new_exc.with_traceback(exc_traceback)
@@ -23,7 +23,6 b' import logging'
23 import uuid
23 import uuid
24 import time
24 import time
25 import wsgiref.util
25 import wsgiref.util
26 import traceback
27 import tempfile
26 import tempfile
28 import psutil
27 import psutil
29
28
@@ -62,7 +61,7 b' from vcsserver.git_lfs.app import GIT_LF'
62 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
61 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
63 from vcsserver.echo_stub.echo_app import EchoApp
62 from vcsserver.echo_stub.echo_app import EchoApp
64 from vcsserver.exceptions import HTTPRepoLocked, HTTPRepoBranchProtected
63 from vcsserver.exceptions import HTTPRepoLocked, HTTPRepoBranchProtected
65 from vcsserver.lib.exc_tracking import store_exception
64 from vcsserver.lib.exc_tracking import store_exception, format_exc
66 from vcsserver.server import VcsServer
65 from vcsserver.server import VcsServer
67
66
68 strict_vcs = True
67 strict_vcs = True
@@ -410,8 +409,7 b' class HTTPApplication(object):'
410 if should_store_exc:
409 if should_store_exc:
411 store_exception(id(exc_info), exc_info, request_path=request.path)
410 store_exception(id(exc_info), exc_info, request_path=request.path)
412
411
413 tb_info = ''.join(
412 tb_info = format_exc(exc_info)
414 traceback.format_exception(exc_type, exc_value, exc_traceback))
415
413
416 type_ = e.__class__.__name__
414 type_ = e.__class__.__name__
417 if type_ not in self.ALLOWED_EXCEPTIONS:
415 if type_ not in self.ALLOWED_EXCEPTIONS:
@@ -669,11 +667,10 b' class HTTPApplication(object):'
669
667
670 traceback_info = 'unavailable'
668 traceback_info = 'unavailable'
671 if request.exc_info:
669 if request.exc_info:
672 exc_type, exc_value, exc_tb = request.exc_info
670 traceback_info = format_exc(request.exc_info)
673 traceback_info = ''.join(traceback.format_exception(exc_type, exc_value, exc_tb))
674
671
675 log.error(
672 log.error(
676 'error occurred handling this request for path: %s, \n tb: %s',
673 'error occurred handling this request for path: %s, \n%s',
677 request.path, traceback_info)
674 request.path, traceback_info)
678
675
679 statsd = request.registry.statsd
676 statsd = request.registry.statsd
@@ -18,11 +18,13 b''
18
18
19 import os
19 import os
20 import time
20 import time
21 import sys
21 import datetime
22 import datetime
22 import msgpack
23 import msgpack
23 import logging
24 import logging
24 import traceback
25 import traceback
25 import tempfile
26 import tempfile
27 import glob
26
28
27 log = logging.getLogger(__name__)
29 log = logging.getLogger(__name__)
28
30
@@ -31,7 +33,7 b" global_prefix = 'vcsserver'"
31 exc_store_dir_name = 'rc_exception_store_v1'
33 exc_store_dir_name = 'rc_exception_store_v1'
32
34
33
35
34 def exc_serialize(exc_id, tb, exc_type):
36 def exc_serialize(exc_id, tb, exc_type, extra_data=None):
35
37
36 data = {
38 data = {
37 'version': 'v1',
39 'version': 'v1',
@@ -41,6 +43,8 b' def exc_serialize(exc_id, tb, exc_type):'
41 'exc_message': tb,
43 'exc_message': tb,
42 'exc_type': exc_type,
44 'exc_type': exc_type,
43 }
45 }
46 if extra_data:
47 data.update(extra_data)
44 return msgpack.packb(data), data
48 return msgpack.packb(data), data
45
49
46
50
@@ -48,10 +52,19 b' def exc_unserialize(tb):'
48 return msgpack.unpackb(tb)
52 return msgpack.unpackb(tb)
49
53
50
54
55 _exc_store = None
56
57
51 def get_exc_store():
58 def get_exc_store():
52 """
59 """
53 Get and create exception store if it's not existing
60 Get and create exception store if it's not existing
54 """
61 """
62 global _exc_store
63
64 if _exc_store is not None:
65 # quick global cache
66 return _exc_store
67
55 import vcsserver as app
68 import vcsserver as app
56
69
57 exc_store_dir = app.CONFIG.get('exception_tracker.store_path', '') or tempfile.gettempdir()
70 exc_store_dir = app.CONFIG.get('exception_tracker.store_path', '') or tempfile.gettempdir()
@@ -61,36 +74,114 b' def get_exc_store():'
61 if not os.path.isdir(_exc_store_path):
74 if not os.path.isdir(_exc_store_path):
62 os.makedirs(_exc_store_path)
75 os.makedirs(_exc_store_path)
63 log.debug('Initializing exceptions store at %s', _exc_store_path)
76 log.debug('Initializing exceptions store at %s', _exc_store_path)
77 _exc_store = _exc_store_path
78
64 return _exc_store_path
79 return _exc_store_path
65
80
66
81
67 def _store_exception(exc_id, exc_info, prefix, request_path=''):
82 def get_detailed_tb(exc_info):
68 exc_type, exc_value, exc_traceback = exc_info
83 from io import StringIO
84
85 try:
86 from pip._vendor.rich import traceback as rich_tb, scope as rich_scope, console as rich_console
87 except ImportError:
88 try:
89 from rich import traceback as rich_tb, scope as rich_scope, console as rich_console
90 except ImportError:
91 return None
92
93 console = rich_console.Console(
94 width=160,
95 file=StringIO()
96 )
97
98 exc = rich_tb.Traceback.extract(*exc_info, show_locals=True)
99
100 tb_rich = rich_tb.Traceback(
101 trace=exc,
102 width=160,
103 extra_lines=3,
104 theme=None,
105 word_wrap=False,
106 show_locals=False,
107 max_frames=100
108 )
109
110 formatted_locals = ""
69
111
70 tb = ''.join(traceback.format_exception(
112 # last_stack = exc.stacks[-1]
71 exc_type, exc_value, exc_traceback, None))
113 # last_frame = last_stack.frames[-1]
114 # if last_frame and last_frame.locals:
115 # console.print(
116 # rich_scope.render_scope(
117 # last_frame.locals,
118 # title=f'{last_frame.filename}:{last_frame.lineno}'))
119
120 console.print(tb_rich)
121 formatted_locals = console.file.getvalue()
122
123 return formatted_locals
124
72
125
73 detailed_tb = getattr(exc_value, '_org_exc_tb', None)
126 def get_request_metadata(request=None) -> dict:
127 request_metadata = {}
128 if not request:
129 from pyramid.threadlocal import get_current_request
130 request = get_current_request()
74
131
132 # NOTE(marcink): store request information into exc_data
133 if request:
134 request_metadata['client_address'] = getattr(request, 'client_addr', '')
135 request_metadata['user_agent'] = getattr(request, 'user_agent', '')
136 request_metadata['method'] = getattr(request, 'method', '')
137 request_metadata['url'] = getattr(request, 'url', '')
138 return request_metadata
139
140
141 def format_exc(exc_info):
142 exc_type, exc_value, exc_traceback = exc_info
143 tb = "++ TRACEBACK ++\n\n"
144 tb += "".join(traceback.format_exception(exc_type, exc_value, exc_traceback, None))
145
146 detailed_tb = getattr(exc_value, "_org_exc_tb", None)
147
75 if detailed_tb:
148 if detailed_tb:
76 remote_tb = detailed_tb
149 remote_tb = detailed_tb
77 if isinstance(detailed_tb, str):
150 if isinstance(detailed_tb, str):
78 remote_tb = [detailed_tb]
151 remote_tb = [detailed_tb]
79
152
80 tb += (
153 tb += (
81 '\n+++ BEG SOURCE EXCEPTION +++\n\n'
154 "\n+++ BEG SOURCE EXCEPTION +++\n\n"
82 '{}\n'
155 "{}\n"
83 '+++ END SOURCE EXCEPTION +++\n'
156 "+++ END SOURCE EXCEPTION +++\n"
84 ''.format('\n'.join(remote_tb))
157 "".format("\n".join(remote_tb))
85 )
158 )
86
159
87 # Avoid that remote_tb also appears in the frame
160 # Avoid that remote_tb also appears in the frame
88 del remote_tb
161 del remote_tb
162
163 locals_tb = get_detailed_tb(exc_info)
164 if locals_tb:
165 tb += f"\n+++ DETAILS +++\n\n{locals_tb}\n" ""
166 return tb
167
168
169 def _store_exception(exc_id, exc_info, prefix, request_path=''):
170 """
171 Low level function to store exception in the exception tracker
172 """
173
174 extra_data = {}
175 extra_data.update(get_request_metadata())
176
177 exc_type, exc_value, exc_traceback = exc_info
178 tb = format_exc(exc_info)
89
179
90 exc_type_name = exc_type.__name__
180 exc_type_name = exc_type.__name__
181 exc_data, org_data = exc_serialize(exc_id, tb, exc_type_name, extra_data=extra_data)
182
183 exc_pref_id = '{}_{}_{}'.format(exc_id, prefix, org_data['exc_timestamp'])
91 exc_store_path = get_exc_store()
184 exc_store_path = get_exc_store()
92 exc_data, org_data = exc_serialize(exc_id, tb, exc_type_name)
93 exc_pref_id = '{}_{}_{}'.format(exc_id, prefix, org_data['exc_timestamp'])
94 if not os.path.isdir(exc_store_path):
185 if not os.path.isdir(exc_store_path):
95 os.makedirs(exc_store_path)
186 os.makedirs(exc_store_path)
96 stored_exc_path = os.path.join(exc_store_path, exc_pref_id)
187 stored_exc_path = os.path.join(exc_store_path, exc_pref_id)
@@ -100,7 +191,7 b' def _store_exception(exc_id, exc_info, p'
100
191
101 log.error(
192 log.error(
102 'error occurred handling this request.\n'
193 'error occurred handling this request.\n'
103 'Path: `%s`, tb: %s',
194 'Path: `%s`, %s',
104 request_path, tb)
195 request_path, tb)
105
196
106
197
@@ -113,8 +204,12 b' def store_exception(exc_id, exc_info, pr'
113 """
204 """
114
205
115 try:
206 try:
207 exc_type = exc_info[0]
208 exc_type_name = exc_type.__name__
209
116 _store_exception(exc_id=exc_id, exc_info=exc_info, prefix=prefix,
210 _store_exception(exc_id=exc_id, exc_info=exc_info, prefix=prefix,
117 request_path=request_path)
211 request_path=request_path)
212 return exc_id, exc_type_name
118 except Exception:
213 except Exception:
119 log.exception('Failed to store exception `%s` information', exc_id)
214 log.exception('Failed to store exception `%s` information', exc_id)
120 # there's no way this can fail, it will crash server badly if it does.
215 # there's no way this can fail, it will crash server badly if it does.
@@ -129,16 +224,12 b' def _find_exc_file(exc_id, prefix=global'
129 # search without a prefix
224 # search without a prefix
130 exc_id = f'{exc_id}'
225 exc_id = f'{exc_id}'
131
226
132 # we need to search the store for such start pattern as above
227 found_exc_id = None
133 for fname in os.listdir(exc_store_path):
228 matches = glob.glob(os.path.join(exc_store_path, exc_id) + '*')
134 if fname.startswith(exc_id):
229 if matches:
135 exc_id = os.path.join(exc_store_path, fname)
230 found_exc_id = matches[0]
136 break
137 continue
138 else:
139 exc_id = None
140
231
141 return exc_id
232 return found_exc_id
142
233
143
234
144 def _read_exception(exc_id, prefix):
235 def _read_exception(exc_id, prefix):
@@ -170,3 +261,7 b' def delete_exception(exc_id, prefix=glob'
170 log.exception('Failed to remove exception `%s` information', exc_id)
261 log.exception('Failed to remove exception `%s` information', exc_id)
171 # there's no way this can fail, it will crash server badly if it does.
262 # there's no way this can fail, it will crash server badly if it does.
172 pass
263 pass
264
265
266 def generate_id():
267 return id(object())
@@ -18,9 +18,9 b' import binascii'
18 import io
18 import io
19 import logging
19 import logging
20 import stat
20 import stat
21 import sys
21 import urllib.request
22 import urllib.request
22 import urllib.parse
23 import urllib.parse
23 import traceback
24 import hashlib
24 import hashlib
25
25
26 from hgext import largefiles, rebase, purge
26 from hgext import largefiles, rebase, purge
@@ -33,19 +33,54 b' from mercurial.error import AmbiguousPre'
33
33
34 import vcsserver
34 import vcsserver
35 from vcsserver import exceptions
35 from vcsserver import exceptions
36 from vcsserver.base import RepoFactory, obfuscate_qs, raise_from_original, store_archive_in_cache, ArchiveNode, BytesEnvelope, \
36 from vcsserver.base import (
37 BinaryEnvelope
37 RepoFactory,
38 obfuscate_qs,
39 raise_from_original,
40 store_archive_in_cache,
41 ArchiveNode,
42 BytesEnvelope,
43 BinaryEnvelope,
44 )
38 from vcsserver.hgcompat import (
45 from vcsserver.hgcompat import (
39 archival, bin, clone, config as hgconfig, diffopts, hex, get_ctx,
46 archival,
40 hg_url as url_parser, httpbasicauthhandler, httpdigestauthhandler,
47 bin,
41 makepeer, instance, match, memctx, exchange, memfilectx, nullrev, hg_merge,
48 clone,
42 patch, peer, revrange, ui, hg_tag, Abort, LookupError, RepoError,
49 config as hgconfig,
43 RepoLookupError, InterventionRequired, RequirementError,
50 diffopts,
44 alwaysmatcher, patternmatcher, hgutil, hgext_strip)
51 hex,
52 get_ctx,
53 hg_url as url_parser,
54 httpbasicauthhandler,
55 httpdigestauthhandler,
56 makepeer,
57 instance,
58 match,
59 memctx,
60 exchange,
61 memfilectx,
62 nullrev,
63 hg_merge,
64 patch,
65 peer,
66 revrange,
67 ui,
68 hg_tag,
69 Abort,
70 LookupError,
71 RepoError,
72 RepoLookupError,
73 InterventionRequired,
74 RequirementError,
75 alwaysmatcher,
76 patternmatcher,
77 hgutil,
78 hgext_strip,
79 )
45 from vcsserver.str_utils import ascii_bytes, ascii_str, safe_str, safe_bytes
80 from vcsserver.str_utils import ascii_bytes, ascii_str, safe_str, safe_bytes
46 from vcsserver.vcs_base import RemoteBase
81 from vcsserver.vcs_base import RemoteBase
47 from vcsserver.config import hooks as hooks_config
82 from vcsserver.config import hooks as hooks_config
48
83 from vcsserver.lib.exc_tracking import format_exc
49
84
50 log = logging.getLogger(__name__)
85 log = logging.getLogger(__name__)
51
86
@@ -736,15 +771,15 b' class HgRemote(RemoteBase):'
736 rev = rev + -1
771 rev = rev + -1
737 try:
772 try:
738 ctx = self._get_ctx(repo, rev)
773 ctx = self._get_ctx(repo, rev)
739 except (AmbiguousPrefixLookupError) as e:
774 except AmbiguousPrefixLookupError:
740 e = RepoLookupError(rev)
775 e = RepoLookupError(rev)
741 e._org_exc_tb = traceback.format_exc()
776 e._org_exc_tb = format_exc(sys.exc_info())
742 raise exceptions.LookupException(e)(rev)
777 raise exceptions.LookupException(e)(rev)
743 except (TypeError, RepoLookupError, binascii.Error) as e:
778 except (TypeError, RepoLookupError, binascii.Error) as e:
744 e._org_exc_tb = traceback.format_exc()
779 e._org_exc_tb = format_exc(sys.exc_info())
745 raise exceptions.LookupException(e)(rev)
780 raise exceptions.LookupException(e)(rev)
746 except LookupError as e:
781 except LookupError as e:
747 e._org_exc_tb = traceback.format_exc()
782 e._org_exc_tb = format_exc(sys.exc_info())
748 raise exceptions.LookupException(e)(e.name)
783 raise exceptions.LookupException(e)(e.name)
749
784
750 if not both:
785 if not both:
General Comments 0
You need to be logged in to leave comments. Login now