Projet

Général

Profil

0001-misc-only-log-missing-statuses-as-functional-errors-.patch

Thomas Noël, 07 juin 2018 17:31

Télécharger (11,3 ko)

Voir les différences:

Subject: [PATCH] misc: only log missing statuses as functional errors (#24327)

 tests/test_form_pages.py   | 27 ++++++++++++-
 wcs/admin/logged_errors.py | 21 +++++++++--
 wcs/logged_errors.py       | 77 +++++++++++++++++++++++++++++++-------
 wcs/publisher.py           |  2 +-
 wcs/workflows.py           | 12 +++---
 5 files changed, 113 insertions(+), 26 deletions(-)
tests/test_form_pages.py
1694 1694
    assert 'barXYZ' in resp.body # unchanged value is still there
1695 1695
    assert formdef.data_class().get(data_id).status == 'wf-%s' % st2.id
1696 1696

  
1697
    # jump to a nonexistent status == do not jump
1697
    # jump to a nonexistent status == do not jump, but add a LoggedError
1698
    LoggedError.wipe()
1699
    assert LoggedError.count() == 0
1698 1700
    editable.status = 'deleted_status_id'
1699 1701
    workflow.store()
1700 1702
    # go back to st1
......
1710 1712
    resp = resp.forms[0].submit('submit')
1711 1713
    resp = resp.follow()
1712 1714
    assert formdef.data_class().get(data_id).status == 'wf-%s' % st1.id  # stay on st1
1715
    assert LoggedError.count() == 1
1716
    logged_error = LoggedError.select()[0]
1717
    assert logged_error.formdata_id == str(formdata.id)
1718
    assert logged_error.formdef_id == formdef.id
1719
    assert logged_error.workflow_id == workflow.id
1720
    assert logged_error.status_id == st1.id
1721
    assert logged_error.status_item_id == editable.id
1722
    assert logged_error.occurences_count == 1
1723

  
1724
    # do it again: increment logged_error.occurences_count
1725
    page = login(get_app(pub), username='foo', password='foo').get('/test/%s/' % data_id)
1726
    resp = page.forms[0].submit('button_editable')
1727
    resp = resp.follow()
1728
    resp.forms[0]['f1'] = 'foo3'
1729
    resp = resp.forms[0].submit('submit')
1730
    resp = resp.forms[0].submit('submit')
1731
    resp = resp.follow()
1732
    assert formdef.data_class().get(data_id).status == 'wf-%s' % st1.id  # stay on st1
1733
    assert LoggedError.count() == 1
1734
    logged_error = LoggedError.select()[0]
1735
    assert logged_error.occurences_count == 2
1713 1736

  
1714 1737
def test_form_count_dispatching(pub):
1715 1738
    user = create_user(pub)
......
4359 4382
    assert LoggedError.count() == 1
4360 4383

  
4361 4384
    error = LoggedError.get_on_index(
4362
            '34-12-zerodivisionerror-integer-division-or-modulo-by-zero', 'tech_id')
4385
            '34-12-None-None-zerodivisionerror-integer-division-or-modulo-by-zero', 'tech_id')
4363 4386
    assert error.occurences_count == 2
4364 4387

  
4365 4388
    assert len(LoggedError.get_ids_with_indexed_value('formdef_id', '34')) == 1
wcs/admin/logged_errors.py
56 56
        if workflow:
57 57
            r += htmltext('  <li>%s <a href="/backoffice/workflows/%s/">%s</a></li>') % (
58 58
                    _('Workflow: '), workflow.id, workflow.name)
59
            status = self.error.get_status()
60
            if status:
61
                r += htmltext('<ul>')
62
                r += htmltext(
63
                    '<li>%s <a href="/backoffice/workflows/%s/status/%s/">%s</a></li>') % (
64
                        _('Status:'), workflow.id, status.id, status.name)
65
                status_item = self.error.get_status_item()
66
                if status_item:
67
                    r += htmltext(
68
                        '<li>%s <a href="/backoffice/workflows/%s/status/%s/items/%s/">%s</a></li>') % (
69
                            _('Action:'), workflow.id, status.id, status_item.id,
70
                            _(status_item.description))
71
                r += htmltext('</ul>')
59 72

  
60 73
        if formdef:
61 74
            formdata = self.error.get_formdata()
......
68 81
                r += htmltext('</li>')
69 82

  
70 83
        r += htmltext('</ul>')
84

  
85
        if not self.error.traceback:
86
            return r.getvalue()
87

  
71 88
        parts = (N_('Exception'), N_('Stack trace (most recent call first)'),
72 89
                N_('Form'), N_('Cookies'), N_('Environment'))
73 90
        current_part = None
......
105 122
            r += htmltext('<li>%s</li>' % _('Acked'))
106 123
        r += htmltext('<li><a href="delete">%s</a></li>') % _('Delete')
107 124
        r += htmltext('</ul>')
108
        if get_publisher().logger.error_email:
109
            r += htmltext('<div class="infonotice"><p>')
110
            r += _('This error has also been sent by email to %s.') % get_publisher().logger.error_email
111
            r += htmltext('</p></div>')
112 125
        return r.getvalue()
113 126

  
114 127
    def ack(self):
wcs/logged_errors.py
31 31
    formdata_id = None
32 32
    formdef_id = None
33 33
    workflow_id = None
34
    status_id = None
35
    status_item_id = None
34 36
    traceback = None
35 37
    occurences_count = 0
36 38
    first_occurence_timestamp = None
