0001-misc-mark-logged-error-emails-with-a-tech_id-referen.patch
tests/test_backoffice_pages.py | ||
---|---|---|
2679 | 2679 |
assert (' with the number %s.' % number31.get_display_id()) in resp.body |
2680 | 2680 |
assert not 'Error during webservice call' in resp.body |
2681 | 2681 | |
2682 |
def test_backoffice_wscall_error_email(http_requests, pub, emails): |
|
2683 |
pub.cfg['debug'] = {'error_email': 'errors@localhost.invalid'} |
|
2684 |
pub.write_cfg() |
|
2685 | ||
2686 |
user = create_user(pub) |
|
2687 |
create_environment(pub) |
|
2688 |
formdef = FormDef.get_by_urlname('form-title') |
|
2689 |
form_class = formdef.data_class() |
|
2690 | ||
2691 |
number31 = [x for x in form_class.select() if x.data['1'] == 'FOO BAR 30'][0] |
|
2692 | ||
2693 |
# attach a custom workflow |
|
2694 |
workflow = Workflow(name='wscall') |
|
2695 |
st1 = workflow.add_status('Status1', number31.status.split('-')[1]) |
|
2696 | ||
2697 |
wscall = WebserviceCallStatusItem() |
|
2698 |
wscall.id = '_wscall' |
|
2699 |
wscall.varname = 'xxx' |
|
2700 |
wscall.url = 'http://remote.example.net/xml' |
|
2701 |
wscall.action_on_bad_data = ':stop' |
|
2702 |
wscall.record_errors = True |
|
2703 |
st1.items.append(wscall) |
|
2704 |
wscall.parent = st1 |
|
2705 | ||
2706 |
again = ChoiceWorkflowStatusItem() |
|
2707 |
again.id = '_again' |
|
2708 |
again.label = 'Again' |
|
2709 |
again.by = ['_receiver'] |
|
2710 |
again.status = st1.id |
|
2711 |
st1.items.append(again) |
|
2712 |
again.parent = st1 |
|
2713 | ||
2714 |
workflow.store() |
|
2715 | ||
2716 |
formdef.workflow_id = workflow.id |
|
2717 |
formdef.store() |
|
2718 | ||
2719 |
app = login(get_app(pub)) |
|
2720 | ||
2721 |
resp = app.get('/backoffice/management/form-title/%s/' % number31.id) |
|
2722 |
assert (' with the number %s.' % number31.get_display_id()) in resp.body |
|
2723 |
assert 'Again' in resp.body |
|
2724 |
resp = resp.forms[0].submit('button_again') |
|
2725 |
resp = resp.follow() |
|
2726 |
assert 'Error during webservice call' in resp.body |
|
2727 | ||
2728 |
# check email box |
|
2729 |
error_email = emails.get('[ERROR] [WSCALL] ValueError: No JSON object could be decoded') |
|
2730 |
assert '/form-title/%s/' % number31.id in error_email['payload'] |
|
2731 |
assert error_email['msg']['References'] |
|
2732 | ||
2682 | 2733 |
def test_backoffice_wscall_attachment(http_requests, pub): |
2683 | 2734 |
create_user(pub) |
2684 | 2735 |
create_environment(pub) |
wcs/logged_errors.py | ||
---|---|---|
103 | 103 |
error.occurences_count += 1 |
104 | 104 |
error.latest_occurence_timestamp = datetime.datetime.now() |
105 | 105 |
error.store() |
106 |
return error |
|
106 | 107 | |
107 | 108 |
@classmethod |
108 | 109 |
def record_exception(cls, error_summary, plain_error_msg, publisher): |
... | ... | |
118 | 119 |
return |
119 | 120 |
formdef = FormDef.get_by_urlname(formdef_urlname) |
120 | 121 |
formdata = formdef.data_class().get(formdata_id, ignore_errors=True) |
121 |
cls.record(error_summary, plain_error_msg, formdata=formdata, |
|
122 |
return cls.record(error_summary, plain_error_msg, formdata=formdata,
|
|
122 | 123 |
formdef=formdef, workflow=formdef.workflow) |
123 | 124 | |
124 | 125 |
@property |
wcs/publisher.py | ||
---|---|---|
18 | 18 |
import os |
19 | 19 |
import random |
20 | 20 |
import sys |
21 |
import traceback |
|
21 | 22 |
import zipfile |
22 | 23 | |
23 | 24 |
from django.utils.six.moves import cPickle |
... | ... | |
29 | 30 |
except ImportError: |
30 | 31 |
pass |
31 | 32 | |
32 |
from .qommon.publisher import set_publisher_class, QommonPublisher |
|
33 |
from .qommon.publisher import set_publisher_class, QommonPublisher, get_request
|
|
33 | 34 | |
34 | 35 |
# this is terribly ugly but import RootDirectory will import a bunch of things, |
35 | 36 |
# and some of them need a publisher to be set |
... | ... | |
316 | 317 |
conn.commit() |
317 | 318 |
cur.close() |
318 | 319 | |
320 |
def notify_of_exception(self, exc_tuple, context=None): |
|
321 |
exc_type, exc_value, tb = exc_tuple |
|
322 |
error_summary = traceback.format_exception_only(exc_type, exc_value) |
|
323 |
error_summary = error_summary[0][0:-1] # de-listify and strip newline |
|
324 |
if context: |
|
325 |
error_summary = '%s %s' % (context, error_summary) |
|
326 | ||
327 |
plain_error_msg = str(self._generate_plaintext_error( |
|
328 |
get_request(), |
|
329 |
self, |
|
330 |
exc_type, exc_value, |
|
331 |
tb)) |
|
332 | ||
333 |
self.log_internal_error(error_summary, plain_error_msg, record=True) |
|
334 | ||
319 | 335 |
def log_internal_error(self, error_summary, plain_error_msg, record=False): |
320 |
super(WcsPublisher, self).log_internal_error(error_summary, |
|
321 |
plain_error_msg, record=record) |
|
336 |
tech_id = None |
|
322 | 337 |
if record: |
323 |
LoggedError.record_exception(error_summary, plain_error_msg, publisher=self) |
|
338 |
logged_exception = LoggedError.record_exception( |
|
339 |
error_summary, plain_error_msg, publisher=self) |
|
340 |
if logged_exception: |
|
341 |
tech_id = logged_exception.tech_id |
|
342 |
try: |
|
343 |
self.logger.log_internal_error(error_summary, plain_error_msg, tech_id) |
|
344 |
except socket.error: |
|
345 |
# will happen if there is no mail server available and exceptions |
|
346 |
# were configured to be mailed. |
|
347 |
pass |
|
348 |
except OSError: |
|
349 |
# this could happen on file descriptor exhaustion |
|
350 |
pass |
|
324 | 351 | |
325 | 352 |
def apply_global_action_timeouts(self): |
326 | 353 |
from wcs.workflows import Workflow, WorkflowGlobalActionTimeoutTrigger |
wcs/qommon/emails.py | ||
---|---|---|
131 | 131 |
return part |
132 | 132 |
get_logger().warn('Failed to build MIME part from %r', attachment) |
133 | 133 | |
134 |
def email(subject, mail_body, email_rcpt, replyto = None, bcc = None,
|
|
134 |
def email(subject, mail_body, email_rcpt, replyto=None, bcc=None,
|
|
135 | 135 |
email_from=None, exclude_current_user=False, email_type=None, |
136 |
want_html=True, hide_recipients = False, fire_and_forget = False, |
|
137 |
smtp_timeout = None, attachments = ()): |
|
136 |
want_html=True, hide_recipients=False, fire_and_forget=False, |
|
137 |
smtp_timeout=None, attachments=(), |
|
138 |
extra_headers=None): |
|
138 | 139 | |
139 | 140 |
if not get_request(): |
140 | 141 |
# we are not processing a request, no sense delaying the handling |
... | ... | |
274 | 275 |
msg['Reply-To'] = replyto |
275 | 276 | |
276 | 277 |
msg['X-Qommon-Id'] = os.path.basename(get_publisher().app_dir) |
278 |
if extra_headers: |
|
279 |
for key, value in extra_headers.items(): |
|
280 |
msg[key] = value |
|
277 | 281 | |
278 | 282 |
if type(email_rcpt) is list: |
279 | 283 |
rcpts = email_rcpt[:] |
wcs/qommon/logger.py | ||
---|---|---|
22 | 22 | |
23 | 23 | |
24 | 24 |
class ApplicationLogger(DefaultLogger): |
25 |
def log_internal_error(self, error_summary, error_msg): |
|
25 |
def log_internal_error(self, error_summary, error_msg, tech_id=None):
|
|
26 | 26 |
self.log('exception caught') |
27 | 27 |
self.error_log.write(error_msg) |
28 | 28 |
if self.error_email: |
29 | 29 |
from .emails import email |
30 |
headers = {} |
|
31 |
if tech_id: |
|
32 |
headers['References'] = '<%s@%s>' % (tech_id, os.path.basename(get_publisher().app_dir)) |
|
30 | 33 |
email(subject='[ERROR] %s' % error_summary, |
31 | 34 |
mail_body=error_msg, |
32 | 35 |
email_from=self.error_email, |
33 | 36 |
email_rcpt=[self.error_email], |
34 | 37 |
want_html=False, |
35 |
fire_and_forget=True) |
|
38 |
fire_and_forget=True, |
|
39 |
extra_headers=headers) |
|
36 | 40 | |
37 | 41 | |
38 | 42 |
class BotFilter(logging.Filter): |
wcs/qommon/publisher.py | ||
---|---|---|
245 | 245 | |
246 | 246 |
return error_file.getvalue() |
247 | 247 | |
248 |
def notify_of_exception(self, exc_tuple, context=None): |
|
249 |
exc_type, exc_value, tb = exc_tuple |
|
250 |
error_summary = traceback.format_exception_only(exc_type, exc_value) |
|
251 |
error_summary = error_summary[0][0:-1] # de-listify and strip newline |
|
252 |
if context: |
|
253 |
error_summary = '%s %s' % (context, error_summary) |
|
254 | ||
255 |
plain_error_msg = str(self._generate_plaintext_error( |
|
256 |
get_request(), |
|
257 |
self, |
|
258 |
exc_type, exc_value, |
|
259 |
tb)) |
|
260 | ||
261 |
self.log_internal_error(error_summary, plain_error_msg, record=True) |
|
262 | ||
263 |
def log_internal_error(self, error_summary, plain_error_msg, record=False): |
|
264 |
try: |
|
265 |
self.logger.log_internal_error(error_summary, plain_error_msg) |
|
266 |
except socket.error: |
|
267 |
# will happen if there is no mail server available and exceptions |
|
268 |
# were configured to be mailed. |
|
269 |
pass |
|
270 |
except OSError: |
|
271 |
# this could happen on file descriptor exhaustion |
|
272 |
pass |
|
273 | ||
274 | 248 |
def finish_successful_request(self): |
275 | 249 |
if not self.get_request().ignore_session: |
276 | 250 |
self.session_manager.finish_successful_request() |
277 |
- |