##// 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 17 import os
18 18 import sys
19 19 import tempfile
20 import traceback
21 20 import logging
22 21 import urllib.parse
23 22
@@ -27,7 +26,7 b' from vcsserver import exceptions'
27 26 from vcsserver.exceptions import NoContentException
28 27 from vcsserver.hgcompat import archival
29 28 from vcsserver.str_utils import safe_bytes
30
29 from vcsserver.lib.exc_tracking import format_exc
31 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 72 Raise a new exception type with original args and traceback.
74 73 """
75
76 exc_type, exc_value, exc_traceback = sys.exc_info()
74 exc_info = sys.exc_info()
75 exc_type, exc_value, exc_traceback = exc_info
77 76 new_exc = new_type(*exc_value.args)
78 77
79 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 81 try:
83 82 raise new_exc.with_traceback(exc_traceback)
@@ -23,7 +23,6 b' import logging'
23 23 import uuid
24 24 import time
25 25 import wsgiref.util
26 import traceback
27 26 import tempfile
28 27 import psutil
29 28
@@ -62,7 +61,7 b' from vcsserver.git_lfs.app import GIT_LF'
62 61 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
63 62 from vcsserver.echo_stub.echo_app import EchoApp
64 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 65 from vcsserver.server import VcsServer
67 66
68 67 strict_vcs = True
@@ -410,8 +409,7 b' class HTTPApplication(object):'
410 409 if should_store_exc:
411 410 store_exception(id(exc_info), exc_info, request_path=request.path)
412 411
413 tb_info = ''.join(
414 traceback.format_exception(exc_type, exc_value, exc_traceback))
412 tb_info = format_exc(exc_info)
415 413
416 414 type_ = e.__class__.__name__
417 415 if type_ not in self.ALLOWED_EXCEPTIONS:
@@ -669,11 +667,10 b' class HTTPApplication(object):'
669 667
670 668 traceback_info = 'unavailable'
671 669 if request.exc_info:
672 exc_type, exc_value, exc_tb = request.exc_info
673 traceback_info = ''.join(traceback.format_exception(exc_type, exc_value, exc_tb))
670 traceback_info = format_exc(request.exc_info)
674 671
675 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 674 request.path, traceback_info)
678 675
679 676 statsd = request.registry.statsd
@@ -18,11 +18,13 b''
18 18
19 19 import os
20 20 import time
21 import sys
21 22 import datetime
22 23 import msgpack
23 24 import logging
24 25 import traceback
25 26 import tempfile
27 import glob
26 28
27 29 log = logging.getLogger(__name__)
28 30
@@ -31,7 +33,7 b" global_prefix = 'vcsserver'"
31 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 38 data = {
37 39 'version': 'v1',
@@ -41,6 +43,8 b' def exc_serialize(exc_id, tb, exc_type):'
41 43 'exc_message': tb,
42 44 'exc_type': exc_type,
43 45 }
46 if extra_data:
47 data.update(extra_data)
44 48 return msgpack.packb(data), data
45 49
46 50
@@ -48,10 +52,19 b' def exc_unserialize(tb):'
48 52 return msgpack.unpackb(tb)
49 53
50 54
55 _exc_store = None
56
57
51 58 def get_exc_store():
52 59 """
53 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 68 import vcsserver as app
56 69
57 70 exc_store_dir = app.CONFIG.get('exception_tracker.store_path', '') or tempfile.gettempdir()
@@ -61,36 +74,114 b' def get_exc_store():'
61 74 if not os.path.isdir(_exc_store_path):
62 75 os.makedirs(_exc_store_path)
63 76 log.debug('Initializing exceptions store at %s', _exc_store_path)
77 _exc_store = _exc_store_path
78
64 79 return _exc_store_path
65 80
66 81
67 def _store_exception(exc_id, exc_info, prefix, request_path=''):
68 exc_type, exc_value, exc_traceback = exc_info
82 def get_detailed_tb(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(
71 exc_type, exc_value, exc_traceback, None))
112 # last_stack = exc.stacks[-1]
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 148 if detailed_tb:
76 149 remote_tb = detailed_tb
77 150 if isinstance(detailed_tb, str):
78 151 remote_tb = [detailed_tb]
79
152
80 153 tb += (
81 '\n+++ BEG SOURCE EXCEPTION +++\n\n'
82 '{}\n'
83 '+++ END SOURCE EXCEPTION +++\n'
84 ''.format('\n'.join(remote_tb))
154 "\n+++ BEG SOURCE EXCEPTION +++\n\n"
155 "{}\n"
156 "+++ END SOURCE EXCEPTION +++\n"
157 "".format("\n".join(remote_tb))
85 158 )
86
159
87 160 # Avoid that remote_tb also appears in the frame
88 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 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 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 185 if not os.path.isdir(exc_store_path):
95 186 os.makedirs(exc_store_path)
96 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 192 log.error(
102 193 'error occurred handling this request.\n'
103 'Path: `%s`, tb: %s',
194 'Path: `%s`, %s',
104 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 206 try:
207 exc_type = exc_info[0]
208 exc_type_name = exc_type.__name__
209
116 210 _store_exception(exc_id=exc_id, exc_info=exc_info, prefix=prefix,
117 211 request_path=request_path)
212 return exc_id, exc_type_name
118 213 except Exception:
119 214 log.exception('Failed to store exception `%s` information', exc_id)
120 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 224 # search without a prefix
130 225 exc_id = f'{exc_id}'
131 226
132 # we need to search the store for such start pattern as above
133 for fname in os.listdir(exc_store_path):
134 if fname.startswith(exc_id):
135 exc_id = os.path.join(exc_store_path, fname)
136 break
137 continue
138 else:
139 exc_id = None
227 found_exc_id = None
228 matches = glob.glob(os.path.join(exc_store_path, exc_id) + '*')
229 if matches:
230 found_exc_id = matches[0]
140 231
141 return exc_id
232 return found_exc_id
142 233
143 234
144 235 def _read_exception(exc_id, prefix):
@@ -170,3 +261,7 b' def delete_exception(exc_id, prefix=glob'
170 261 log.exception('Failed to remove exception `%s` information', exc_id)
171 262 # there's no way this can fail, it will crash server badly if it does.
172 263 pass
264
265
266 def generate_id():
267 return id(object())
@@ -18,9 +18,9 b' import binascii'
18 18 import io
19 19 import logging
20 20 import stat
21 import sys
21 22 import urllib.request
22 23 import urllib.parse
23 import traceback
24 24 import hashlib
25 25
26 26 from hgext import largefiles, rebase, purge
@@ -33,19 +33,54 b' from mercurial.error import AmbiguousPre'
33 33
34 34 import vcsserver
35 35 from vcsserver import exceptions
36 from vcsserver.base import RepoFactory, obfuscate_qs, raise_from_original, store_archive_in_cache, ArchiveNode, BytesEnvelope, \
37 BinaryEnvelope
36 from vcsserver.base import (
37 RepoFactory,
38 obfuscate_qs,
39 raise_from_original,
40 store_archive_in_cache,
41 ArchiveNode,
42 BytesEnvelope,
43 BinaryEnvelope,
44 )
38 45 from vcsserver.hgcompat import (
39 archival, bin, clone, config as hgconfig, diffopts, hex, get_ctx,
40 hg_url as url_parser, httpbasicauthhandler, httpdigestauthhandler,
41 makepeer, instance, match, memctx, exchange, memfilectx, nullrev, hg_merge,
42 patch, peer, revrange, ui, hg_tag, Abort, LookupError, RepoError,
43 RepoLookupError, InterventionRequired, RequirementError,
44 alwaysmatcher, patternmatcher, hgutil, hgext_strip)
46 archival,
47 bin,
48 clone,
49 config as hgconfig,
50 diffopts,
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 80 from vcsserver.str_utils import ascii_bytes, ascii_str, safe_str, safe_bytes
46 81 from vcsserver.vcs_base import RemoteBase
47 82 from vcsserver.config import hooks as hooks_config
48
83 from vcsserver.lib.exc_tracking import format_exc
49 84
50 85 log = logging.getLogger(__name__)
51 86
@@ -736,15 +771,15 b' class HgRemote(RemoteBase):'
736 771 rev = rev + -1
737 772 try:
738 773 ctx = self._get_ctx(repo, rev)
739 except (AmbiguousPrefixLookupError) as e:
774 except AmbiguousPrefixLookupError:
740 775 e = RepoLookupError(rev)
741 e._org_exc_tb = traceback.format_exc()
776 e._org_exc_tb = format_exc(sys.exc_info())
742 777 raise exceptions.LookupException(e)(rev)
743 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 780 raise exceptions.LookupException(e)(rev)
746 781 except LookupError as e:
747 e._org_exc_tb = traceback.format_exc()
782 e._org_exc_tb = format_exc(sys.exc_info())
748 783 raise exceptions.LookupException(e)(e.name)
749 784
750 785 if not both:
General Comments 0
You need to be logged in to leave comments. Login now