Show More
@@ -0,0 +1,146 b'' | |||||
|
1 | # -*- coding: utf-8 -*- | |||
|
2 | ||||
|
3 | # RhodeCode VCSServer provides access to different vcs backends via network. | |||
|
4 | # Copyright (C) 2014-2018 RhodeCode GmbH | |||
|
5 | # | |||
|
6 | # This program is free software; you can redistribute it and/or modify | |||
|
7 | # it under the terms of the GNU General Public License as published by | |||
|
8 | # the Free Software Foundation; either version 3 of the License, or | |||
|
9 | # (at your option) any later version. | |||
|
10 | # | |||
|
11 | # This program is distributed in the hope that it will be useful, | |||
|
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
14 | # GNU General Public License for more details. | |||
|
15 | # | |||
|
16 | # You should have received a copy of the GNU General Public License | |||
|
17 | # along with this program; if not, write to the Free Software Foundation, | |||
|
18 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |||
|
19 | ||||
|
20 | ||||
|
21 | import os | |||
|
22 | import time | |||
|
23 | import datetime | |||
|
24 | import msgpack | |||
|
25 | import logging | |||
|
26 | import traceback | |||
|
27 | import tempfile | |||
|
28 | ||||
|
29 | ||||
|
30 | log = logging.getLogger(__name__) | |||
|
31 | ||||
|
32 | # NOTE: Any changes should be synced with exc_tracking at rhodecode.lib.exc_tracking | |||
|
33 | global_prefix = 'vcsserver' | |||
|
34 | ||||
|
35 | ||||
|
36 | def exc_serialize(exc_id, tb, exc_type): | |||
|
37 | ||||
|
38 | data = { | |||
|
39 | 'version': 'v1', | |||
|
40 | 'exc_id': exc_id, | |||
|
41 | 'exc_utc_date': datetime.datetime.utcnow().isoformat(), | |||
|
42 | 'exc_timestamp': repr(time.time()), | |||
|
43 | 'exc_message': tb, | |||
|
44 | 'exc_type': exc_type, | |||
|
45 | } | |||
|
46 | return msgpack.packb(data), data | |||
|
47 | ||||
|
48 | ||||
|
49 | def exc_unserialize(tb): | |||
|
50 | return msgpack.unpackb(tb) | |||
|
51 | ||||
|
52 | ||||
|
53 | def get_exc_store(): | |||
|
54 | """ | |||
|
55 | Get and create exception store if it's not existing | |||
|
56 | """ | |||
|
57 | exc_store_dir = 'rc_exception_store_v1' | |||
|
58 | # fallback | |||
|
59 | _exc_store_path = os.path.join(tempfile.gettempdir(), exc_store_dir) | |||
|
60 | ||||
|
61 | exc_store_dir = '' # TODO: need a persistent cross instance store here | |||
|
62 | if exc_store_dir: | |||
|
63 | _exc_store_path = os.path.join(exc_store_dir, exc_store_dir) | |||
|
64 | ||||
|
65 | _exc_store_path = os.path.abspath(_exc_store_path) | |||
|
66 | if not os.path.isdir(_exc_store_path): | |||
|
67 | os.makedirs(_exc_store_path) | |||
|
68 | log.debug('Initializing exceptions store at %s', _exc_store_path) | |||
|
69 | return _exc_store_path | |||
|
70 | ||||
|
71 | ||||
|
72 | def _store_exception(exc_id, exc_info, prefix): | |||
|
73 | exc_type, exc_value, exc_traceback = exc_info | |||
|
74 | tb = ''.join(traceback.format_exception( | |||
|
75 | exc_type, exc_value, exc_traceback, None)) | |||
|
76 | ||||
|
77 | exc_type_name = exc_type.__name__ | |||
|
78 | exc_store_path = get_exc_store() | |||
|
79 | exc_data, org_data = exc_serialize(exc_id, tb, exc_type_name) | |||
|
80 | exc_pref_id = '{}_{}_{}'.format(exc_id, prefix, org_data['exc_timestamp']) | |||
|
81 | if not os.path.isdir(exc_store_path): | |||
|
82 | os.makedirs(exc_store_path) | |||
|
83 | stored_exc_path = os.path.join(exc_store_path, exc_pref_id) | |||
|
84 | with open(stored_exc_path, 'wb') as f: | |||
|
85 | f.write(exc_data) | |||
|
86 | log.debug('Stored generated exception %s as: %s', exc_id, stored_exc_path) | |||
|
87 | ||||
|
88 | ||||
|
89 | def store_exception(exc_id, exc_info, prefix=global_prefix): | |||
|
90 | try: | |||
|
91 | _store_exception(exc_id=exc_id, exc_info=exc_info, prefix=prefix) | |||
|
92 | except Exception: | |||
|
93 | log.exception('Failed to store exception `%s` information', exc_id) | |||
|
94 | # there's no way this can fail, it will crash server badly if it does. | |||
|
95 | pass | |||
|
96 | ||||
|
97 | ||||
|
98 | def _find_exc_file(exc_id, prefix=global_prefix): | |||
|
99 | exc_store_path = get_exc_store() | |||
|
100 | if prefix: | |||
|
101 | exc_id = '{}_{}'.format(exc_id, prefix) | |||
|
102 | else: | |||
|
103 | # search without a prefix | |||
|
104 | exc_id = '{}'.format(exc_id) | |||
|
105 | ||||
|
106 | # we need to search the store for such start pattern as above | |||
|
107 | for fname in os.listdir(exc_store_path): | |||
|
108 | if fname.startswith(exc_id): | |||
|
109 | exc_id = os.path.join(exc_store_path, fname) | |||
|
110 | break | |||
|
111 | continue | |||
|
112 | else: | |||
|
113 | exc_id = None | |||
|
114 | ||||
|
115 | return exc_id | |||
|
116 | ||||
|
117 | ||||
|
118 | def _read_exception(exc_id, prefix): | |||
|
119 | exc_id_file_path = _find_exc_file(exc_id=exc_id, prefix=prefix) | |||
|
120 | if exc_id_file_path: | |||
|
121 | with open(exc_id_file_path, 'rb') as f: | |||
|
122 | return exc_unserialize(f.read()) | |||
|
123 | else: | |||
|
124 | log.debug('Exception File `%s` not found', exc_id_file_path) | |||
|
125 | return None | |||
|
126 | ||||
|
127 | ||||
|
128 | def read_exception(exc_id, prefix=global_prefix): | |||
|
129 | try: | |||
|
130 | return _read_exception(exc_id=exc_id, prefix=prefix) | |||
|
131 | except Exception: | |||
|
132 | log.exception('Failed to read exception `%s` information', exc_id) | |||
|
133 | # there's no way this can fail, it will crash server badly if it does. | |||
|
134 | return None | |||
|
135 | ||||
|
136 | ||||
|
137 | def delete_exception(exc_id, prefix=global_prefix): | |||
|
138 | try: | |||
|
139 | exc_id_file_path = _find_exc_file(exc_id, prefix=prefix) | |||
|
140 | if exc_id_file_path: | |||
|
141 | os.remove(exc_id_file_path) | |||
|
142 | ||||
|
143 | except Exception: | |||
|
144 | log.exception('Failed to remove exception `%s` information', exc_id) | |||
|
145 | # there's no way this can fail, it will crash server badly if it does. | |||
|
146 | pass |
@@ -16,6 +16,7 b'' | |||||
16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
|
16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
17 |
|
17 | |||
18 | import os |
|
18 | import os | |
|
19 | import sys | |||
19 | import base64 |
|
20 | import base64 | |
20 | import locale |
|
21 | import locale | |
21 | import logging |
|
22 | import logging | |
@@ -36,6 +37,7 b' from vcsserver.git_lfs.app import GIT_LF' | |||||
36 | from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub |
|
37 | from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub | |
37 | from vcsserver.echo_stub.echo_app import EchoApp |
|
38 | from vcsserver.echo_stub.echo_app import EchoApp | |
38 | from vcsserver.exceptions import HTTPRepoLocked |
|
39 | from vcsserver.exceptions import HTTPRepoLocked | |
|
40 | from vcsserver.lib.exc_tracking import store_exception | |||
39 | from vcsserver.server import VcsServer |
|
41 | from vcsserver.server import VcsServer | |
40 |
|
42 | |||
41 | try: |
|
43 | try: | |
@@ -289,7 +291,21 b' class HTTPApplication(object):' | |||||
289 | try: |
|
291 | try: | |
290 | resp = getattr(remote, method)(*args, **kwargs) |
|
292 | resp = getattr(remote, method)(*args, **kwargs) | |
291 | except Exception as e: |
|
293 | except Exception as e: | |
292 |
|
|
294 | exc_info = list(sys.exc_info()) | |
|
295 | exc_type, exc_value, exc_traceback = exc_info | |||
|
296 | ||||
|
297 | org_exc = getattr(e, '_org_exc', None) | |||
|
298 | org_exc_name = None | |||
|
299 | if org_exc: | |||
|
300 | org_exc_name = org_exc.__class__.__name__ | |||
|
301 | # replace our "faked" exception with our org | |||
|
302 | exc_info[0] = org_exc.__class__ | |||
|
303 | exc_info[1] = org_exc | |||
|
304 | ||||
|
305 | store_exception(id(exc_info), exc_info) | |||
|
306 | ||||
|
307 | tb_info = ''.join( | |||
|
308 | traceback.format_exception(exc_type, exc_value, exc_traceback)) | |||
293 |
|
309 | |||
294 | type_ = e.__class__.__name__ |
|
310 | type_ = e.__class__.__name__ | |
295 | if type_ not in self.ALLOWED_EXCEPTIONS: |
|
311 | if type_ not in self.ALLOWED_EXCEPTIONS: | |
@@ -300,6 +316,7 b' class HTTPApplication(object):' | |||||
300 | 'error': { |
|
316 | 'error': { | |
301 | 'message': e.message, |
|
317 | 'message': e.message, | |
302 | 'traceback': tb_info, |
|
318 | 'traceback': tb_info, | |
|
319 | 'org_exc': org_exc_name, | |||
303 | 'type': type_ |
|
320 | 'type': type_ | |
304 | } |
|
321 | } | |
305 | } |
|
322 | } | |
@@ -492,9 +509,14 b' class HTTPApplication(object):' | |||||
492 | status_code = request.headers.get('X-RC-Locked-Status-Code') |
|
509 | status_code = request.headers.get('X-RC-Locked-Status-Code') | |
493 | return HTTPRepoLocked( |
|
510 | return HTTPRepoLocked( | |
494 | title=exception.message, status_code=status_code) |
|
511 | title=exception.message, status_code=status_code) | |
|
512 | ||||
|
513 | exc_info = request.exc_info | |||
|
514 | store_exception(id(exc_info), exc_info) | |||
|
515 | ||||
495 | traceback_info = 'unavailable' |
|
516 | traceback_info = 'unavailable' | |
496 | if request.exc_info: |
|
517 | if request.exc_info: | |
497 | traceback_info = traceback.format_exc(request.exc_info[2]) |
|
518 | exc_type, exc_value, exc_tb = request.exc_info | |
|
519 | traceback_info = ''.join(traceback.format_exception(exc_type, exc_value, exc_tb)) | |||
498 |
|
520 | |||
499 | log.error( |
|
521 | log.error( | |
500 | 'error occurred handling this request for path: %s, \n tb: %s', |
|
522 | 'error occurred handling this request for path: %s, \n tb: %s', |
General Comments 0
You need to be logged in to leave comments.
Login now