Tornado Pretty Error Pages

One of the challenges encountered during development with Tornado is debugging template and uncaught exceptions. To address those issues we will pretty up the error reporting.

We will continue with the app that we have been building in Tornado Templates and Storm Chasing Tornado articles.

To refresh our memories our handler.py should look like the following:

import os.path
import tornado.ioloop
import tornado.web

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        storm_chasers = ["TIV 1", "TIV 2"]
       
        self.render("home.html", 
                    storm_chasers=storm_chasers)                   


handlers = [
    (r"/", MainHandler),
]

settings = dict(
        template_path=os.path.join(os.path.dirname(__file__), "templates"),
)               

application = tornado.web.Application(handlers, **settings)


if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()


Our base.html should look like the following:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-GB">
<head>
  <title>Storm Chasing Tornado</title>
</head>
<body>

<div id="header">
  {% block header %}{% end %}
</div>

<div id="content">
  {% block content %}{% end %}
</div>

<div id="footer">
  {% block footer %}{% end %}
</div>

</body>
</html>

And finally our home.html:

{% extends "base.html" %}

{% block header %}
  <h1>Storm Chasing Tornado</h1>
{% end %}

{% block content %}
  
<ul>
 {% for storm_chaser in storm_chasers %}
     <li>{{ storm_chaser }}</li>
 {% end %}
</ul>
{% end %}

{% block footer %}
  <p>2012 Storm Chaser
  <ul>
    <li><a href="http://carlosgabaldon.com">Home</a></li>
    <li><a href="about">About</a></li>
    <li><a href="terms.html">Terms of Service</a></li>
    <li><a href="privacy.html">Privacy</a></li>
  </ul>
  </p>
{% end %}

As in pervious articles we first need to start up our application.

carlos@webserver:~/tornado_alley$ python chasing_tornado.py

To understand the problem with debugging errors we need to break our application. We will do that by going into our home.html template and removing the closing bracket } from our block header.

