[utils] Add a '_cwtracehtml' GET parameter to trace self._cw.w() calls (closes #4601327)
The core of this patch is in UStringIO.write(). When tracing is enabled,
write() doesn't just append the 'value' argument to the underlying list.
Instead, a stack trace is recorded and a special HTML "source" is
formatted.
The output with tracing enabled is an HTML page, with the original HTML
escaped, and made clickable to show the stack trace when the write()
call was done.
This allows answering the recurring question: "who wrote this tag
here?!"
--- a/utils.py Wed Nov 19 11:57:55 2014 +0100
+++ b/utils.py Wed Nov 19 12:13:32 2014 +0100
@@ -206,12 +206,23 @@
specifed in the constructor
"""
+ def __init__(self, tracewrites=False, *args, **kwargs):
+ self.tracewrites = tracewrites
+ super(UStringIO, self).__init__(*args, **kwargs)
+
def __nonzero__(self):
return True
def write(self, value):
assert isinstance(value, unicode), u"unicode required not %s : %s"\
% (type(value).__name__, repr(value))
+ if self.tracewrites:
+ from traceback import format_stack
+ stack = format_stack(None)[:-1]
+ escaped_stack = xml_escape(json_dumps(u'\n'.join(stack)))
+ escaped_html = xml_escape(value).replace('\n', '<br/>\n')
+ tpl = u'<span onclick="alert(%s)">%s</span>'
+ value = tpl % (escaped_stack, escaped_html)
self.append(value)
def getvalue(self):
@@ -234,8 +245,8 @@
script_opening = u'<script type="text/javascript">\n'
script_closing = u'\n</script>'
- def __init__(self, req):
- super(HTMLHead, self).__init__()
+ def __init__(self, req, *args, **kwargs):
+ super(HTMLHead, self).__init__(*args, **kwargs)
self.jsvars = []
self.jsfiles = []
self.cssfiles = []
@@ -399,10 +410,15 @@
w(self.script_opening)
w(u'\n\n'.join(self.post_inlined_scripts))
w(self.script_closing)
- header = super(HTMLHead, self).getvalue()
- if skiphead:
- return header
- return u'<head>\n%s</head>\n' % header
+ # at the start of this function, the parent UStringIO may already have
+ # data in it, so we can't w(u'<head>\n') at the top. Instead, we create
+ # a temporary UStringIO to get the same debugging output formatting
+ # if debugging is enabled.
+ headtag = UStringIO(tracewrites=self.tracewrites)
+ if not skiphead:
+ headtag.write(u'<head>\n')
+ w(u'</head>\n')
+ return headtag.getvalue() + super(HTMLHead, self).getvalue()
class HTMLStream(object):
@@ -416,10 +432,11 @@
"""
def __init__(self, req):
+ self.tracehtml = req.tracehtml
# stream for <head>
self.head = req.html_headers
# main stream
- self.body = UStringIO()
+ self.body = UStringIO(tracewrites=req.tracehtml)
# this method will be assigned to self.w in views
self.write = self.body.write
self.doctype = u''
@@ -457,6 +474,26 @@
def getvalue(self):
"""writes HTML headers, closes </head> tag and writes HTML body"""
+ if self.tracehtml:
+ css = u'\n'.join((u'span {',
+ u' font-family: monospace;',
+ u' word-break: break-all;',
+ u' word-wrap: break-word;',
+ u'}',
+ u'span:hover {',
+ u' color: red;',
+ u' text-decoration: underline;',
+ u'}'))
+ style = u'<style type="text/css">\n%s\n</style>\n' % css
+ return (u'<!DOCTYPE html>\n'
+ + u'<html>\n<head>\n%s\n</head>\n' % style
+ + u'<body>\n'
+ + u'<span>' + xml_escape(self.doctype) + u'</span><br/>'
+ + u'<span>' + xml_escape(self.htmltag) + u'</span><br/>'
+ + self.head.getvalue()
+ + self.body.getvalue()
+ + u'<span>' + xml_escape(u'</html>') + u'</span>'
+ + u'</body>\n</html>')
return u'%s\n%s\n%s\n%s\n</html>' % (self.doctype,
self.htmltag,
self.head.getvalue(),
--- a/web/request.py Wed Nov 19 11:57:55 2014 +0100
+++ b/web/request.py Wed Nov 19 12:13:32 2014 +0100
@@ -128,8 +128,12 @@
self.datadir_url = vreg.config.https_datadir_url
else:
self.datadir_url = vreg.config.datadir_url
+ #: enable UStringIO's write tracing
+ self.tracehtml = False
+ if vreg.config.debugmode:
+ self.tracehtml = bool(form.pop('_cwtracehtml', False))
#: raw html headers that can be added from any view
- self.html_headers = HTMLHead(self)
+ self.html_headers = HTMLHead(self, tracewrites=self.tracehtml)
#: received headers
self._headers_in = Headers()
if headers is not None: