You know when you have coded your biggest project and every time it runs you can barely figure out what is doing, only by reading a series of print statements and the creation of strategically saved files?
Well if that is the case, you ought to learn logging and step up your game.
With a proper system of logging. you will have a consistent, ordered and a more reliable way to understand your own code, to time and track its progression and capture bugs easily.
Let’s break down the advantages of logging:
- Formatting: Logging allows you to standardize every message using a format of your choosing.
- Time tracking: Alongside the message you can add the time when it is generated.
- Compact: All messages are gathered in files, you don’t need to scroll up continuously.
- Versatility: Print does not work everywhere (i.e., objects without __str__ methods).
- Flexibility: Logging allows different levels of importance to your messages so you regulate what to show.
With all of this, you won’t be the only one who can understand your code.
Functions of logging
“logging” is a standard library of python, therefore, you won’t need to install it. Simply type:
And you are good to go.
At its core there are only five functions of logging that we are going to use all the times, those are:
- logging.debug and logging.info: Conceptually, These two are the closest to a normal print. They report events following the normal run of a program.
- logging.info: It’s meant to confirm that things are working as expected.
- logging.debug: These are messages expected to arise during debugging and it should carry detailed information.
- logging.warning: This is a low level error message, meaning that there is an issue present that won’t stop the flow of the program but it has to be noted by the user.
- logging.error: This message is meant when an issue has arisen that won’t allow the program to execute some functions, but it won’t stop the program.
- logging.critical: This the highest level of error messages. A critical means that a serious issue is present and the program may stop.
Let’s see an example:
By running this short code you can see that the first two messages have not been printed, to your terminal or notebook.
This is due to the low level of severity that logging gives to these messages by default.
These levels of severity are:
- debug (Level 10)
- info (Level 20)
- warning (Level 30)
- error (Level 40)
- critical (Level 50)
In a sense, python tries to be clever. If a message like debug() and info() is expected, there is no reason to fill the entire terminal with these (imagine you have hundreds of them).
But for all the other messages, the user has to be notified at every stage (However, you can modify the levels for each function if you wish).
Create a log file
Now, let’s see how we can create a log file.
Note: If you have followed along and tried the previous script, you need to restart your python terminal (or kernel) otherwise the following script won’t work, we will see why shortly.
To create a log file we will use the function basicConfig() this should be executed before any other logging function.
Let’s see what are basicConfig() has done:
With its first parameter “filename” we chose the name of the log file, this is pretty straightforward. But with the second one “level” we have decided what level of message we want our file to have. Indeed, by default it won’t log info() and debug() messages.
Other two important parameter of basicConfig() are:
- filemode: In what mode to open the file. The default is “a”, which means append.
- format: In what way you want the message string to appear
- datefmt: A specified date/time format
Let’s see an example:
If the previous script did not work is because you have not restarted Python. To solve this problem try to run this line:
If the result is not an empty list runs the following:
Let’s see what all that means later *.
Classes of logging
Until now we have seen the basic functions of logging and every time, they were the modules of a class logger called by default, root.
It is good practice to define a new name of the class logger, in your code. Indeed, you can customize your logger and adapt to the needs of your project.
Commonly a logger is configured using:
- logger.setLevel() set the the lowest severity for the log messages similarly to what we have seen with basicConfig()
- logging.Formatter(): How to format the messages
- logger.addHandler() and logger.removeHandler() add and remove handler objects from the logger object.
- logger.addFilter() and logger.removeFilter() add and remove filter objects from the logger object.
Handlers are used to send the logs to one or more output destinations. Each logger can have more than one handler. And like with loggers you can configure the level of severity of a message, formats, etc.
Therefore, in each project you can have more than one logger, dealing with different parts and more than one handlers to deal with one or more logging files.
Like we saw with basicConfig() we can format our messages in different ways obtaining different string formats.
There are many other classes (e.g.: Filters) but I’ll leave to you to explore there more let’s see now an example:
Using this “Hello World” you should be able to create every logging system you might require.
* By using logging.info() before logging.basicConfig() we have already called the logger root, therefore logging.basicConfig() won’t work. We have to remove everything connected to root before starting again.