I recently wrote an article about tracking user exceptions using Rollbar. For the article, I implemented an experiment using Python. While I think Rollbar is a fantastic tool, I also think that in some ways developers could take a better approach than inserting a call to rollbar.report_exc_info in the except block. This method only traps exceptions you are expecting to throw and ignores unintended exceptions. In addition, if the code is not in a try … except block, then the exception does not get reported to your Rollbar account.
Fortunately, there is another solution in Python for globally capturing exceptions. In this article, I’ll take a look at the problems that this approach solves, then explain how to implement it.
First, Some Problems
So let’s take a look at some simple example code. Granted, it is silly, but it makes the point:
import rollbar import sys def print_message(obj): try: if not isinstance(obj.msg, str): raise TypeError print(obj.msg) except (AttributeError, TypeError) as e: exc_type, exc_value, traceback = sys.exc_info() rollbar.report_exc_info((exc_type, exc_value, traceback)) # other code to handle or re-raise
Every try … except block will have to have a call to sys.exc_info and a call to Rollbar to report the exception. This ends up littering logging code throughout your code base. While many developers may be fine with this, I find it gets in the way of reading the logic of the code.
Our above code will only phone home to Rollbar if there is an AttributeError or Type error. If another type of error is thrown, the exception is not sent to Rollbar. Consider the following code:
class SampleClass(object): def __init__(self, msg): self._msg = msg @property def msg(self): raise NameError
For the sake of the example, the class is sabotaged with a NameError in place of a legitimate reason to raise an exception. If we create an instance of SampleClass and access its msg property with our print_message function above, a NameError will be raised. However, no exception will be reported to Rollbar.
This implies that every time it is determined by the developers, a method or function should throw an exception not listed in the except block, and the code will have to be refactored to catch the new exception so it will be reported to Rollbar. This is an error-prone process we would wish to avoid.
Consider also that any code not wrapped in a try … except block will never report to Rollbar at all. This could possibly lead to developers overusing try … except in Python or implementing a bare except in hopes of catching everything.
Fortunately, Python’s sys module provides sys.excepthook which allows developers to inject custom behavior into Python’s exception handling.
Here is an example of how it could be used:
import sys import rollbar ROLLBAR_POST_ACCESS_TOKEN = < your token here > rollbar.init(ROLLBAR_POST_ACCESS_TOKEN, 'production') def rollbar_except_hook(exc_type, exc_value, traceback): # Report the issue to rollbar here. rollbar.report_exc_info((exc_type, exc_value, traceback)) # display the error as normal here sys.__excepthook__(exc_type, exc_value, traceback) sys.excepthook = rollbar_except_hook
By replacing the default sys.excepthook with a custom functionality to log exceptions to Rollbar, we get a single place in our code, which will be called when any exception is raised. Note that sys also provides the excepthook dunder, which can be called to trigger the default behavior of sys.excepthook.
Let’s revisit our print_message function:
import sys def print_message(obj): if not isinstance(obj.msg, str): raise TypeError try: print(obj.msg) except AttributeError as e: # other code to handle or re-raise
That cleans up rather nicely, and both expected and unexpected exceptions will be logged to Rollbar (rather than just the expected exceptions), while allowing developers to create custom handling for expected exceptions in the standard manner without extra code.
Further, if an exception is thrown by code not wrapped in a try … except block, it will trigger our custom behavior and log the exception to Rollbar.
In this article, we have taken a look at some of the problems with using Rollbar. We’ve looked at some of the issues around logging uncaught exceptions to Rollbar, and a possible solution.