......
41 43
    XML_NODES = [
42 44
            ('summary', 'str'), ('traceback', 'str'),
43 45
            ('formdata_id', 'str'), ('formdef_id', 'str'), ('workflow_id', 'str'),
46
            ('status_id', 'str'), ('status_item_id', 'str'),
44 47
            ('occurences_count', 'int'),
45 48
            ('first_occurence_timestamp', 'datetime'),
46 49
            ('latest_occurence_timestamp', 'datetime'),
47 50
            ('acked', 'bool')]
48 51

  
49 52
    @classmethod
50
    def record(cls, error_summary, plain_error_msg, publisher):
53
    def record(cls, error_summary, plain_error_msg=None, formdata=None,
54
               formdef=None, workflow=None, status=None, status_item=None):
51 55
        error = cls()
52 56
        error.summary = error_summary
53 57
        error.traceback = plain_error_msg
54
        try:
55
            context = publisher.substitutions.get_context_variables()
56
        except:
57
            return
58 58

  
59
        formdef_urlname = context.get('form_slug')
60
        if not formdef_urlname:
59
        if formdata:
60
            error.formdata_id = str(formdata.id)
61
            error.formdef_id = formdata.formdef.id
62
            error.workflow_id = formdata.formdef.workflow.id
63
        elif formdef:
64
            error.formdef_id = formdef.id
65
            error.workflow_id = formdef.workflow.id
66

  
67
        if not error.formdef_id:
61 68
            # cannot attach error to formdef, don't record in journal, it will
62 69
            # still be sent by email to administrators.
63 70
            return
64 71

  
65
        error.formdata_id = context.get('form_number_raw')
66
        if formdef_urlname:
67
            formdef = FormDef.get_by_urlname(formdef_urlname)
68
            error.formdef_id = formdef.id
69
            error.workflow_id = formdef.workflow_id
72
        if not error.workflow_id:
73
            error.workflow_id = workflow.id
74

  
75
        if status_item:
76
            error.status_item_id = status_item.id
77
            if status_item.parent:
78
                error.status_id = status_item.parent.id
79
        if status:
80
            error.status_id = status.id
70 81

  
71 82
        error.first_occurence_timestamp = datetime.datetime.now()
72 83
        error.id = '%s-%s' % (
......
80 91
        error.latest_occurence_timestamp = datetime.datetime.now()
81 92
        error.store()
82 93

  
94
    @classmethod
95
    def record_exception(cls, error_summary, plain_error_msg, publisher):
96
        try:
97
            context = publisher.substitutions.get_context_variables()
98
        except Exception as e:
99
            return
100
        formdata_id = context.get('form_number_raw')
101
        formdef_urlname = context.get('form_slug')
102
        if not formdef_urlname:
103
            # cannot attach error to formdef, don't record in journal, it will
104
            # still be sent by email to administrators.
105
            return
106
        formdef = FormDef.get_by_urlname(formdef_urlname)
107
        formdata = formdef.data_class().get(formdata_id, ignore_errors=True)
108
        cls.record(error_summary, plain_error_msg, formdata=formdata,
109
                   formdef=formdef, workflow=formdef.workflow)
110

  
83 111
    @property
84 112
    def tech_id(self):
85
        return ('%s-%s-%s' % (self.formdef_id, self.workflow_id, simplify(self.summary)))[:200]
113
        return ('%s-%s-%s-%s-%s' % (self.formdef_id, self.workflow_id, self.status_id,
114
                                    self.status_item_id, simplify(self.summary)))[:200]
86 115

  
87 116
    def get_formdef(self):
88 117
        return FormDef.get(self.formdef_id, ignore_errors=True)
......
91 120
        return Workflow.get(self.workflow_id, ignore_errors=True)
92 121

  
93 122
    def get_formdata(self):
123
        if not self.formdata_id:
124
            return None
94 125
        return self.get_formdef().data_class().get(self.formdata_id, ignore_errors=True)
126

  
127
    def get_status(self):
128
        if not self.status_id:
129
            return None
130
        workflow = self.get_workflow()
131
        if not workflow:
132
            return None
133
        for status in workflow.possible_status:
134
            if status.id == self.status_id:
135
                return status
136
        return None
137

  
138
    def get_status_item(self):
139
        status = self.get_status()
140
        if not status or not status.items:
141
            return None
142
        for status_item in status.items:
143
            if status_item.id == self.status_item_id:
144
                return status_item
145
        return None
wcs/publisher.py
287 287
        super(WcsPublisher, self).log_internal_error(error_summary,
288 288
                plain_error_msg, record=record)
289 289
        if record:
290
            LoggedError.record(error_summary, plain_error_msg, publisher=self)
290
            LoggedError.record_exception(error_summary, plain_error_msg, publisher=self)
291 291

  
292 292
    def apply_global_action_timeouts(self):
293 293
        from wcs.workflows import Workflow, WorkflowGlobalActionTimeoutTrigger
wcs/workflows.py
1725 1725
            return []
1726 1726

  
1727 1727
        targets = [x for x in self.parent.parent.possible_status if x.id == self.status]
1728
        if not targets:
1729
            get_publisher().get_app_logger().error(
1730
                            'reference to invalid status in workflow %r, status %r, item %r' % (
1731
                                    self.parent.parent.name,
1732
                                    self.parent.name,
1733
                                    self.description))
1728
        if not targets and formdata:  # do not log in presentation context: formdata is needed
1729
            from wcs.logged_errors import LoggedError
1730
            message = _('reference to invalid status %s in status %s, action %s') % (
1731
                self.status, self.parent.name, _(self.description))
1732
            LoggedError.record(message, formdata=formdata, status_item=self)
1733

  
1734 1734
        return targets
1735 1735

  
1736 1736
    def get_jump_label(self):
1737
-