##// END OF EJS Templates
exc_tracker: disable send-email if mail server is not configured.
marcink -
r4297:7f340174 default
parent child Browse files
Show More
@@ -1,222 +1,222 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 37 def exc_serialize(exc_id, tb, exc_type):
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 47 return msgpack.packb(data), data
48 48
49 49
50 50 def exc_unserialize(tb):
51 51 return msgpack.unpackb(tb)
52 52
53 53 _exc_store = None
54 54
55 55
56 56 def get_exc_store():
57 57 """
58 58 Get and create exception store if it's not existing
59 59 """
60 60 global _exc_store
61 61 import rhodecode as app
62 62
63 63 if _exc_store is not None:
64 64 # quick global cache
65 65 return _exc_store
66 66
67 67 exc_store_dir = app.CONFIG.get('exception_tracker.store_path', '') or tempfile.gettempdir()
68 68 _exc_store_path = os.path.join(exc_store_dir, exc_store_dir_name)
69 69
70 70 _exc_store_path = os.path.abspath(_exc_store_path)
71 71 if not os.path.isdir(_exc_store_path):
72 72 os.makedirs(_exc_store_path)
73 73 log.debug('Initializing exceptions store at %s', _exc_store_path)
74 74 _exc_store = _exc_store_path
75 75
76 76 return _exc_store_path
77 77
78 78
79 79 def _store_exception(exc_id, exc_type_name, exc_traceback, prefix, send_email=None):
80 80 """
81 81 Low level function to store exception in the exception tracker
82 82 """
83 83 import rhodecode as app
84 84
85 85 exc_store_path = get_exc_store()
86 86 exc_data, org_data = exc_serialize(exc_id, exc_traceback, exc_type_name)
87 87 exc_pref_id = '{}_{}_{}'.format(exc_id, prefix, org_data['exc_timestamp'])
88 88 if not os.path.isdir(exc_store_path):
89 89 os.makedirs(exc_store_path)
90 90 stored_exc_path = os.path.join(exc_store_path, exc_pref_id)
91 91 with open(stored_exc_path, 'wb') as f:
92 92 f.write(exc_data)
93 93 log.debug('Stored generated exception %s as: %s', exc_id, stored_exc_path)
94 94
95 95 if send_email is None:
96 96 # NOTE(marcink): read app config unless we specify explicitly
97 97 send_email = app.CONFIG.get('exception_tracker.send_email', False)
98
98 99 mail_server = app.CONFIG.get('smtp_server') or None
99 100 send_email = send_email and mail_server
100
101 101 if send_email:
102 102 try:
103 103 send_exc_email(exc_id, exc_type_name)
104 104 except Exception:
105 105 log.exception('Failed to send exception email')
106 106 pass
107 107
108 108
109 109 def send_exc_email(exc_id, exc_type_name):
110 110 import rhodecode as app
111 111 from pyramid.threadlocal import get_current_request
112 112 from rhodecode.apps._base import TemplateArgs
113 113 from rhodecode.lib.utils2 import aslist
114 114 from rhodecode.lib.celerylib import run_task, tasks
115 115 from rhodecode.lib.base import attach_context_attributes
116 116 from rhodecode.model.notification import EmailNotificationModel
117 117
118 118 request = get_current_request()
119 119
120 120 recipients = aslist(app.CONFIG.get('exception_tracker.send_email_recipients', ''))
121 121 log.debug('Sending Email exception to: `%s`', recipients or 'all super admins')
122 122
123 123 # NOTE(marcink): needed for email template rendering
124 124 user_id = None
125 125 if request:
126 126 user_id = request.user.user_id
127 127 attach_context_attributes(TemplateArgs(), request, user_id=user_id, is_api=True)
128 128
129 129 email_kwargs = {
130 130 'email_prefix': app.CONFIG.get('exception_tracker.email_prefix', '') or '[RHODECODE ERROR]',
131 131 'exc_url': request.route_url('admin_settings_exception_tracker_show', exception_id=exc_id),
132 132 'exc_id': exc_id,
133 133 'exc_type_name': exc_type_name,
134 134 'exc_traceback': read_exception(exc_id, prefix=None),
135 135 }
136 136
137 137 (subject, headers, email_body,
138 138 email_body_plaintext) = EmailNotificationModel().render_email(
139 139 EmailNotificationModel.TYPE_EMAIL_EXCEPTION, **email_kwargs)
140 140
141 141 run_task(tasks.send_email, recipients, subject,
142 142 email_body_plaintext, email_body)
143 143
144 144
145 145 def _prepare_exception(exc_info):
146 146 exc_type, exc_value, exc_traceback = exc_info
147 147 exc_type_name = exc_type.__name__
148 148
149 149 tb = ''.join(traceback.format_exception(
150 150 exc_type, exc_value, exc_traceback, None))
151 151
152 152 return exc_type_name, tb
153 153
154 154
155 155 def store_exception(exc_id, exc_info, prefix=global_prefix):
156 156 """
157 157 Example usage::
158 158
159 159 exc_info = sys.exc_info()
160 160 store_exception(id(exc_info), exc_info)
161 161 """
162 162
163 163 try:
164 164 exc_type_name, exc_traceback = _prepare_exception(exc_info)
165 165 _store_exception(exc_id=exc_id, exc_type_name=exc_type_name,
166 166 exc_traceback=exc_traceback, prefix=prefix)
167 167 return exc_id, exc_type_name
168 168 except Exception:
169 169 log.exception('Failed to store exception `%s` information', exc_id)
170 170 # there's no way this can fail, it will crash server badly if it does.
171 171 pass
172 172
173 173
174 174 def _find_exc_file(exc_id, prefix=global_prefix):
175 175 exc_store_path = get_exc_store()
176 176 if prefix:
177 177 exc_id = '{}_{}'.format(exc_id, prefix)
178 178 else:
179 179 # search without a prefix
180 180 exc_id = '{}'.format(exc_id)
181 181
182 182 found_exc_id = None
183 183 matches = glob.glob(os.path.join(exc_store_path, exc_id) + '*')
184 184 if matches:
185 185 found_exc_id = matches[0]
186 186
187 187 return found_exc_id
188 188
189 189
190 190 def _read_exception(exc_id, prefix):
191 191 exc_id_file_path = _find_exc_file(exc_id=exc_id, prefix=prefix)
192 192 if exc_id_file_path:
193 193 with open(exc_id_file_path, 'rb') as f:
194 194 return exc_unserialize(f.read())
195 195 else:
196 196 log.debug('Exception File `%s` not found', exc_id_file_path)
197 197 return None
198 198
199 199
200 200 def read_exception(exc_id, prefix=global_prefix):
201 201 try:
202 202 return _read_exception(exc_id=exc_id, prefix=prefix)
203 203 except Exception:
204 204 log.exception('Failed to read exception `%s` information', exc_id)
205 205 # there's no way this can fail, it will crash server badly if it does.
206 206 return None
207 207
208 208
209 209 def delete_exception(exc_id, prefix=global_prefix):
210 210 try:
211 211 exc_id_file_path = _find_exc_file(exc_id, prefix=prefix)
212 212 if exc_id_file_path:
213 213 os.remove(exc_id_file_path)
214 214
215 215 except Exception:
216 216 log.exception('Failed to remove exception `%s` information', exc_id)
217 217 # there's no way this can fail, it will crash server badly if it does.
218 218 pass
219 219
220 220
221 221 def generate_id():
222 222 return id(object())
General Comments 0
You need to be logged in to leave comments. Login now