1 # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 """ 4 Middleware that displays everything that is printed inline in 5 application pages. 6 7 Anything printed during the request will get captured and included on 8 the page. It will usually be included as a floating element in the 9 top right hand corner of the page. If you want to override this 10 you can include a tag in your template where it will be placed:: 11 12 <pre id="paste-debug-prints"></pre> 13 14 You might want to include ``style="white-space: normal"``, as all the 15 whitespace will be quoted, and this allows the text to wrap if 16 necessary. 17 18 """ 19 20 from cStringIO import StringIO 21 import re 22 import cgi 23 from paste.util import threadedprint 24 from paste import wsgilib 25 from paste import response 26 import six 27 import sys 28 29 _threadedprint_installed = False 30 31 __all__ = ['PrintDebugMiddleware'] 32 33 class TeeFile(object): 34 35 def __init__(self, files): 36 self.files = files 37 38 def write(self, v): 39 if isinstance(v, unicode): 40 # WSGI is picky in this case 41 v = str(v) 42 for file in self.files: 43 file.write(v) 44 45 class PrintDebugMiddleware(object): 46 47 """ 48 This middleware captures all the printed statements, and inlines 49 them in HTML pages, so that you can see all the (debug-intended) 50 print statements in the page itself. 51 52 There are two keys added to the environment to control this: 53 ``environ['paste.printdebug_listeners']`` is a list of functions 54 that will be called everytime something is printed. 55 56 ``environ['paste.remove_printdebug']`` is a function that, if 57 called, will disable printing of output for that request. 58 59 If you have ``replace_stdout=True`` then stdout is replaced, not 60 captured. 61 """ 62 63 log_template = ( 64 '<pre style="width: 40%%; border: 2px solid #000; white-space: normal; ' 65 'background-color: #ffd; color: #000; float: right;">' 66 '<b style="border-bottom: 1px solid #000">Log messages</b><br>' 67 '%s</pre>') 68 69 def __init__(self, app, global_conf=None, force_content_type=False, 70 print_wsgi_errors=True, replace_stdout=False): 71 # @@: global_conf should be handled separately and only for 72 # the entry point 73 self.app = app 74 self.force_content_type = force_content_type 75 if isinstance(print_wsgi_errors, six.string_types): 76 from paste.deploy.converters import asbool 77 print_wsgi_errors = asbool(print_wsgi_errors) 78 self.print_wsgi_errors = print_wsgi_errors 79 self.replace_stdout = replace_stdout 80 self._threaded_print_stdout = None 81 82 def __call__(self, environ, start_response): 83 global _threadedprint_installed 84 if environ.get('paste.testing'): 85 # In a testing environment this interception isn't 86 # useful: 87 return self.app(environ, start_response) 88 if (not _threadedprint_installed 89 or self._threaded_print_stdout is not sys.stdout): 90 # @@: Not strictly threadsafe 91 _threadedprint_installed = True 92 threadedprint.install(leave_stdout=not self.replace_stdout) 93 self._threaded_print_stdout = sys.stdout 94 removed = [] 95 def remove_printdebug(): 96 removed.append(None) 97 environ['paste.remove_printdebug'] = remove_printdebug 98 logged = StringIO() 99 listeners = [logged] 100 environ['paste.printdebug_listeners'] = listeners 101 if self.print_wsgi_errors: 102 listeners.append(environ['wsgi.errors']) 103 replacement_stdout = TeeFile(listeners) 104 threadedprint.register(replacement_stdout) 105 try: 106 status, headers, body = wsgilib.intercept_output( 107 environ, self.app) 108 if status is None: 109 # Some error occurred 110 status = '500 Server Error' 111 headers = [('Content-type', 'text/html')] 112 start_response(status, headers) 113 if not body: 114 body = 'An error occurred' 115 content_type = response.header_value(headers, 'content-type') 116 if (removed or 117 (not self.force_content_type and 118 (not content_type 119 or not content_type.startswith('text/html')))): 120 if replacement_stdout == logged: 121 # Then the prints will be lost, unless... 122 environ['wsgi.errors'].write(logged.getvalue()) 123 start_response(status, headers) 124 return [body] 125 response.remove_header(headers, 'content-length') 126 body = self.add_log(body, logged.getvalue()) 127 start_response(status, headers) 128 return [body] 129 finally: 130 threadedprint.deregister() 131 132 _body_re = re.compile(r'<body[^>]*>', re.I) 133 _explicit_re = re.compile(r'<pre\s*[^>]*id="paste-debug-prints".*?>', 134 re.I+re.S) 135 136 def add_log(self, html, log): 137 if not log: 138 return html 139 text = cgi.escape(log) 140 text = text.replace('\n', '<br>') 141 text = text.replace(' ', ' ') 142 match = self._explicit_re.search(html) 143 if not match: 144 text = self.log_template % text 145 match = self._body_re.search(html) 146 if not match: 147 return text + html 148 else: 149 return html[:match.end()] + text + html[match.end():] 150