[utils] Add a '_cwtracehtml' GET parameter to trace self._cw.w() calls (closes #4601327)
authorRémi Cardona <remi.cardona@logilab.fr>
Wed, 19 Nov 2014 12:13:32 +0100
changeset 10090 0aebb1c0f849
parent 10089 6346f53c85f1
child 10091 09878c2f8621
[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?!"
utils.py
web/request.py
--- 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: