##// END OF EJS Templates
exceptions: allow error tracking and exception storage on vcsserver.
marcink -
r491:5e446817 default
parent child Browse files
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 tb_info = traceback.format_exc()
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