Chris Shaw

Chris Shaw

Lead Profile All Articles

I am an ardent software developer and eager lifelong learner of code. My passion is the python language with particular emphasis in the 'django' and  'opencv' packages. I have created this blog to share my e... Read More

Tutorials


Django 2.1 and Sentry integration with Custom Error Pages.

Feb. 28, 2019 Chris Shaw 1539 views

So you have built a Django site, the client is happy and you put it in production mode in the wild. But what if there are errors in cases that you could not even have imagined? How do you make sure you know what errors that users are experiencing?

At the very least you should enable logging, with email alerts being sent for any ERROR or CRITICAL level errors. This is probably acceptable for a low traffic site. I would however reccomend using Sentry with thier powerful Djando intergration. In this tutorial, we will look at how easy it is to intergrate a free Sentry account with our project, and as a bonus, set up some custom error pages that also send logs to Sentry and even have an optional feedback form.

Create A Sentry Account

If you have not already signed up with Sentry, head on over to https://sentry.io/ and click on the 'Get Started' and fill in the simple form to create your account. 

Next, you will be asked to 'Choose a language or framework:'. At time of writing this article, Django is listed under 'Popular', but if you don't see it, there is a 'filter' facility. Click on 'Django' and then 'Give your project a name'. I tend to use the url of my project, but call it what suits you and click 'Create Project'.

Currently, you are then asked to 'Tell us a bit about yourself:', but this is optional. Do so if you wish and then click the 'Next' button.

Right, it is time to 'Configure your application'. I don't think they could have made it any simpler.

  1. They tell you the how to install the correct sentry-sdk using pip install from you virtual environment.
  2. You simply need to copy and paste into your settings.py the imports and init function as is. If you have not already, set DEBUG = False in you settings file now and save the changes. Restart the app to make the changes effective.
  3. Sentry is waiting to receive an error, this is quite easy, simply put a spelling error in one of the imports in your urls.py or views.py, restart the app, open a page. Normally, you would get a debug page open, this time it will say something like 'Incomplete response received from application', but now the error has been sent to Sentry. Quickly correct the intentional error you previously created and restart the app again. 

You can go though the wealth of information provided by Sentry on the error you created. I find Sentry to be very user friendly and you can follow the helpful hints provided. I have elected not to copy and paste the instructions here, as I would rather you copied and pasted from the instructions provided.

Custom Error Pages

The default error pages (400, 403, 404 and 500) are bland and there are a lot of good reasons to create custom ones and give your users a better experience. Also, Sentry does not log these errors, and it may be useful to. These instructions are for Django 2+. I may update with older versions later.

Edit our urls.py and add the following lines at the bottom. Simply replace mysite with the name of your app. Please note, you do NOT need to import handler400. handler403 etc.

handler400 = 'mysite.views.error_400_view'
handler403 = 'mysite.views.error_403_view'
handler404 = 'mysite.views.error_404_view'
handler500 = 'mysite.views.error_500_view'

Next, in views, add the following imports and views:

from django.shortcuts import render

from sentry_sdk import capture_message

"""
Custom Error pages for the top level domain
"""
def error_400_view(request, exception):
    id = capture_message("Bad Request (400)", level="error")
    data = {"event_id": id,
            "code": 400,
            "title": "BAD REQUEST",
            "description": "The server cannot process the request due to a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing)."}
    return render(request,'error.html', data)

def error_403_view(request, exception):
    id = capture_message("Forbidden (403)", level="error")
    data = {"event_id": id,
            "code": 403,
            "title": "FORBIDDEN",
            "description": "The client has insufficient authentication credentials for the server to process this request."}
    return render(request,'error.html', data)

def error_404_view(request, exception):
    id = capture_message("Not Found (404)", level="error")
    data = {"event_id": id,
            "code": 404, "title":
            "NOT FOUND", "description":
            "The server is not able to find the requested resource. AKA, Page Not Found."}
    return render(request,'error.html', data)

def error_500_view(request):
    id = capture_message("Internal Server Error (500)", level="error")
    data = {"event_id": id,
            "code": 500,
            "title": "INTERNAL SERVER ERROR",
            "description": "The server encountered an unexpected condition that prevented it from fulfilling the request."}
    return render(request,'error.html', data)

 

Basically, for each of the 4 custom views, I am capturing the error with Sentry and saving the event_id in a variable called id. I then create the data for our error template containing the event id, error code, an error title and a description of the error. Finally, in all the views, I render the same template with the data. Next create the file error.html in our template directory and here is some of my sample code.

{% extends 'base.html' %}
{% load staticfiles %}

{% block title %}Error: {{ code }}{% endblock %}

{% block js %}
  {% if event_id %}
    <script src="https://browser.sentry-cdn.com/4.6.4/bundle.min.js" crossorigin="anonymous"></script>
    <script src="https://code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
    <script>
    (function($) {
      $('.error_feedback').click(function() {
        Sentry.init({ dsn: 'https://xxxxxxxxxxxxxxxxxxxxxxx@sentry.io/xxxxxx' });
        Sentry.showReportDialog({ eventId: '{{ event_id }}' });
      });
    })(jQuery);
    </script>
  {% endif %}
{% endblock %}

{% block content %}
      <h2>Opps, a {{ code }} error has been encountered!</h2>
      <h5>{{ title }}</h5>
      <p>{{ description }}</p>
      <p>All errors are logged and reviewed, because our visitors are important to us.<p>
      <a href="{% url 'home' %}">Home</a>
      <button id="error_feedback" type="button">Submit Error Feedback</button>
{% endblock %}

The gem in the code above is that small block of javascript code, based on the docs, please select django in the dropdown. You will have to put in your dsn and if you are logged in, the code in the docs includes your dsn. Basically what happens, is when a user clicks on the 'Submit Error Feedback', a model form from Sentry pops up that enables the user to add extra information that may be useful to you. This is wonderful and so full of potential. If only the end users would cooperate. 

Summary

We now have our site in production mode, any errors generated, including the 400, 403, 400 and 500 errors, are logged by Sentry and you are sent emails so that you can promptly deal with issues. I have only scraped the basics of Sentry, there is so much more, such as teams and integration with releases on Github.


blog comments powered by Disqus