David Cramer's Blog

Error Tracing in Sentry

A few weeks ago we pushed out an update to Sentry, bumping it’s version to 1.6.0. Among the changes was a new “Sentry ID” value which is created by the client, rather than relying on the server. This seems like something insignificant, but it allows you to do something very powerful: trace errors from the customer or developer down to the precise request and log entry.

Exposing Sentry ID

The new IDs are generated automatically when a message is processed (by the client), so you won’t need to make any changes on that end. Likely, however, you’re going to want to expose these at your application level for a couple of different reasons. The first one we’re going to cover is your customer’s experience.

The easiest way to expose this information in a useful manner, is by creating a modified 500.html. In DISQUS’ case, we mention the error reference ID to the end-user, so that when they’re reporting a problem they can pass along this information.

Create a custom 500 handler

The first thing you’re going to need to do is to create a custom 500 handler. This defined in urls.py, so we’re just going to go ahead and create the view in-place.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def handler500(request):
    """
    An error handler which exposes the request object to the error template.
    """
    from django.template import Context, loader
    from django.http import HttpResponseServerError
    from disqus.context_processors import default
    import logging
    import sys
    try:
        context = default(request)
    except Exception, e:
        logging.error(e, exc_info=sys.exc_info(), extra={'request': request})
        context = {}

    context['request'] = request

    t = loader.get_template('500.html') # You need to create a 500.html template.
    return HttpResponseServerError(t.render(Context(context)))

We’re going to expose the request object to our 500.html in the above. Keep in mind, that doing this allows you to add some logic into your template, and you’re going to need to be very careful that this logic can’t raise a new exception.

Tweaking your 500.html

The next thing you’ll need to do is to tweak your 500.html template to actually show the Sentry ID. Assuming the request object was passed into Sentry, it will attach the last error seen under request.sentry['id']. Given this, we can easily report it to the end-user in our template:

<p>The Disqus team has been alerted and we're on the case. For more information, check out <a href="http://status.disqus.com">Disqus Status »</a></p>
{% if request.sentry.id %}
    <p>If you need assistance, you may reference this error as <strong>{{ request.sentry.id }}</strong>.</p>
{% endif %}

Sentry ID as a response header

The other quick solution to get access to this variable is simply by enabling an included response middleware, SentryResponseErrorIdMiddleware. Just pop open your settings.py and append it to your MIDDLEWARE_CLASSES:

1
2
3
4
MIDDLEWARE_CLASSES = (
    ...,
    'sentry.client.middleware.SentryResponseErrorIdMiddleware',
)

Now if you check your response headers after hitting an error, you should see X-Sentry-ID.

Find errors by ID

Sentry makes it very easy to pull up error messages by ID. The one requirement is that you’re going to need to ensure sentry.filters.SearchFilter is included within SENTRY_FILTERS (it’s enabled by default). Once done, Sentry will discover if you’re entering a UUID hex value (the Sentry ID) in the search box, and it will jump directly to that error’s page.

You’ll also notice that all messages are now tagged with their unique Sentry ID as well (per the screenshot).