##// END OF EJS Templates
exc-tracking: synced with ce
super-admin -
r1146:42e5e37a default
parent child Browse files
Show More
@@ -1,267 +1,272 b''
1 1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 2 # Copyright (C) 2014-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software; you can redistribute it and/or modify
5 5 # it under the terms of the GNU General Public License as published by
6 6 # the Free Software Foundation; either version 3 of the License, or
7 7 # (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software Foundation,
16 16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 17
18
18 import io
19 19 import os
20 20 import time
21 21 import sys
22 22 import datetime
23 23 import msgpack
24 24 import logging
25 25 import traceback
26 26 import tempfile
27 27 import glob
28 28
29 29 log = logging.getLogger(__name__)
30 30
31 31 # NOTE: Any changes should be synced with exc_tracking at rhodecode.lib.exc_tracking
32 32 global_prefix = 'vcsserver'
33 33 exc_store_dir_name = 'rc_exception_store_v1'
34 34
35 35
36 36 def exc_serialize(exc_id, tb, exc_type, extra_data=None):
37
38 37 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,
38 "version": "v1",
39 "exc_id": exc_id,
40 "exc_utc_date": datetime.datetime.utcnow().isoformat(),
41 "exc_timestamp": repr(time.time()),
42 "exc_message": tb,
43 "exc_type": exc_type,
45 44 }
46 45 if extra_data:
47 46 data.update(extra_data)
48 47 return msgpack.packb(data), data
49 48
50 49
51 50 def exc_unserialize(tb):
52 51 return msgpack.unpackb(tb)
53 52
54 53
55 54 _exc_store = None
56 55
57 56
58 57 def get_exc_store():
59 58 """
60 59 Get and create exception store if it's not existing
61 60 """
62 61 global _exc_store
63 62
64 63 if _exc_store is not None:
65 64 # quick global cache
66 65 return _exc_store
67 66
68 67 import vcsserver as app
69 68
70 exc_store_dir = app.CONFIG.get('exception_tracker.store_path', '') or tempfile.gettempdir()
69 exc_store_dir = (
70 app.CONFIG.get("exception_tracker.store_path", "") or tempfile.gettempdir()
71 )
71 72 _exc_store_path = os.path.join(exc_store_dir, exc_store_dir_name)
72 73
73 74 _exc_store_path = os.path.abspath(_exc_store_path)
74 75 if not os.path.isdir(_exc_store_path):
75 76 os.makedirs(_exc_store_path)
76 log.debug('Initializing exceptions store at %s', _exc_store_path)
77 log.debug("Initializing exceptions store at %s", _exc_store_path)
77 78 _exc_store = _exc_store_path
78 79
79 80 return _exc_store_path
80 81
81 82
82 83 def get_detailed_tb(exc_info):
83 from io import StringIO
84
85 84 try:
86 from pip._vendor.rich import traceback as rich_tb, scope as rich_scope, console as rich_console
85 from pip._vendor.rich import (
86 traceback as rich_tb,
87 scope as rich_scope,
88 console as rich_console,
89 )
87 90 except ImportError:
88 91 try:
89 from rich import traceback as rich_tb, scope as rich_scope, console as rich_console
92 from rich import (
93 traceback as rich_tb,
94 scope as rich_scope,
95 console as rich_console,
96 )
90 97 except ImportError:
91 98 return None
92 99
93 console = rich_console.Console(
94 width=160,
95 file=StringIO()
96 )
100 console = rich_console.Console(width=160, file=io.StringIO())
97 101
98 102 exc = rich_tb.Traceback.extract(*exc_info, show_locals=True)
99 103
100 104 tb_rich = rich_tb.Traceback(
101 105 trace=exc,
102 106 width=160,
103 107 extra_lines=3,
104 108 theme=None,
105 109 word_wrap=False,
106 110 show_locals=False,
107 max_frames=100
111 max_frames=100,
108 112 )
109 113
110 formatted_locals = ""
111
112 114 # last_stack = exc.stacks[-1]
113 115 # last_frame = last_stack.frames[-1]
114 116 # if last_frame and last_frame.locals:
115 117 # console.print(
116 118 # rich_scope.render_scope(
117 119 # last_frame.locals,
118 120 # title=f'{last_frame.filename}:{last_frame.lineno}'))
119 121
120 122 console.print(tb_rich)
121 123 formatted_locals = console.file.getvalue()
122 124
123 125 return formatted_locals
124 126
125 127
126 128 def get_request_metadata(request=None) -> dict:
127 129 request_metadata = {}
128 130 if not request:
129 131 from pyramid.threadlocal import get_current_request
132
130 133 request = get_current_request()
131 134
132 135 # NOTE(marcink): store request information into exc_data
133 136 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', '')
137 request_metadata["client_address"] = getattr(request, "client_addr", "")
138 request_metadata["user_agent"] = getattr(request, "user_agent", "")
139 request_metadata["method"] = getattr(request, "method", "")
140 request_metadata["url"] = getattr(request, "url", "")
138 141 return request_metadata
139 142
140 143
141 144 def format_exc(exc_info):
142 145 exc_type, exc_value, exc_traceback = exc_info
143 146 tb = "++ TRACEBACK ++\n\n"
144 147 tb += "".join(traceback.format_exception(exc_type, exc_value, exc_traceback, None))
145 148
146 149 detailed_tb = getattr(exc_value, "_org_exc_tb", None)
147 150
148 151 if detailed_tb:
149 152 remote_tb = detailed_tb
150 153 if isinstance(detailed_tb, str):
151 154 remote_tb = [detailed_tb]
152 155
153 156 tb += (
154 157 "\n+++ BEG SOURCE EXCEPTION +++\n\n"
155 158 "{}\n"
156 159 "+++ END SOURCE EXCEPTION +++\n"
157 160 "".format("\n".join(remote_tb))
158 161 )
159 162
160 163 # Avoid that remote_tb also appears in the frame
161 164 del remote_tb
162 165
163 166 locals_tb = get_detailed_tb(exc_info)
164 167 if locals_tb:
165 168 tb += f"\n+++ DETAILS +++\n\n{locals_tb}\n" ""
166 169 return tb
167 170
168 171
169 172 def _store_exception(exc_id, exc_info, prefix, request_path=''):
170 173 """
171 174 Low level function to store exception in the exception tracker
172 175 """
173 176
174 177 extra_data = {}
175 178 extra_data.update(get_request_metadata())
176 179
177 180 exc_type, exc_value, exc_traceback = exc_info
178 181 tb = format_exc(exc_info)
179 182
180 183 exc_type_name = exc_type.__name__
181 184 exc_data, org_data = exc_serialize(exc_id, tb, exc_type_name, extra_data=extra_data)
182 185
183 exc_pref_id = '{}_{}_{}'.format(exc_id, prefix, org_data['exc_timestamp'])
186 exc_pref_id = f"{exc_id}_{prefix}_{org_data['exc_timestamp']}"
184 187 exc_store_path = get_exc_store()
185 188 if not os.path.isdir(exc_store_path):
186 189 os.makedirs(exc_store_path)
187 190 stored_exc_path = os.path.join(exc_store_path, exc_pref_id)
188 with open(stored_exc_path, 'wb') as f:
191 with open(stored_exc_path, "wb") as f:
189 192 f.write(exc_data)
190 log.debug('Stored generated exception %s as: %s', exc_id, stored_exc_path)
193 log.debug("Stored generated exception %s as: %s", exc_id, stored_exc_path)
191 194
195 if request_path:
192 196 log.error(
193 197 'error occurred handling this request.\n'
194 198 'Path: `%s`, %s',
195 199 request_path, tb)
196 200
197 201
198 202 def store_exception(exc_id, exc_info, prefix=global_prefix, request_path=''):
199 203 """
200 204 Example usage::
201 205
202 206 exc_info = sys.exc_info()
203 207 store_exception(id(exc_info), exc_info)
204 208 """
205 209
206 210 try:
207 211 exc_type = exc_info[0]
208 212 exc_type_name = exc_type.__name__
209 213
210 _store_exception(exc_id=exc_id, exc_info=exc_info, prefix=prefix,
211 request_path=request_path)
214 _store_exception(
215 exc_id=exc_id, exc_info=exc_info, prefix=prefix, request_path=request_path,
216 )
212 217 return exc_id, exc_type_name
213 218 except Exception:
214 log.exception('Failed to store exception `%s` information', exc_id)
219 log.exception("Failed to store exception `%s` information", exc_id)
215 220 # there's no way this can fail, it will crash server badly if it does.
216 221 pass
217 222
218 223
219 224 def _find_exc_file(exc_id, prefix=global_prefix):
220 225 exc_store_path = get_exc_store()
221 226 if prefix:
222 exc_id = f'{exc_id}_{prefix}'
227 exc_id = f"{exc_id}_{prefix}"
223 228 else:
224 229 # search without a prefix
225 exc_id = f'{exc_id}'
230 exc_id = f"{exc_id}"
226 231
227 232 found_exc_id = None
228 matches = glob.glob(os.path.join(exc_store_path, exc_id) + '*')
233 matches = glob.glob(os.path.join(exc_store_path, exc_id) + "*")
229 234 if matches:
230 235 found_exc_id = matches[0]
231 236
232 237 return found_exc_id
233 238
234 239
235 240 def _read_exception(exc_id, prefix):
236 241 exc_id_file_path = _find_exc_file(exc_id=exc_id, prefix=prefix)
237 242 if exc_id_file_path:
238 with open(exc_id_file_path, 'rb') as f:
243 with open(exc_id_file_path, "rb") as f:
239 244 return exc_unserialize(f.read())
240 245 else:
241 log.debug('Exception File `%s` not found', exc_id_file_path)
246 log.debug("Exception File `%s` not found", exc_id_file_path)
242 247 return None
243 248
244 249
245 250 def read_exception(exc_id, prefix=global_prefix):
246 251 try:
247 252 return _read_exception(exc_id=exc_id, prefix=prefix)
248 253 except Exception:
249 log.exception('Failed to read exception `%s` information', exc_id)
254 log.exception("Failed to read exception `%s` information", exc_id)
250 255 # there's no way this can fail, it will crash server badly if it does.
251 256 return None
252 257
253 258
254 259 def delete_exception(exc_id, prefix=global_prefix):
255 260 try:
256 261 exc_id_file_path = _find_exc_file(exc_id, prefix=prefix)
257 262 if exc_id_file_path:
258 263 os.remove(exc_id_file_path)
259 264
260 265 except Exception:
261 log.exception('Failed to remove exception `%s` information', exc_id)
266 log.exception("Failed to remove exception `%s` information", exc_id)
262 267 # there's no way this can fail, it will crash server badly if it does.
263 268 pass
264 269
265 270
266 271 def generate_id():
267 272 return id(object())
General Comments 0
You need to be logged in to leave comments. Login now