Wellfire Interactive // Expertise for established Django SaaS applications

Logging's low hanging fruit: errors and other things that go bump in the night (This Old Pony #22)

I never wanted to do this in the first place!
I…  I wanted to be… A LUMBERJACK! 

Back to logging this week.

When you’re working with an existing or legacy application one of the challenges is lack of information. Information about what’s actually going on under the hood and how users are actually interacting with the software.

In both the case of tests and logging the purpose is to give you information to make decisions to improve the software in one way or another. Like software without tests, it can be daunting to approach software without much in the way of helpful logging to know where to start. And in each the best place to start is with errors.

For the sake of this discussion I’ll assume that you already have some kind of logging infrastructure in place[0] and/or are using a decent error tracker[1]. #
Starting with serious fixes There’s a lot of valuable information to gleaned from a running app at all urgency levels, but in a pinch your best bet is to start at the top.

Hopefully this doesn’t describe your codebase, however any “naked” try/except block should be updated with an exception logging statement.

Let’s take a block of code like so:

try: do\_something() except Exception: pass

There may be valid reasons not to raise the exception, say, in the context of a request to the user. However that doesn’t mean it should be ignored by the development team.

I call exceptions like this “naked” because they’re not clothed in an informative exception type. Informative exceptions are can be used to type match in cases where there’s some kind of anticipation of the shape of the exception. But in locations without a specific shape anticipated, that’s a good indicator we need to watch out for something potentially very wrong.

At a minimum what’s needed here is exception logging.

try: do\_something() except Exception:   logger.exception("Error trying to do something")

As minimal as this is, it’s a huge improvement over the original block. We now have an opportunity of seeing exactly what’s failing. From here the code can be fixed if a specific type of error crops up and the exception matching can be made a bit more fine grained.

And lest I forget to mention, if you’re using an error tracker you should get the full stacktrace as if the error had been raised for the end user. #
Exceptions to the exception rule It’s not always the case that you want to log exceptions.

Consider this block of code:

try: acme\_api.refresh\_anvil\_prices() except Exception: pass

Should you use an exception logging statement here? 

Nope. Or at least, probably not. The point of using an exception logging statement is to capture the stacktrace. Now, it’s entirely possible that you’re concerned about errors in your own stack making a call to a third party API, but usually the concern is some error raised by the client library due to a bad response from the API.

No, here we just want to log an error.

try:   acme\_api.refresh\_anvil\_prices() except Exception as err:   logger.error("Called Acme API for Anvil prices: {}".format(err))

Ideally with an API library you’ll have more specific exceptions including error messages and codes, but in any case the problem is usually downstream. Knowing “it broke” is enough.

No stacktrace needed

Just like with logged exceptions, with at least some error trackers if you use the error level logging you’ll get this included in your error tracker, including notifications and error aggregation.


[0] This could be local file logging or, especially if you’re using a PaaS or some other cloud based infrastructure, a service like Papertrail
[1] Sentry is what we rely on, and it will treat error messages like exceptions for the purpose of tracking

Learn from more articles like this how to make the most out of your existing Django site.