..
{% block header %}
  <h1>Storm Chasing Tornado</h1>
{% end %
..

Now if we open our browser; we get a 500 error, but unfortunately no hint on what went wrong.

We see the error printed to our terminal session, but not very pretty:

[E 120320 21:05:09 web:1041] Uncaught exception GET / (127.0.0.1)
    HTTPRequest(protocol='http', host='0.0.0.0:8888', method='GET', uri='/', version='HTTP/1.1', remote_ip='127.0.0.1', body='', headers={'Accept-Language': 'en-US,en;q=0.8', 'Accept-Encoding': 'gzip,deflate,sdch', 'Host': '0.0.0.0:8888', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.100 Safari/535.19', 'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.3', 'Connection': 'keep-alive', 'Cookie': 'user="MQ==|1331478313|171690d80a5672578ef1fa741c3f0695943dfda3"; _xsrf=83d91b01577b420caa139b27eb8a20ba', 'Cache-Control': 'max-age=0', 'If-None-Match': '"45bb8bcea0330526023c5939bd2903a363d3e4e3"'})
    Traceback (most recent call last):
      File "/Users/carlos/Projects/tornado/tornado_alley/tornado/web.py", line 998, in _execute
        getattr(self, self.request.method.lower())(*args, **kwargs)
      File "chasing_tornado.py", line 33, in get
        storm_chasers=storm_chasers)
      File "/Users/carlos/Projects/tornado/tornado_alley/tornado/web.py", line 478, in render
        html = self.render_string(template_name, **kwargs)
      File "/Users/carlos/Projects/tornado/tornado_alley/tornado/web.py", line 582, in render_string
        t = loader.load(template_name)
      File "/Users/carlos/Projects/tornado/tornado_alley/tornado/template.py", line 324, in load
        self.templates[name] = self._create_template(name)
      File "/Users/carlos/Projects/tornado/tornado_alley/tornado/template.py", line 356, in _create_template
        template = Template(f.read(), name=name, loader=self)
      File "/Users/carlos/Projects/tornado/tornado_alley/tornado/template.py", line 213, in __init__
        self.file = _File(self, _parse(reader, self))
      File "/Users/carlos/Projects/tornado/tornado_alley/tornado/template.py", line 778, in _parse
        raise ParseError("Extra {%% end %%} block on line %d" % line)
    ParseError: Extra {% end %} block on line 14
[E 120320 21:05:09 web:1409] 500 GET / (127.0.0.1) 2.87ms

The cool thing is that since version 2.1 Tornado supports a debug application setting that will cause stack traces to be displayed in the browser on uncaught exceptions. Note: For security reasons we should never turn on this setting in a production environment, because sensitive information could be displayed to the public.

We turn this on by adding the debug key to our settings dictionary.

settings = dict(
        template_path=os.path.join(os.path.dirname(__file__), "templates"),
        debug=True,
)

If we refresh our browser again, we at least see the error in the browser, but still not pretty.

Tornado’s RequestHandler.write_error method handles writing the stack trace to the browser if we are in debug mode, but we can overwrite this method in our application to make our pretty error page.

To accomplish this we will define a write_error method in our MainHandler class in our handlers.py:

import os.path
import tornado.ioloop
import tornado.web
from tornado.options import options

class MainHandler(tornado.web.RequestHandler):
    def write_error(self, status_code, **kwargs):
        import traceback
        if self.settings.get("debug") and "exc_info" in kwargs:
            exc_info = kwargs["exc_info"]
            trace_info = ''.join(["%s<br/>" % line for line in traceback.format_exception(*exc_info)])
            request_info = ''.join(["<strong>%s</strong>: %s<br/>" % (k, self.request.__dict__[k] ) for k in self.request.__dict__.keys()])
            error = exc_info[1]
            
            self.set_header('Content-Type', 'text/html')
            self.finish("""<html>
                             <title>%s</title>
                             <body>
                                <h2>Error</h2>
                                <p>%s</p>
                                <h2>Traceback</h2>
                                <p>%s</p>
                                <h2>Request Info</h2>
                                <p>%s</p>
                             </body>
                           </html>""" % (error, error, 
                                        trace_info, request_info))
        
    def get(self):
        storm_chasers = ["TIV 1", "TIV 2"]
        
        self.render("home.html", 
                    storm_chasers=storm_chasers)                   


handlers = [
    (r"/", MainHandler),
]

settings = dict(
        template_path=os.path.join(os.path.dirname(__file__), "templates"),
        debug=True,
)
               
application = tornado.web.Application(handlers, **settings)

if __name__ == "__main__":
    tornado.options.parse_command_line()
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

Let’s walk through the changes we made.

We first check to ensure that the debug key is in our settings dictionary and the exception info is contained in the keyword arguments.

..
if self.settings.get("debug") and "exc_info" in kwargs:
..

Next we gather and format the stack trace, HTTP request, and exception.

..
exc_info = kwargs["exc_info"]
trace_info = ''.join(["%s<br/>" % line for line in traceback.format_exception(*exc_info)])
request_info = ''.join(["<strong>%s</strong>: %s<br/>" % (k, self.request.__dict__[k] ) for k in self.request.__dict__.keys()])
error = exc_info[1]
..

Finally, we set the content type and construct the html with our error data.

..
self.set_header('Content-Type', 'text/html')
self.finish("""<html>
                 <title>%s</title>
                 <body>
                    <h2>Error</h2>
                    <p>%s</p>
                    <h2>Traceback</h2>
                    <p>%s</p>
                    <h2>Request Info</h2>
                    <p>%s</p>
                 </body>
               </html>""" % (error, error, 
                            trace_info, request_info))
..

Now if we refresh our browser, we can see our pretty error page.

We should now have a good foundation for continued exploration of Tornado, the project that we have been working on can be cloned on GitHub :

$ git clone https://github.com/CarlosGabaldon/tornado_alley

2 thoughts on “Tornado Pretty Error Pages

Leave a comment