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 = |
|
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 = |
|
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 = |
|
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 |
|
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 |
|
|
154 | "\n+++ BEG SOURCE EXCEPTION +++\n\n" | |
82 |
|
|
155 | "{}\n" | |
83 |
|
|
156 | "+++ END SOURCE EXCEPTION +++\n" | |
84 |
|
|
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`, |
|
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 |
|
774 | except AmbiguousPrefixLookupError: | |
740 | e = RepoLookupError(rev) |
|
775 | e = RepoLookupError(rev) | |
741 |
e._org_exc_tb = |
|
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 = |
|
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 = |
|
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