##// 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 16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 17
18 18 import os
19 import sys
19 20 import base64
20 21 import locale
21 22 import logging
@@ -36,6 +37,7 b' from vcsserver.git_lfs.app import GIT_LF'
36 37 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
37 38 from vcsserver.echo_stub.echo_app import EchoApp
38 39 from vcsserver.exceptions import HTTPRepoLocked
40 from vcsserver.lib.exc_tracking import store_exception
39 41 from vcsserver.server import VcsServer
40 42
41 43 try:
@@ -289,7 +291,21 b' class HTTPApplication(object):'
289 291 try:
290 292 resp = getattr(remote, method)(*args, **kwargs)
291 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 310 type_ = e.__class__.__name__
295 311 if type_ not in self.ALLOWED_EXCEPTIONS:
@@ -300,6 +316,7 b' class HTTPApplication(object):'
300 316 'error': {
301 317 'message': e.message,
302 318 'traceback': tb_info,
319 'org_exc': org_exc_name,
303 320 'type': type_
304 321 }
305 322 }
@@ -492,9 +509,14 b' class HTTPApplication(object):'
492 509 status_code = request.headers.get('X-RC-Locked-Status-Code')
493 510 return HTTPRepoLocked(
494 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 516 traceback_info = 'unavailable'
496 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 521 log.error(
500 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