/
ext.py
277 lines (236 loc) · 8.96 KB
/
ext.py
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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# *-* coding: utf-8 *-*
# This file is part of wdb
#
# wdb Copyright (c) 2012-2016 Florian Mounier, Kozea
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from . import (
trace, start_trace, stop_trace, set_trace, Wdb, WEB_SERVER, WEB_PORT)
from .ui import dump
from ._compat import to_bytes, escape, logger, TCPServer
import traceback
from threading import current_thread
from uuid import uuid4
import sys
log = logger('wdb.ext')
_exc_cache = {}
# Patch shutdown request to open blocking interaction after the end of the
# request
shutdown_request = TCPServer.shutdown_request
def shutdown_request_patched(*args, **kwargs):
thread = current_thread()
shutdown_request(*args, **kwargs)
if thread in _exc_cache:
post_mortem_interaction(*_exc_cache.pop(thread))
TCPServer.shutdown_request = shutdown_request_patched
def post_mortem_interaction(uuid, exc_info):
wdb = Wdb.get(force_uuid=uuid)
type_, value, tb = exc_info
frame = None
_value = value
if not isinstance(_value, BaseException):
_value = type_(value)
wdb.obj_cache[id(exc_info)] = exc_info
wdb.extra_vars['__exception__'] = exc_info
exception = type_.__name__
exception_description = str(value) + ' [POST MORTEM]'
init = 'Echo|%s' % dump({
'for': '__exception__',
'val': escape('%s: %s') % (
exception, exception_description)})
wdb.interaction(
frame, tb, exception, exception_description,
init=init, iframe_mode=True, timeout=3)
def _handle_off(silent=False):
if not silent:
log.exception('Exception with wdb off')
uuid = str(uuid4())
_exc_cache[current_thread()] = (uuid, sys.exc_info())
web_url = 'http://%s:%d/pm/session/%s' % (
WEB_SERVER or 'localhost',
WEB_PORT or 1984,
uuid)
return to_bytes('''
<html>
<head>
<title>WDB Post Mortem</title>
<style>
html, body {
margin: 0;
padding: 0;
}
</style>
<script>
addEventListener("message", function (e) {
if (e.data == 'activate') {
var request = new XMLHttpRequest();
request.open('GET', '/__wdb/on', true);
request.onload = function() {
location.reload(true);
}
request.send();
}
}, false);
</script>
</head>
<body>
<iframe
src="%s"
marginheight="0"
marginwidth="0"
frameborder="0"
scrolling="no"
width="100%%"
height="100%%"
id="wdbframe">
</iframe>
</body>
</html>
''' % web_url)
class WdbMiddleware(object):
def __init__(self, app, start_disabled=False):
self.app = app
Wdb.enabled = not start_disabled
def __call__(self, environ, start_response):
path = environ.get('PATH_INFO', '')
if path == '/__wdb/on':
# Enable wdb
Wdb.enabled = True
start_response('200 OK', [('Content-Type', 'text/html')])
return to_bytes('Wdb is now on'),
if path == '/__wdb/shell':
def f():
# Enable wdb
wdb = Wdb.get()
Wdb.enabled = True
start_response('200 OK', [
('Content-Type', 'text/html'), ('X-Thing', wdb.uuid)])
yield to_bytes(' ' * 4096)
wdb = set_trace()
wdb.die()
yield to_bytes('Exited')
return f()
if Wdb.enabled:
def trace_wsgi(environ, start_response):
wdb = Wdb.get()
wdb.closed = False
appiter = None
try:
with trace(close_on_exit=True, under=self.app):
appiter = self.app(environ, start_response)
for item in appiter:
yield item
except Exception:
exc_info = sys.exc_info()
try:
start_response('500 INTERNAL SERVER ERROR', [
('Content-Type', 'text/html')])
except AssertionError:
log.exception(
'Exception with wdb off and headers already set',
exc_info=exc_info)
yield '\n'.join(
traceback.format_exception(*exc_info)
).replace('\n', '\n<br>\n').encode('utf-8')
else:
yield _handle_off()
finally:
hasattr(appiter, 'close') and appiter.close()
wdb.closed = False
return trace_wsgi(environ, start_response)
def catch(environ, start_response):
appiter = None
try:
appiter = self.app(environ, start_response)
for item in appiter:
yield item
except Exception:
exc_info = sys.exc_info()
try:
start_response('500 INTERNAL SERVER ERROR', [
('Content-Type', 'text/html')])
except AssertionError:
log.exception(
'Exception with wdb off and headers already set',
exc_info=exc_info)
yield '\n'.join(
traceback.format_exception(*exc_info)
).replace('\n', '\n<br>\n').encode('utf-8')
else:
yield _handle_off()
finally:
# Close set_trace debuggers
stop_trace(close_on_exit=True)
hasattr(appiter, 'close') and appiter.close()
return catch(environ, start_response)
def wdb_tornado(application, start_disabled=False):
from tornado.web import (
RequestHandler, ErrorHandler, HTTPError, StaticFileHandler)
from tornado.gen import coroutine
Wdb.enabled = not start_disabled
class WdbOn(RequestHandler):
def get(self):
Wdb.enabled = True
self.write('Wdb is now on')
class WdbOff(RequestHandler):
def get(self):
Wdb.enabled = False
self.write('Wdb is now off')
application.add_handlers(r'.*', ((r'/__wdb/on', WdbOn),
(r'/__wdb/off', WdbOff)))
old_execute = RequestHandler._execute
under = getattr(RequestHandler._execute, '__wrapped__', None)
@coroutine
def _wdb_execute(*args, **kwargs):
from wdb import trace, Wdb
wdb = Wdb.get()
wdb.closed = False # Activate request ignores
interesting = True
if len(args) > 0 and isinstance(args[0], ErrorHandler):
interesting = False
elif len(args) > 2 and isinstance(
args[0], StaticFileHandler) and args[2] == 'favicon.ico':
interesting = False
if Wdb.enabled and interesting:
with trace(close_on_exit=True, under=under):
old_execute(*args, **kwargs)
else:
old_execute(*args, **kwargs)
# Close set_trace debuggers
stop_trace(close_on_exit=True)
# Reset closed state
wdb.closed = False
RequestHandler._execute = _wdb_execute
def _wdb_error_writter(self, status_code, **kwargs):
silent = False
ex = kwargs.get('exc_info')
if ex:
silent = issubclass(ex[0], HTTPError)
self.finish(_handle_off(silent=silent))
RequestHandler.write_error = _wdb_error_writter
def add_w_builtin():
class w(object):
"""Global shortcuts"""
@property
def tf(self):
set_trace(sys._getframe().f_back)
@property
def start(self):
start_trace(sys._getframe().f_back)
@property
def stop(self):
stop_trace(sys._getframe().f_back)
@property
def trace(self):
trace(sys._getframe().f_back)
__builtins__['w'] = w()