##// END OF EJS Templates
exception_tracker: store request info if available.
marcink -
r4301:9712e26b default
parent child Browse files
Show More
@@ -1,222 +1,231 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
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 Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import os
22 22 import time
23 23 import datetime
24 24 import msgpack
25 25 import logging
26 26 import traceback
27 27 import tempfile
28 28 import glob
29 29
30 30 log = logging.getLogger(__name__)
31 31
32 32 # NOTE: Any changes should be synced with exc_tracking at vcsserver.lib.exc_tracking
33 33 global_prefix = 'rhodecode'
34 34 exc_store_dir_name = 'rc_exception_store_v1'
35 35
36 36
37 def exc_serialize(exc_id, tb, exc_type):
37 def exc_serialize(exc_id, tb, exc_type, extra_data=None):
38 38
39 39 data = {
40 40 'version': 'v1',
41 41 'exc_id': exc_id,
42 42 'exc_utc_date': datetime.datetime.utcnow().isoformat(),
43 43 'exc_timestamp': repr(time.time()),
44 44 'exc_message': tb,
45 45 'exc_type': exc_type,
46 46 }
47 if extra_data:
48 data.update(extra_data)
47 49 return msgpack.packb(data), data
48 50
49 51
50 52 def exc_unserialize(tb):
51 53 return msgpack.unpackb(tb)
52 54
53 55 _exc_store = None
54 56
55 57
56 58 def get_exc_store():
57 59 """
58 60 Get and create exception store if it's not existing
59 61 """
60 62 global _exc_store
61 63 import rhodecode as app
62 64
63 65 if _exc_store is not None:
64 66 # quick global cache
65 67 return _exc_store
66 68
67 69 exc_store_dir = app.CONFIG.get('exception_tracker.store_path', '') or tempfile.gettempdir()
68 70 _exc_store_path = os.path.join(exc_store_dir, exc_store_dir_name)
69 71
70 72 _exc_store_path = os.path.abspath(_exc_store_path)
71 73 if not os.path.isdir(_exc_store_path):
72 74 os.makedirs(_exc_store_path)
73 75 log.debug('Initializing exceptions store at %s', _exc_store_path)
74 76 _exc_store = _exc_store_path
75 77
76 78 return _exc_store_path
77 79
78 80
79 81 def _store_exception(exc_id, exc_type_name, exc_traceback, prefix, send_email=None):
80 82 """
81 83 Low level function to store exception in the exception tracker
82 84 """
85 from pyramid.threadlocal import get_current_request
83 86 import rhodecode as app
87 request = get_current_request()
88 extra_data = {}
89 # NOTE(marcink): store request information into exc_data
90 if request:
91 extra_data['client_address'] = getattr(request, 'client_addr', '')
92 extra_data['user_agent'] = getattr(request, 'user_agent', '')
93 extra_data['method'] = getattr(request, 'method', '')
94 extra_data['url'] = getattr(request, 'url', '')
84 95
85 96 exc_store_path = get_exc_store()
86 exc_data, org_data = exc_serialize(exc_id, exc_traceback, exc_type_name)
97 exc_data, org_data = exc_serialize(exc_id, exc_traceback, exc_type_name, extra_data=extra_data)
98
87 99 exc_pref_id = '{}_{}_{}'.format(exc_id, prefix, org_data['exc_timestamp'])
88 100 if not os.path.isdir(exc_store_path):
89 101 os.makedirs(exc_store_path)
90 102 stored_exc_path = os.path.join(exc_store_path, exc_pref_id)
91 103 with open(stored_exc_path, 'wb') as f:
92 104 f.write(exc_data)
93 105 log.debug('Stored generated exception %s as: %s', exc_id, stored_exc_path)
94 106
95 107 if send_email is None:
96 108 # NOTE(marcink): read app config unless we specify explicitly
97 109 send_email = app.CONFIG.get('exception_tracker.send_email', False)
98 110
99 111 mail_server = app.CONFIG.get('smtp_server') or None
100 112 send_email = send_email and mail_server
101 113 if send_email:
102 114 try:
103 send_exc_email(exc_id, exc_type_name)
115 send_exc_email(request, exc_id, exc_type_name)
104 116 except Exception:
105 117 log.exception('Failed to send exception email')
106 118 pass
107 119
108 120
109 def send_exc_email(exc_id, exc_type_name):
121 def send_exc_email(request, exc_id, exc_type_name):
110 122 import rhodecode as app
111 from pyramid.threadlocal import get_current_request
112 123 from rhodecode.apps._base import TemplateArgs
113 124 from rhodecode.lib.utils2 import aslist
114 125 from rhodecode.lib.celerylib import run_task, tasks
115 126 from rhodecode.lib.base import attach_context_attributes
116 127 from rhodecode.model.notification import EmailNotificationModel
117 128
118 request = get_current_request()
119
120 129 recipients = aslist(app.CONFIG.get('exception_tracker.send_email_recipients', ''))
121 130 log.debug('Sending Email exception to: `%s`', recipients or 'all super admins')
122 131
123 132 # NOTE(marcink): needed for email template rendering
124 133 user_id = None
125 134 if request:
126 135 user_id = request.user.user_id
127 136 attach_context_attributes(TemplateArgs(), request, user_id=user_id, is_api=True)
128 137
129 138 email_kwargs = {
130 139 'email_prefix': app.CONFIG.get('exception_tracker.email_prefix', '') or '[RHODECODE ERROR]',
131 140 'exc_url': request.route_url('admin_settings_exception_tracker_show', exception_id=exc_id),
132 141 'exc_id': exc_id,
133 142 'exc_type_name': exc_type_name,
134 143 'exc_traceback': read_exception(exc_id, prefix=None),
135 144 }
136 145
137 146 (subject, headers, email_body,
138 147 email_body_plaintext) = EmailNotificationModel().render_email(
139 148 EmailNotificationModel.TYPE_EMAIL_EXCEPTION, **email_kwargs)
140 149
141 150 run_task(tasks.send_email, recipients, subject,
142 151 email_body_plaintext, email_body)
143 152
144 153
145 154 def _prepare_exception(exc_info):
146 155 exc_type, exc_value, exc_traceback = exc_info
147 156 exc_type_name = exc_type.__name__
148 157
149 158 tb = ''.join(traceback.format_exception(
150 159 exc_type, exc_value, exc_traceback, None))
151 160
152 161 return exc_type_name, tb
153 162
154 163
155 164 def store_exception(exc_id, exc_info, prefix=global_prefix):
156 165 """
157 166 Example usage::
158 167
159 168 exc_info = sys.exc_info()
160 169 store_exception(id(exc_info), exc_info)
161 170 """
162 171
163 172 try:
164 173 exc_type_name, exc_traceback = _prepare_exception(exc_info)
165 174 _store_exception(exc_id=exc_id, exc_type_name=exc_type_name,
166 175 exc_traceback=exc_traceback, prefix=prefix)
167 176 return exc_id, exc_type_name
168 177 except Exception:
169 178 log.exception('Failed to store exception `%s` information', exc_id)
170 179 # there's no way this can fail, it will crash server badly if it does.
171 180 pass
172 181
173 182
174 183 def _find_exc_file(exc_id, prefix=global_prefix):
175 184 exc_store_path = get_exc_store()
176 185 if prefix:
177 186 exc_id = '{}_{}'.format(exc_id, prefix)
178 187 else:
179 188 # search without a prefix
180 189 exc_id = '{}'.format(exc_id)
181 190
182 191 found_exc_id = None
183 192 matches = glob.glob(os.path.join(exc_store_path, exc_id) + '*')
184 193 if matches:
185 194 found_exc_id = matches[0]
186 195
187 196 return found_exc_id
188 197
189 198
190 199 def _read_exception(exc_id, prefix):
191 200 exc_id_file_path = _find_exc_file(exc_id=exc_id, prefix=prefix)
192 201 if exc_id_file_path:
193 202 with open(exc_id_file_path, 'rb') as f:
194 203 return exc_unserialize(f.read())
195 204 else:
196 205 log.debug('Exception File `%s` not found', exc_id_file_path)
197 206 return None
198 207
199 208
200 209 def read_exception(exc_id, prefix=global_prefix):
201 210 try:
202 211 return _read_exception(exc_id=exc_id, prefix=prefix)
203 212 except Exception:
204 213 log.exception('Failed to read exception `%s` information', exc_id)
205 214 # there's no way this can fail, it will crash server badly if it does.
206 215 return None
207 216
208 217
209 218 def delete_exception(exc_id, prefix=global_prefix):
210 219 try:
211 220 exc_id_file_path = _find_exc_file(exc_id, prefix=prefix)
212 221 if exc_id_file_path:
213 222 os.remove(exc_id_file_path)
214 223
215 224 except Exception:
216 225 log.exception('Failed to remove exception `%s` information', exc_id)
217 226 # there's no way this can fail, it will crash server badly if it does.
218 227 pass
219 228
220 229
221 230 def generate_id():
222 231 return id(object())
@@ -1,38 +1,47 b''
1 1 <div class="panel panel-default">
2 2 <div class="panel-heading">
3 3 <h3 class="panel-title">${_('Exceptions Tracker - Exception ID')}: ${c.exception_id}</h3>
4 4 </div>
5 5 <div class="panel-body">
6 6 % if c.traceback:
7 7
8 8 <h4>${_('Exception `{}` generated on UTC date: {}').format(c.traceback.get('exc_type', 'NO_TYPE'), c.traceback.get('exc_utc_date', 'NO_DATE'))}</h4>
9 % if c.traceback.get('url'):
10 Request:
11 <code>${c.traceback.get('method')} ${c.traceback.get('url')}</code><br/>
12 <code>${c.traceback.get('client_address')} ${c.traceback.get('user_agent')}</code>
13 <br/>
14 <br/>
15 % endif
16
9 17 <pre>${c.traceback.get('exc_message', 'NO_MESSAGE')}</pre>
10 18
19
11 20 % else:
12 21 ${_('Unable to Read Exception. It might be removed or non-existing.')}
13 22 % endif
14 23 </div>
15 24 </div>
16 25
17 26
18 27 % if c.traceback:
19 28 <div class="panel panel-danger">
20 29 <div class="panel-heading" id="advanced-delete">
21 30 <h3 class="panel-title">${_('Delete this Exception')}</h3>
22 31 </div>
23 32 <div class="panel-body">
24 33 ${h.secure_form(h.route_path('admin_settings_exception_tracker_delete', exception_id=c.exception_id), request=request)}
25 34 <div style="margin: 0 0 20px 0" class="fake-space"></div>
26 35
27 36 <div class="field">
28 37 <button class="btn btn-small btn-danger" type="submit"
29 38 onclick="return confirm('${_('Confirm to delete this exception')}');">
30 39 <i class="icon-remove"></i>
31 40 ${_('Delete This Exception')}
32 41 </button>
33 42 </div>
34 43
35 44 ${h.end_form()}
36 45 </div>
37 46 </div>
38 47 % endif
General Comments 0
You need to be logged in to leave comments. Login now