forked from gocept/Products.PerFactErrors
-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy patherrors.py
More file actions
159 lines (140 loc) · 5.66 KB
/
errors.py
File metadata and controls
159 lines (140 loc) · 5.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
import logging
import transaction
import ZPublisher.interfaces
import zope.component
import zExceptions.ExceptionFormatter
from zExceptions import Unauthorized
from zope.pagetemplate.pagetemplate import PTRuntimeError
from transaction.interfaces import TransientError
try:
from ZPublisher.HTTPRequest import WSGIRequest
except ImportError:
class WSGIRequest():
pass
logger = logging.getLogger(__name__)
@zope.component.adapter(ZPublisher.interfaces.IPubFailure)
def afterfail_error_message(event):
"""
Render error message after an aborted transaction.
The transaction in which the exception was raised gets aborted just before
IPubFailure, so we can safely commit a new one during our error handling
procedure. This is also the reason why we cannot use a logging view
directly.
"""
req = event.request
context = req['PARENTS'][0]
try:
error_type, error_value, error_tb = event.exc_info
render = getattr(context, 'afterfail_error_message_', None)
retry = isinstance(error_value, TransientError) and req.supports_retry()
if render is None or retry:
return
# With WSGI, the error traceback itself no longer is printed to the
# event.log, so we do that manually - except for special cases
log_error = (
isinstance(req, WSGIRequest)
and not isinstance(error_value, Unauthorized)
and not (
isinstance(error_value, PerFactException)
and not error_value.apperrorlog
)
)
if log_error:
logger.exception(error_value)
tracebacks = []
tracebacks.append('\n'.join(
zExceptions.ExceptionFormatter.format_exception(
error_type, error_value, error_tb,
as_html=True,
)
))
cause = error_value.__cause__
while cause:
tracebacks.append('\n'.join(
zExceptions.ExceptionFormatter.format_exception(
type(cause),
cause,
cause.__traceback__,
as_html=True,
)
))
cause = cause.__cause__
tracebacks.reverse()
error_tb = (
'The above exception was the '
'direct cause of the following exception:\n'
).join(tracebacks)
# Chameleon adds some traceback information to the error_value's
# __str__ method, which we do not want to show the user, so we replace
# the error value by its original string representation. Information
# like the exact line and expression in the page template can still be
# found in the event.log and in the traceback.
if (
hasattr(error_value, '_original__str__')
and not isinstance(error_value, PerFactException)
):
error_value = error_value._original__str__()
# Chameleon's own errors are also too verbose
if isinstance(error_value, PTRuntimeError):
error_value = 'Error parsing page template'
# Inside a method inside Zope, we can handle a string better, so we
# pass the __name__ as error_type
body = render(
error_type=error_type.__name__,
error_value=error_value,
error_tb=error_tb,
)
if body is not None:
req.response.setBody(body)
except Exception:
logger.exception('Error while rendering error message')
transaction.abort()
else: # no exception
transaction.commit()
class PerFactException(Exception):
'''
This special exception class may be used for equipping error
messages with dynamic content where it is possible to keep the
parts separate from the error message.
Also additional properties allow more control in error handling.
'''
def __init__(self, msg='', show_to_user=False,
apperrorlog=True, payload=None, **kw):
'''
Input:
- "msg" (string) is the error message,
possibly containing placeholders like "{lotnumber}"
- "show_to_user" (boolean, default False), controls
if the error is intended to be shown to the end
user even on a production system. Also causes the
error to be translated before showing it to the user
(not the one that is inserted into the database). If
it is not set, the user simply gets a generic "An
error occured" with an ID.
- "apperrorlog" (boolean, default True), controls if
the error is logged in the apperrorlog
- "payload" (dictionary, default None which means {})
contains values that should be inserted into the
placeholders, possibly after translation.
- **kw should be used to allow more arguments to be
passed later. Anything given here should be stored in
the class instance.
'''
super(PerFactException, self).__init__()
self.msg = msg
self.show_to_user = show_to_user
self.apperrorlog = apperrorlog
self.payload = payload or {}
def __str__(self):
return repr((self.msg, self.payload))
class PerFactUserWarning(PerFactException):
'''
This subclass of PerFactException automatically sets some
superclasses properties when initiallized. In case of this
subclass, the exception shall not by logged and the rendered
error message shall be displayed to the user.
'''
def __init__(self, msg='', payload=None, **kw):
super(PerFactUserWarning, self).__init__(
msg=msg, show_to_user=True,
apperrorlog=False, payload=payload, **kw)