Projet

Général

Profil

0001-misc-use-only-record_error-to-record-and-notify-5541.patch

Lauréline Guérin, 09 juillet 2021 14:18

Télécharger (44,7 ko)

Voir les différences:

Subject: [PATCH] misc: use only record_error to record and notify (#55414)

 tests/form_pages/test_all.py      |  7 +---
 tests/test_datasource.py          | 33 +++++++----------
 tests/test_hobo_notify.py         | 15 ++++----
 tests/test_workflows.py           |  7 +++-
 tests/test_wscall.py              |  2 +-
 wcs/api.py                        |  9 +----
 wcs/ctl/hobo_notify.py            |  8 ++--
 wcs/data_sources.py               | 35 ++++--------------
 wcs/logged_errors.py              | 44 +++++++++++++---------
 wcs/publisher.py                  | 61 ++++++++++++++++++++-----------
 wcs/qommon/afterjobs.py           |  4 +-
 wcs/qommon/cron.py                |  6 +--
 wcs/qommon/ident/franceconnect.py |  5 +--
 wcs/qommon/saml2.py               | 13 +++----
 wcs/qommon/storage.py             |  4 +-
 wcs/wf/profile.py                 |  9 ++---
 wcs/wf/register_comment.py        |  6 +--
 wcs/wf/roles.py                   |  9 ++---
 wcs/wf/wscall.py                  | 52 +++++++++++++-------------
 wcs/workflows.py                  | 25 ++++++-------
 wcs/wscalls.py                    | 25 +++++--------
 21 files changed, 180 insertions(+), 199 deletions(-)
tests/form_pages/test_all.py
5908 5908
        data_source.store()
5909 5909
        resp2 = app.get(select2_url + '?q=hell')
5910 5910
        assert emails.count() == 1
5911
        assert (
5912
            emails.get_latest('subject')
5913
            == '[ERROR] [DATASOURCE] Exception: Error loading JSON data source (...)'
5914
        )
5911
        assert emails.get_latest('subject') == '[ERROR] [DATASOURCE] Error loading JSON data source (...)'
5915 5912
        if pub.is_using_postgresql():
5916 5913
            assert pub.loggederror_class.count() == 1
5917 5914
            logged_error = pub.loggederror_class.select()[0]
5918 5915
            assert logged_error.workflow_id is None
5919
            assert logged_error.summary == '[DATASOURCE] Exception: Error loading JSON data source (...)'
5916
            assert logged_error.summary == '[DATASOURCE] Error loading JSON data source (...)'
5920 5917

  
5921 5918
        data_source.notify_on_errors = False
5922 5919
        data_source.store()
tests/test_datasource.py
148 148
        assert pub.loggederror_class.count() == 1
149 149
        logged_error = pub.loggederror_class.select()[0]
150 150
        assert logged_error.workflow_id is None
151
        assert (
152
            logged_error.summary == "[DATASOURCE] Exception: Failed to eval() Python data source ('foobar')"
153
        )
151
        assert logged_error.summary == "[DATASOURCE] Failed to eval() Python data source ('foobar')"
154 152

  
155 153
    # expression not iterable
156 154
    datasource = {'type': 'formula', 'value': '2', 'notify_on_errors': True, 'record_on_errors': True}
......
160 158
        assert pub.loggederror_class.count() == 2
161 159
        logged_error = pub.loggederror_class.select()[1]
162 160
        assert logged_error.workflow_id is None
163
        assert (
164
            logged_error.summary
165
            == "[DATASOURCE] Exception: Python data source ('2') gave a non-iterable result"
166
        )
161
        assert logged_error.summary == "[DATASOURCE] Python data source ('2') gave a non-iterable result"
167 162

  
168 163

  
169 164
def test_python_datasource_with_evalutils(pub):
......
407 402
        assert logged_error.workflow_id is None
408 403
        assert (
409 404
            logged_error.summary
410
            == "[DATASOURCE] Exception: Error loading JSON data source (error in HTTP request to http://remote.example.net/404 (status: 404))"
405
            == "[DATASOURCE] Error loading JSON data source (error in HTTP request to http://remote.example.net/404 (status: 404))"
411 406
        )
412 407

  
413 408
    datasource = {
......
425 420
        assert logged_error.workflow_id is None
426 421
        assert (
427 422
            logged_error.summary
428
            == "[DATASOURCE] Exception: Error reading JSON data source output (Expecting value: line 1 column 1 (char 0))"
423
            == "[DATASOURCE] Error reading JSON data source output (Expecting value: line 1 column 1 (char 0))"
429 424
        )
430 425

  
431 426
    datasource = {
......
440 435
        assert pub.loggederror_class.count() == 3
441 436
        logged_error = pub.loggederror_class.select()[2]
442 437
        assert logged_error.workflow_id is None
443
        assert logged_error.summary == "[DATASOURCE] Exception: Error loading JSON data source (error)"
438
        assert logged_error.summary == "[DATASOURCE] Error loading JSON data source (error)"
444 439

  
445 440
    datasource = {
446 441
        'type': 'json',
......
454 449
        assert pub.loggederror_class.count() == 4
455 450
        logged_error = pub.loggederror_class.select()[3]
456 451
        assert logged_error.workflow_id is None
457
        assert logged_error.summary == "[DATASOURCE] Exception: Error reading JSON data source output (err 1)"
452
        assert logged_error.summary == "[DATASOURCE] Error reading JSON data source output (err 1)"
458 453

  
459 454

  
460 455
def test_json_datasource_bad_url_scheme(pub, error_email, emails):
......
477 472
        assert logged_error.workflow_id is None
478 473
        assert (
479 474
            logged_error.summary
480
            == "[DATASOURCE] Exception: Error loading JSON data source (invalid scheme in URL foo://bar)"
475
            == "[DATASOURCE] Error loading JSON data source (invalid scheme in URL foo://bar)"
481 476
        )
482 477

  
483 478
    datasource = {'type': 'json', 'value': '/bla/blo', 'notify_on_errors': True, 'record_on_errors': True}
......
490 485
        assert logged_error.workflow_id is None
491 486
        assert (
492 487
            logged_error.summary
493
            == "[DATASOURCE] Exception: Error loading JSON data source (invalid scheme in URL /bla/blo)"
488
            == "[DATASOURCE] Error loading JSON data source (invalid scheme in URL /bla/blo)"
494 489
        )
495 490

  
496 491

  
......
829 824
        assert logged_error.workflow_id is None
830 825
        assert (
831 826
            logged_error.summary
832
            == "[DATASOURCE] Exception: Error loading JSON data source (error in HTTP request to http://remote.example.net/404 (status: 404))"
827
            == "[DATASOURCE] Error loading JSON data source (error in HTTP request to http://remote.example.net/404 (status: 404))"
833 828
        )
834 829

  
835 830
    datasource = {
......
847 842
        assert logged_error.workflow_id is None
848 843
        assert (
849 844
            logged_error.summary
850
            == "[DATASOURCE] Exception: Error reading JSON data source output (Expecting value: line 1 column 1 (char 0))"
845
            == "[DATASOURCE] Error reading JSON data source output (Expecting value: line 1 column 1 (char 0))"
851 846
        )
852 847

  
853 848
    datasource = {
......
863 858
        assert pub.loggederror_class.count() == 3
864 859
        logged_error = pub.loggederror_class.select()[2]
865 860
        assert logged_error.workflow_id is None
866
        assert logged_error.summary == "[DATASOURCE] Exception: Error loading JSON data source (error)"
861
        assert logged_error.summary == "[DATASOURCE] Error loading JSON data source (error)"
867 862

  
868 863
    datasource = {
869 864
        'type': 'geojson',
......
877 872
        assert pub.loggederror_class.count() == 4
878 873
        logged_error = pub.loggederror_class.select()[3]
879 874
        assert logged_error.workflow_id is None
880
        assert logged_error.summary == "[DATASOURCE] Exception: Error reading JSON data source output (err 1)"
875
        assert logged_error.summary == "[DATASOURCE] Error reading JSON data source output (err 1)"
881 876

  
882 877

  
883 878
def test_geojson_datasource_bad_url_scheme(pub, error_email, emails):
......
898 893
        assert logged_error.workflow_id is None
899 894
        assert (
900 895
            logged_error.summary
901
            == "[DATASOURCE] Exception: Error loading JSON data source (invalid scheme in URL foo://bar)"
896
            == "[DATASOURCE] Error loading JSON data source (invalid scheme in URL foo://bar)"
902 897
        )
903 898

  
904 899
    datasource = {'type': 'geojson', 'value': '/bla/blo', 'notify_on_errors': True, 'record_on_errors': True}
......
911 906
        assert logged_error.workflow_id is None
912 907
        assert (
913 908
            logged_error.summary
914
            == "[DATASOURCE] Exception: Error loading JSON data source (invalid scheme in URL /bla/blo)"
909
            == "[DATASOURCE] Error loading JSON data source (invalid scheme in URL /bla/blo)"
915 910
        )
916 911

  
917 912

  
tests/test_hobo_notify.py
664 664
        assert not User.select()[0].email
665 665

  
666 666

  
667
def notify_of_exception(exc_info, context):
668
    raise Exception(exc_info)
667
def record_error(exception=None, *args, **kwargs):
668
    if exception:
669
        raise exception
669 670

  
670 671

  
671 672
def test_process_notification_user_with_errors(pub):
......
711 712

  
712 713
    notification['full'] = False
713 714

  
714
    pub.notify_of_exception = notify_of_exception
715
    pub.record_error = record_error
715 716

  
716 717
    for key in ('uuid', 'first_name', 'last_name', 'email'):
717 718
        backup = notification['objects']['data'][0][key]
718 719
        del notification['objects']['data'][0][key]
719 720
        with pytest.raises(Exception) as e:
720 721
            CmdHoboNotify.process_notification(notification)
721
        assert e.value.args[0][0] == ValueError
722
        assert e.value.args[0][1].args == ('invalid user',)
722
        assert e.type == ValueError
723
        assert e.value.args == ('invalid user',)
723 724
        assert User.count() == 0
724 725
        notification['objects']['data'][0][key] = backup
725 726

  
......
727 728
    del notification['objects']['data'][0]['uuid']
728 729
    with pytest.raises(Exception) as e:
729 730
        CmdHoboNotify.process_notification(notification)
730
    assert e.value.args[0][0] == KeyError
731
    assert e.value.args[0][1].args == ('user without uuid',)
731
    assert e.type == KeyError
732
    assert e.value.args == ('user without uuid',)
732 733

  
733 734

  
734 735
def test_process_notification_role_with_errors(pub):
tests/test_workflows.py
5990 5990
    if two_pubs.is_using_postgresql():
5991 5991
        errors = two_pubs.loggederror_class.select()
5992 5992
        assert len(errors) == 2
5993
        assert any('form_var_toto_string' in (error.exception_message or '') for error in errors)
5994
        assert any('Missing field' in error.summary for error in errors)
5993
        assert 'form_var_toto_string' in errors[0].exception_message
5994
        assert 'Missing field' in errors[1].summary
5995
        assert errors[0].formdata_id == str(target_formdef.data_class().select()[0].id)
5996
        assert errors[1].formdata_id == str(target_formdef.data_class().select()[0].id)
5995 5997

  
5996 5998
    # no tracking code has been created
5997 5999
    created_formdata = target_formdef.data_class().select()[0]
......
6374 6376
        assert two_pubs.loggederror_class.count() == 1
6375 6377
        logged_error = two_pubs.loggederror_class.select()[0]
6376 6378
        assert logged_error.summary == 'Failed to attach user (not found: "zzz")'
6379
        assert logged_error.formdata_id == str(carddef.data_class().select()[0].id)
6377 6380

  
6378 6381
    # user association on invalid template
6379 6382
    carddef.data_class().wipe()
tests/test_wscall.py
220 220
        assert 'Foo Bar ' in resp.text
221 221
        if notify_on_errors:
222 222
            assert emails.count() == 1
223
            assert "[ERROR] [WSCALL] Exception: %s whatever" % status_code in emails.emails
223
            assert "[ERROR] [WSCALL] %s whatever" % status_code in emails.emails
224 224
            emails.empty()
225 225
        else:
226 226
            assert emails.count() == 0
wcs/api.py
17 17
import datetime
18 18
import json
19 19
import re
20
import sys
21 20
import time
22 21
import urllib.parse
23 22

  
......
1119 1118
                if 'data_source' in info:
1120 1119
                    error_summary = 'Error loading JSON data source (%s)' % str(e)
1121 1120
                    data_source = NamedDataSource.get(info['data_source'])
1122
                    try:
1123
                        raise Exception(error_summary) from e
1124
                    except Exception:
1125
                        exc_info = sys.exc_info()
1126
                    get_publisher().notify_of_exception(
1127
                        exc_info,
1121
                    get_publisher().record_error(
1122
                        error_summary,
1128 1123
                        context='[DATASOURCE]',
1129 1124
                        notify=data_source.notify_on_errors,
1130 1125
                        record=data_source.record_on_errors,
wcs/ctl/hobo_notify.py
175 175
                        if field.convert_value_from_anything:
176 176
                            try:
177 177
                                field_value = field.convert_value_from_anything(field_value)
178
                            except ValueError:
179
                                publisher.notify_of_exception(sys.exc_info(), context='[PROVISIONNING]')
178
                            except ValueError as e:
179
                                publisher.record_error(exception=e, context='[PROVISIONNING]', notify=True)
180 180
                                continue
181 181
                        user.form_data[field.id] = field_value
182 182
                    user.name_identifiers = [uuid]
......
202 202
                    users = User.get_users_with_name_identifier(o['uuid'])
203 203
                    for user in users:
204 204
                        user.set_deleted()
205
            except Exception:
206
                publisher.notify_of_exception(sys.exc_info(), context='[PROVISIONNING]')
205
            except Exception as e:
206
                publisher.record_error(exception=e, context='[PROVISIONNING]', notify=True)
207 207

  
208 208

  
209 209
CmdHoboNotify.register()
wcs/data_sources.py
16 16

  
17 17
import collections
18 18
import hashlib
19
import sys
20 19
import urllib.parse
21 20
import xml.etree.ElementTree as ET
22 21

  
......
170 169
    data_key = data_source.get('data_attribute') or 'data'
171 170
    geojson = data_source.get('type') == 'geojson'
172 171
    error_summary = None
173
    exc = None
174 172

  
175 173
    try:
176 174
        entries = misc.json_loads(misc.urlopen(url).read())
......
194 192
        return entries
195 193
    except misc.ConnectionError as e:
196 194
        error_summary = 'Error loading %s (%s)' % (log_message_part, str(e))
197
        exc = e
198 195
    except (ValueError, TypeError) as e:
199 196
        error_summary = 'Error reading %s output (%s)' % (log_message_part, str(e))
200
        exc = e
201 197

  
202
    if data_source and (data_source.get('record_on_errors') or data_source.get('notify_on_errors')):
203
        try:
204
            raise Exception(error_summary) from exc
205
        except Exception:
206
            exc_info = sys.exc_info()
207
        get_publisher().notify_of_exception(
208
            exc_info,
198
    if data_source:
199
        get_publisher().record_error(
200
            error_summary,
209 201
            context='[DATASOURCE]',
210 202
            notify=data_source.get('notify_on_errors'),
211 203
            record=data_source.get('record_on_errors'),
......
312 304
            # noqa pylint: disable=eval-used
313 305
            value = eval(data_source.get('value'), global_eval_dict, variables)
314 306
            if not isinstance(value, collections.Iterable):
315
                try:
316
                    raise Exception(
317
                        'Python data source (%r) gave a non-iterable result' % data_source.get('value')
318
                    )
319
                except Exception:
320
                    exc_info = sys.exc_info()
321
                get_publisher().notify_of_exception(
322
                    exc_info,
307
                get_publisher().record_error(
308
                    'Python data source (%r) gave a non-iterable result' % data_source.get('value'),
323 309
                    context='[DATASOURCE]',
324 310
                    notify=data_source.get('notify_on_errors'),
325 311
                    record=data_source.get('record_on_errors'),
......
340 326
                return [{'id': x, 'text': x} for x in value]
341 327
            return value
342 328
        except Exception as exc:
343
            try:
344
                raise Exception(
345
                    'Failed to eval() Python data source (%r)' % data_source.get('value')
346
                ) from exc
347
            except Exception:
348
                exc_info = sys.exc_info()
349
            get_publisher().notify_of_exception(
350
                exc_info,
329
            get_publisher().record_error(
330
                'Failed to eval() Python data source (%r)' % data_source.get('value'),
331
                exception=exc,
351 332
                context='[DATASOURCE]',
352 333
                notify=data_source.get('notify_on_errors'),
353 334
                record=data_source.get('record_on_errors'),
wcs/logged_errors.py
94 94
        return error
95 95

  
96 96
    @classmethod
97
    def record_exception(cls, error_summary, plain_error_msg, publisher):
98
        try:
99
            context = publisher.substitutions.get_context_variables()
100
        except Exception:
101
            return
102
        formdata_id = context.get('form_number_raw')
103
        formdef_urlname = context.get('form_slug')
104
        if formdef_urlname:
105
            klass = FormDef
106
            if context.get('form_class_name') == 'CardDef':
107
                klass = CardDef
108
            formdef = klass.get_by_urlname(formdef_urlname)
109
            formdata = formdef.data_class().get(formdata_id, ignore_errors=True)
110
            workflow = formdef.workflow
111
        else:
112
            formdef = formdata = workflow = None
97
    def record_error(cls, error_summary, plain_error_msg, publisher, *args, **kwargs):
98
        formdef = kwargs.pop('formdef', None)
99
        formdata = kwargs.pop('formdata', None)
100
        workflow = kwargs.pop('workflow', None)
101
        if not any([formdef, formdata, workflow]):
102
            try:
103
                context = publisher.substitutions.get_context_variables()
104
            except Exception:
105
                return
106
            formdata_id = context.get('form_number_raw')
107
            formdef_urlname = context.get('form_slug')
108
            if formdef_urlname:
109
                klass = FormDef
110
                if context.get('form_class_name') == 'CardDef':
111
                    klass = CardDef
112
                formdef = klass.get_by_urlname(formdef_urlname)
113
                formdata = formdef.data_class().get(formdata_id, ignore_errors=True)
114
                workflow = formdef.workflow
115
            else:
116
                formdef = formdata = workflow = None
113 117
        return cls.record(
114
            error_summary, plain_error_msg, formdata=formdata, formdef=formdef, workflow=workflow
118
            error_summary,
119
            plain_error_msg,
120
            formdata=formdata,
121
            formdef=formdef,
122
            workflow=workflow,
123
            *args,
124
            **kwargs,
115 125
        )
116 126

  
117 127
    def build_tech_id(self):
wcs/publisher.py
14 14
# You should have received a copy of the GNU General Public License
15 15
# along with this program; if not, see <http://www.gnu.org/licenses/>.
16 16

  
17
import io
17 18
import json
18 19
import os
19 20
import pickle
......
350 351

  
351 352
        conn, cur = sql.get_connection_and_cursor()
352 353
        sql.drop_views(None, conn, cur)
353
        for formdef in FormDef.select() + CardDef.select():
354
            sql.do_formdef_tables(formdef)
354
        for _formdef in FormDef.select() + CardDef.select():
355
            sql.do_formdef_tables(_formdef)
355 356
        sql.migrate_global_views(conn, cur)
356 357
        conn.commit()
357 358
        cur.close()
358 359

  
359
    def notify_of_exception(self, exc_tuple, context=None, record=True, notify=True):
360
        exc_type, exc_value, tb = exc_tuple
361
        error_summary = traceback.format_exception_only(exc_type, exc_value)
362
        error_summary = error_summary[0][0:-1]  # de-listify and strip newline
363
        if context:
364
            error_summary = '%s %s' % (context, error_summary)
360
    def record_error(
361
        self, error_summary=None, context=None, exception=None, record=True, notify=False, *args, **kwargs
362
    ):
363
        if not record and not notify:
364
            return
365 365

  
366
        plain_error_msg = str(self._generate_plaintext_error(get_request(), self, exc_type, exc_value, tb))
366
        if exception is not None:
367
            exc_type, exc_value, tb = sys.exc_info()
368
            if not error_summary:
369
                error_summary = traceback.format_exception_only(exc_type, exc_value)
370
                error_summary = error_summary[0][0:-1]  # de-listify and strip newline
371
            plain_error_msg = str(
372
                self._generate_plaintext_error(get_request(), self, exc_type, exc_value, tb)
373
            )
374
        else:
375
            error_file = io.StringIO()
376
            print('Stack trace (most recent call first):', file=error_file)
377
            stack_summary = traceback.extract_stack()
378
            stack_summary.reverse()
379
            traceback.print_list(stack_summary[1:], file=error_file)
380
            if get_request():
381
                error_file.write('\n')
382
                error_file.write(get_request().dump())
383
                error_file.write('\n')
384
            plain_error_msg = error_file.getvalue()
367 385

  
368
        self.log_internal_error(error_summary, plain_error_msg, record=record, notify=notify)
386
        if context:
387
            error_summary = '%s %s' % (context, error_summary)
388
        if error_summary is None:
389
            return
369 390

  
370
    def log_internal_error(self, error_summary, plain_error_msg, record=False, notify=True):
371
        tech_id = None
391
        logged_exception = None
372 392
        if record and self.loggederror_class:
373
            logged_exception = self.loggederror_class.record_exception(
374
                error_summary, plain_error_msg, publisher=self
393
            logged_exception = self.loggederror_class.record_error(
394
                error_summary, plain_error_msg, publisher=self, exception=exception, *args, **kwargs
375 395
            )
376
            if logged_exception:
377
                tech_id = logged_exception.tech_id
378
        if not notify:
396
        if not notify or logged_exception and logged_exception.occurences_count > 1:
397
            # notify only first occurence
379 398
            return
380 399
        try:
381
            self.logger.log_internal_error(error_summary, plain_error_msg, tech_id)
400
            self.logger.log_internal_error(
401
                error_summary, plain_error_msg, logged_exception.tech_id if logged_exception else None
402
            )
382 403
        except OSError:
383 404
            # Could happen if there is no mail server available and exceptions
384 405
            # were configured to be mailed. (formerly socket.error)
385 406
            # Could also could happen on file descriptor exhaustion.
386 407
            pass
387 408

  
388
    def record_error(self, *args, **kwargs):
389
        if self.loggederror_class:
390
            self.loggederror_class.record(*args, **kwargs)
391

  
392 409
    def apply_global_action_timeouts(self):
393 410
        from wcs.workflows import Workflow, WorkflowGlobalActionTimeoutTrigger
394 411

  
wcs/qommon/afterjobs.py
95 95
                self.execute()
96 96
            else:
97 97
                self.job_cmd(job=self)
98
        except Exception:
99
            get_publisher().notify_of_exception(sys.exc_info())
98
        except Exception as e:
99
            get_publisher().record_error(exception=e, notify=True)
100 100
            self.exception = traceback.format_exc()
101 101
            self.status = N_('failed')
102 102
        else:
wcs/qommon/cron.py
14 14
# You should have received a copy of the GNU General Public License
15 15
# along with this program; if not, see <http://www.gnu.org/licenses/>.
16 16

  
17
import sys
18

  
19 17
from django.conf import settings
20 18

  
21 19

  
......
69 67
            publisher.substitutions.feed(extra_source(publisher, None))
70 68
        try:
71 69
            job.function(publisher)
72
        except Exception:
73
            publisher.notify_of_exception(sys.exc_info(), context='[CRON]')
70
        except Exception as e:
71
            publisher.record_error(exception=e, context='[CRON]', notify=True)
wcs/qommon/ident/franceconnect.py
16 16

  
17 17
import base64
18 18
import hashlib
19
import sys
20 19
import urllib.parse
21 20
import uuid
22 21

  
......
415 414

  
416 415
            try:
417 416
                value = WorkflowStatusItem.compute(value, context=user_info)
418
            except Exception:
419
                get_publisher().notify_of_exception(sys.exc_info(), context='[FC-user-compute]')
417
            except Exception as e:
418
                get_publisher().record_error(exception=e, context='[FC-user-compute]', notify=True)
420 419
                continue
421 420
            if field_varname == '__name':
422 421
                user.name = value
wcs/qommon/saml2.py
15 15
# along with this program; if not, see <http://www.gnu.org/licenses/>.
16 16

  
17 17
import os
18
import sys
19 18
import time
20 19
import urllib.parse
21 20
from xml.sax.saxutils import escape
......
299 298
                != get_cfg('sp', {}).get('saml2_base_url') + get_request().get_url()[last_slash:]
300 299
            ):
301 300
                return error_page('SubjectConfirmation Recipient Mismatch')
302
        except Exception:
303
            get_publisher().notify_of_exception(sys.exc_info(), context='[SAML]')
301
        except Exception as e:
302
            get_publisher().record_error(exception=e, context='[SAML]', notify=True)
304 303
            return error_page('Error checking SubjectConfirmation Recipient')
305 304

  
306 305
        assertions_dir = os.path.join(get_publisher().app_dir, 'assertions')
......
339 338
                return error_page('Assertion received too early')
340 339
            if not_on_or_after and current_time > not_on_or_after:
341 340
                return error_page('Assertion expired')
342
        except Exception:
343
            get_publisher().notify_of_exception(sys.exc_info(), context='[SAML]')
341
        except Exception as e:
342
            get_publisher().record_error(exception=e, context='[SAML]', notify=True)
344 343
            return error_page('Error checking Assertion Time')
345 344

  
346 345
        # TODO: check for unknown conditions
......
491 490
            if field and field.convert_value_from_anything:
492 491
                try:
493 492
                    field_value = field.convert_value_from_anything(field_value)
494
                except ValueError:
495
                    get_publisher().notify_of_exception(sys.exc_info(), context='[SAML]')
493
                except ValueError as e:
494
                    get_publisher().record_error(exception=e, context='[SAML]', notify=True)
496 495
                    continue
497 496
            if user.form_data.get(field_id) != field_value:
498 497
                user.form_data[field_id] = field_value
wcs/qommon/storage.py
686 686
        with locket.lock_file(objects_dir + '.lock.index'):
687 687
            try:
688 688
                self.update_indexes(previous_object_value, relative_object_filename)
689
            except Exception:
689
            except Exception as e:
690 690
                # something failed, we can't keep using possibly broken indexes, so
691 691
                # we notify of the bug and remove the indexes
692
                get_publisher().notify_of_exception(sys.exc_info(), context='[STORAGE]')
692
                get_publisher().record_error(exception=e, context='[STORAGE]', notify=True)
693 693
                self.destroy_indexes()
694 694

  
695 695
    @classmethod
wcs/wf/profile.py
16 16

  
17 17
import datetime
18 18
import json
19
import sys
20 19
import time
21 20
import urllib.parse
22 21
import xml.etree.ElementTree as ET
......
158 157
                if field and field.convert_value_from_anything:
159 158
                    try:
160 159
                        field_value = field.convert_value_from_anything(field_value)
161
                    except ValueError:
162
                        get_publisher().notify_of_exception(sys.exc_info(), context='[PROFILE]')
160
                    except ValueError as e:
161
                        get_publisher().record_error(exception=e, context='[PROFILE]', notify=True)
163 162
                        # invalid attribute, do not update it
164 163
                        del new_data[field.varname]
165 164
                        continue
......
188 187
        user_uuid = user.name_identifiers[0]
189 188
        try:
190 189
            url = user_ws_url(user_uuid)
191
        except MissingSecret:
192
            get_publisher().notify_of_exception(sys.exc_info(), context='[PROFILE]')
190
        except MissingSecret as e:
191
            get_publisher().record_error(exception=e, context='[PROFILE]', notify=True)
193 192
            return
194 193

  
195 194
        payload = new_data.copy()
wcs/wf/register_comment.py
14 14
# You should have received a copy of the GNU General Public License
15 15
# along with this program; if not, see <http://www.gnu.org/licenses/>.
16 16

  
17
import sys
18

  
19 17
from quixote import get_publisher
20 18
from quixote.html import htmltext
21 19

  
......
121 119
                # needed by AttachmentEvolutionPart.from_upload()
122 120
                upload.get_file_pointer()
123 121
                formdata.evolution[-1].add_part(AttachmentEvolutionPart.from_upload(upload, to=to))
124
            except Exception:
125
                get_publisher().notify_of_exception(sys.exc_info(), context='[comment/attachments]')
122
            except Exception as e:
123
                get_publisher().record_error(exception=e, context='[comment/attachments]', notify=True)
126 124
            continue
127 125

  
128 126
    def perform(self, formdata):
wcs/wf/roles.py
14 14
# You should have received a copy of the GNU General Public License
15 15
# along with this program; if not, see <http://www.gnu.org/licenses/>.
16 16

  
17
import sys
18 17
import urllib.parse
19 18

  
20 19
from quixote import get_publisher, get_request, get_response
......
105 104
        user_uuid = user.name_identifiers[0]
106 105
        try:
107 106
            url = roles_ws_url(role_uuid, user_uuid)
108
        except MissingSecret:
109
            get_publisher().notify_of_exception(sys.exc_info(), context='[ROLES]')
107
        except MissingSecret as e:
108
            get_publisher().record_error(exception=e, context='[ROLES]', notify=True)
110 109
            return
111 110

  
112 111
        def after_job(job=None):
......
175 174
        user_uuid = user.name_identifiers[0]
176 175
        try:
177 176
            url = roles_ws_url(role_uuid, user_uuid)
178
        except MissingSecret:
179
            get_publisher().notify_of_exception(sys.exc_info(), context='[ROLES]')
177
        except MissingSecret as e:
178
            get_publisher().record_error(exception=e, context='[ROLES]', notify=True)
180 179
            return
181 180

  
182 181
        def after_job(job=None):
wcs/wf/wscall.py
392 392
                workflow_data['%s_connection_error' % self.varname] = str(e)
393 393
                formdata.update_workflow_data(workflow_data)
394 394
                formdata.store()
395
            self.action_on_error(self.action_on_network_errors, formdata, exc_info=sys.exc_info())
395
            self.action_on_error(self.action_on_network_errors, formdata, exception=e)
396 396
            return
397 397

  
398 398
        app_error_code = get_app_error_code(response, data, self.response_type)
......
452 452
        if self.response_type == 'json':
453 453
            try:
454 454
                d = json_loads(force_text(data))
455
            except (ValueError, TypeError):
455
            except (ValueError, TypeError) as e:
456 456
                formdata.update_workflow_data(workflow_data)
457 457
                formdata.store()
458
                self.action_on_error(
459
                    self.action_on_bad_data, formdata, response, data=data, exc_info=sys.exc_info()
460
                )
458
                self.action_on_error(self.action_on_bad_data, formdata, response, data=data, exception=e)
461 459
            else:
462 460
                workflow_data['%s_response' % self.varname] = d
463 461
                if isinstance(d, dict) and self.method == 'POST':
......
480 478
            )
481 479
            formdata.evolution[-1].add_part(attachment)
482 480

  
483
    def action_on_error(self, action, formdata, response=None, data=None, exc_info=None):
481
    def action_on_error(self, action, formdata, response=None, data=None, exception=None):
484 482
        if action in (':pass', ':stop') and (
485 483
            self.notify_on_errors or self.record_on_errors or self.record_errors
486 484
        ):
487
            if exc_info:
488
                summary = traceback.format_exception_only(exc_info[0], exc_info[1])[-1]
489
            else:
485
            if exception is None:
490 486
                summary = '<no response>'
491 487
                if response is not None:
492 488
                    summary = '%s %s' % (response.status_code, response.reason)
493
                try:
494
                    raise Exception(summary)
495
                except Exception:
496
                    exc_info = sys.exc_info()
497

  
498
            if self.notify_on_errors or self.record_on_errors:
499
                get_publisher().notify_of_exception(
500
                    exc_info, context='[WSCALL]', notify=self.notify_on_errors, record=self.record_on_errors
501
                )
489
            else:
490
                exc_type, exc_value = sys.exc_info()[:2]
491
                summary = traceback.format_exception_only(exc_type, exc_value)[-1]
492

  
493
            get_publisher().record_error(
494
                error_summary=summary,
495
                exception=exception,
496
                context='[WSCALL]',
497
                notify=self.notify_on_errors,
498
                record=self.record_on_errors,
499
            )
502 500
            if self.record_errors and formdata.evolution:
503 501
                formdata.evolution[-1].add_part(JournalWsCallErrorPart(summary, self.label, data))
504 502
                formdata.store()
......
510 508
        # verify that target still exist
511 509
        try:
512 510
            self.parent.parent.get_status(action)
513
        except KeyError:
514
            try:
515
                raise IndexError(
516
                    'reference to invalid status %r in workflow %r, status %r'
517
                    % (action, self.parent.parent.name, self.parent.name)
518
                )
519
            except IndexError:
520
                get_publisher().notify_of_exception(sys.exc_info(), context='[WSCALL]')
521
                raise AbortActionException()
511
        except KeyError as e:
512
            get_publisher().record_error(
513
                'reference to invalid status %r in workflow %r, status %r'
514
                % (action, self.parent.parent.name, self.parent.name),
515
                exception=e,
516
                context='[WSCALL]',
517
                notify=True,
518
            )
519
            raise AbortActionException()
522 520

  
523 521
        formdata.status = 'wf-%s' % action
524 522
        formdata.store()
wcs/workflows.py
21 21
import itertools
22 22
import os
23 23
import random
24
import sys
25 24
import time
26 25
import uuid
27 26
import xml.etree.ElementTree as ET
......
1354 1353
            try:
1355 1354
                # noqa pylint: disable=eval-used
1356 1355
                anchor_date = eval(self.anchor_expression, get_publisher().get_global_eval_dict(), variables)
1357
            except Exception:
1356
            except Exception as e:
1358 1357
                # get the variables in the locals() namespace so they are
1359 1358
                # displayed within the trace.
1360 1359
                expression = self.anchor_expression  # noqa pylint: disable=unused-variable
1361 1360
                # noqa pylint: disable=unused-variable
1362 1361
                global_variables = get_publisher().get_global_eval_dict()
1363
                get_publisher().notify_of_exception(sys.exc_info(), context='[TIMEOUTS]')
1362
                get_publisher().record_error(exception=e, context='[TIMEOUTS]', notify=True)
1364 1363

  
1365 1364
        # convert anchor_date to datetime.datetime()
1366 1365
        if isinstance(anchor_date, datetime.datetime):
......
1374 1373
        elif isinstance(anchor_date, str) and anchor_date:
1375 1374
            try:
1376 1375
                anchor_date = get_as_datetime(anchor_date)
1377
            except ValueError:
1378
                get_publisher().notify_of_exception(sys.exc_info(), context='[TIMEOUTS]')
1376
            except ValueError as e:
1377
                get_publisher().record_error(exception=e, context='[TIMEOUTS]', notify=True)
1379 1378
                anchor_date = None
1380 1379
        elif anchor_date:
1381 1380
            # timestamp
1382 1381
            try:
1383 1382
                anchor_date = datetime.datetime.fromtimestamp(anchor_date)
1384
            except TypeError:
1385
                get_publisher().notify_of_exception(sys.exc_info(), context='[TIMEOUTS]')
1383
            except TypeError as e:
1384
                get_publisher().record_error(exception=e, context='[TIMEOUTS]', notify=True)
1386 1385
                anchor_date = None
1387 1386

  
1388 1387
        if not anchor_date:
......
2365 2364

  
2366 2365
                try:
2367 2366
                    attachment = WorkflowStatusItem.compute(attachment, allow_complex=True, raises=True)
2368
                except Exception:
2369
                    get_publisher().notify_of_exception(sys.exc_info(), context='[workflow/attachments]')
2367
                except Exception as e:
2368
                    get_publisher().record_error(exception=e, context='[workflow/attachments]', notify=True)
2370 2369
                else:
2371 2370
                    if attachment:
2372 2371
                        complex_value = get_publisher().get_cached_complex_data(attachment)
......
2387 2386
                    # and magically convert string like 'form_var_*_raw' to a PicklableUpload
2388 2387
                    # noqa pylint: disable=eval-used
2389 2388
                    picklableupload = eval(attachment, global_eval_dict, local_eval_dict)
2390
                except Exception:
2391
                    get_publisher().notify_of_exception(sys.exc_info(), context='[workflow/attachments]')
2389
                except Exception as e:
2390
                    get_publisher().record_error(exception=e, context='[workflow/attachments]', notify=True)
2392 2391
                    continue
2393 2392

  
2394 2393
                if not picklableupload:
......
2402 2401
            if not isinstance(upload, PicklableUpload):
2403 2402
                try:
2404 2403
                    upload = FileField.convert_value_from_anything(upload)
2405
                except ValueError:
2406
                    get_publisher().notify_of_exception(sys.exc_info(), context='[workflow/attachments]')
2404
                except ValueError as e:
2405
                    get_publisher().record_error(exception=e, context='[workflow/attachments]', notify=True)
2407 2406
                    continue
2408 2407

  
2409 2408
            yield upload
wcs/wscalls.py
16 16

  
17 17
import collections
18 18
import json
19
import sys
20 19
import urllib.parse
21 20
import xml.etree.ElementTree as ET
22 21

  
......
99 98
            try:
100 99
                value = WorkflowStatusItem.compute(value, raises=True)
101 100
                value = str(value)
102
            except Exception:
103
                get_publisher().notify_of_exception(sys.exc_info())
101
            except Exception as e:
102
                get_publisher().record_error(exception=e, notify=True)
104 103
            else:
105 104
                key = force_str(key)
106 105
                value = force_str(value)
......
130 129
            for (key, value) in post_data.items():
131 130
                try:
132 131
                    payload[key] = WorkflowStatusItem.compute(value, allow_complex=True, raises=True)
133
                except Exception:
134
                    get_publisher().notify_of_exception(sys.exc_info())
132
                except Exception as e:
133
                    get_publisher().record_error(exception=e, notify=True)
135 134
                else:
136 135
                    if payload[key]:
137 136
                        payload[key] = get_publisher().get_cached_complex_data(payload[key])
......
163 162
    except ConnectionError as e:
164 163
        if not handle_connection_errors:
165 164
            raise e
166
        if notify_on_errors or record_on_errors:
167
            exc_info = sys.exc_info()
168
            get_publisher().notify_of_exception(
169
                exc_info, context='[WSCALL]', notify=notify_on_errors, record=record_on_errors
170
            )
165
        get_publisher().record_error(
166
            exception=e, context='[WSCALL]', notify=notify_on_errors, record=record_on_errors
167
        )
171 168
        return (None, None, None)
172 169

  
173 170
    app_error_code = get_app_error_code(response, data, 'json')
......
176 173
        summary = '<no response>'
177 174
        if response is not None:
178 175
            summary = '%s %s' % (status, response.reason)
179
        try:
180
            raise Exception(summary)
181
        except Exception:
182
            exc_info = sys.exc_info()
183
        get_publisher().notify_of_exception(
184
            exc_info, context='[WSCALL]', notify=notify_on_errors, record=record_on_errors
176
        get_publisher().record_error(
177
            summary, context='[WSCALL]', notify=notify_on_errors, record=record_on_errors
185 178
        )
186 179

  
187 180
    return (response, status, data)
188
-