Projet

Général

Profil

0001-misc-mark-logged-error-emails-with-a-tech_id-referen.patch

Frédéric Péters, 29 octobre 2019 09:57

Télécharger (9,79 ko)

Voir les différences:

Subject: [PATCH] misc: mark logged error emails with a tech_id reference
 (#36807)

 tests/test_backoffice_pages.py | 51 ++++++++++++++++++++++++++++++++++
 wcs/logged_errors.py           |  3 +-
 wcs/publisher.py               | 35 ++++++++++++++++++++---
 wcs/qommon/emails.py           | 10 +++++--
 wcs/qommon/logger.py           |  8 ++++--
 wcs/qommon/publisher.py        | 26 -----------------
 6 files changed, 97 insertions(+), 36 deletions(-)
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
-