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 = |
|
|
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 = |
|
|
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 = |
|
|
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 |
|
|
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 |
|
|
|
82 |
|
|
|
83 |
|
|
|
84 |
|
|
|
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`, |
|
|
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 |
|
|
774 | except AmbiguousPrefixLookupError: | |
|
740 | 775 | e = RepoLookupError(rev) |
|
741 |
e._org_exc_tb = |
|
|
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 = |
|
|
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 = |
|
|
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