Logging and Logging Downsides to Defensive Programming in Python
Feb 27, 2019 • 7 Minute Read
Introduction
For an introduction and dive into the theory around Defensive Programming, check out the first guide in this series.
Logging
Logging can be used similarly to assert statements. It can provide run time debugging information and potentially improve upon your executable documentation. Although, logging is not exactly like asserts. It has a few extra benefits.
1. Increased Control Granularity
Python's logging module is very comprehensive and customizable. You can send messages to several different levels and each level can be turned on and off at will. So, you might consider some situations to be more severe than others and can codify this with log levels.
Remember, this isn't the case with asserts because they rely on debug mode.
2. More Dynamic Control
Logging allows you to read your log level from pretty much anywhere. Some common places are a config file, environment variable, or a database. This flexibility allows you to control how much log information you see without re-running or re-distributing your application.
In contrast, Python doesn't allow you to dynamically assign to the debug constant for assert statements. So, you can't turn asserts on and off without re-running the application.
This is worth considering when deciding on your defensive coding strategy. Dynamic logging control is especially important if you an "alternative" distribution mechanism like PyInstaller.
3. Silently Save Tracebacks
It's useful to have traceback and debugging information at the time of a failure and smart logging usage can do this for you almost automatically.
This concept is demonstrated in a great exception handling post by Doug Hellman:
```python
def main():
logging.basicConfig(level=logging.WARNING)
log = logging.getLogger('example')
try:
throws()
return 0
except Exception, err:
log.exception('Error from throws():')
return 1
```
The call to log.exception automatically adds the exception information for us. Then, we could configure the logger to put tracebacks and exceptions into a separate log file for later inspection without all the normal information and warnings.
This exception file could contain excellent debug information if enabled in production code. This opens up a lot of exciting possibilities for mining this logging data:
- Discover users trying different permutations of features we've never tested or even considered, which could lead to adding new features to make common use-cases easier.
- Find common errors due to a misunderstanding that users have about how the application works, which could lead to writing better user documentation.
4. Higher-level Combinations with Assertions
You can also use assertions in tandem with your logging. For example, you could run your application in the default debug mode then catch and log your AssertionError to a different file. This could lead to even more data mining possibilities such as finding out an environmental assumption you have about the platform your running in.
These are just a few uses for logging in the context of Defensive Programming. In fact, you could use logging to create low-fidelity solutions for all sorts of problems, which is another post all in its own.
You could even use logging to build your own analytics tool. Log in to a network or Dropbox file each time a feature is used. Then, have a shell script come behind you every time and collect these files. Now you have a ton of usage information in a simple text-based format, opening up tons of possibilities for data mining and helping your users.
Keep in mind that most users won't fill out surveys. So, this would be a way to collect information on the features they are trying to use or common workflows. Then, you could make these better in future releases.
Logging Downsides
Logging suffers from many of the same downsides as asserts. However, logging's additional flexibility comes with additional baggage to consider.
1. Difficulty Managing Consistent Levels
The most difficult part of logging is using the available set of levels consistently throughout a code base. This boils down to the subjective problem of naming, which is one of only two problems in Computer Science. The best solution is to commit guidelines alongside your code in a style guide. Then, there is some project-specific documentation for newcomers to refer to when adding log messages.
2. Design of Multiple Loggers
The logging module is extremely flexible, but that flexibility comes at a cost. Logging configurations can get complicated. Consider a strategy like the following:
- Debug-level messages go to a hidden file called .debug
- Info, warning, and error level messages go to stderr
- Critical and exception level messages go into GUI pop up boxes
This is a good starting point. Also, Python's documentation includes several good logging strategies that is definitely worth a read before deciding on your setup.
Remember, logging could be your saving grace when chasing a difficult bug. So, take the time to learn the logging system and be sure to design your configuration carefully. Even simple applications deserve a good, carefully designed logging strategy.
Conclusion
This style of development is tough to categorize and unfortunately, there aren't any solid rules to say when to use what. So, I encourage you to keep the guidelines in mind. Continue on to the next guide in this series to learn more about unit testing for defensive programming.
The guidelines will lead to a subtle change in mindset. The mindset change is important, not the tools and mechanisms themselves. Eventually you'll make some mistakes by overusing asserts or logging and start to form your own style. Also, the requirements for every project differ so it's important to learn all the tools and combine them in ways that make sense for your situation.