Python Logging Basics

Introduction

Logging helps you keep track of events happening during the execution of your code, which can then be used in the future for debugging purposes. It provides a better picture of the flow of the application and helps developers trace the source of errors that happens during execution of your code, thereby enhancing the maintainability of the application.

In Python, most of the basic logging features are provided by the Python standard library. Hence, you can add logging to your application easily without any extra configurations. The standard logging module enables the developer to write status messages to a file or any other output stream.

The logging Module

The logging module is readily available in Python environments by default and provides a default logger named "root". It defines the functions and classes that implement logging functionality.

The logging API provided by the standard library enables you to include your own messages in the application log, as well as integration with messages from third-party modules. It also provides a mechanism to annotate the log messages with the source, timestamp, severity and other metadata, which helps in log analysis.

Logging Types (Log levels)

Each log message is associated with a severity level, which is an integer used to signal the criticality of the event(s) logged. The logging module has a helper function for each log level - these are named according to the log level. Below, is a list of logging levels along with their recommended usage.

Debug (logger.debug): Provide very detailed output. Used for diagnosing problems.

Info (logger.info): Provides information on successful execution. Confirms if things are working as expected.

Warning (logger.warn or logger.warning): Issue a warning regarding a problem that might occur in the future or a recoverable fault.

Error (logger.error): Indicates a problem in the software as it is not executing as expected.

Critical (logger.critical): Indicates a serious error that might stop the program from running.

By default, the root logger is set up to report all messages at or above the Warning level - any messages below this level are filtered. However, it is possible to configure the module explicitly to be more or less selective in filtering.

To add logging to a Python script, just import the module using import logging, and after a successful import the script can log messages using the logging.* methods, such as logging.debug().

Here you can see a simple example of the logging module in action:

import logging
logging.warning("Caution: This is the root logger!")

Output:

WARNING:root:Caution: This is the root logger!

Logger Objects

The logging module allows the user to create multiple logger objects. Different types of logger objects can be used to obtain fine-grained control over how different parts of a Python application log their messages - for example, the main Python app can use the root logger whereas the third-party libraries used from within this app can use their own logger objects with their own configurations.

While using the default root logger's functions, we can call the functions directly, e.g., logging.debug(). It is possible to configure your own logger by creating an object of the Logger class, and this can be useful if your application has multiple modules.

Let's have a look at some of the classes and functions in the logging module. The basic classes and their functions are as follows:

Loggers – Exposes the interface that the app uses. The objects of this class are used directly to call the functions in the application.

Handlers – Sends log messages to the appropriate location in the software like standard output console, a file, via HTTP, or even email (via SMTP).

Filters – Gives fine-grained control over the choice of log records to display.

Formatters – Specifies the final layout of log records, specifying the attributes the output must contain.

Of these, the objects of the Logger class are used most frequently.

To create a new logger, we can use the logging.getLogger() method. In the following script, we log errors using the root logger as well as our custom logger my_logger.

Each log message will not only indicate the source - the logger object through which it was logged - but will display a message based on the configuration of that logger object.

In the following sections, we will look at the various configuration options of logger objects.

Logging to a File vs the Standard Output

By default logger objects output the logs to the standard output. You can use basicConfig() method to change this and other parameters. Below is a list of parameters for the basicConfig method:

level: Set the logger a severity level. Any messages below this severity level will not be logged.

filename: The name of the file where logs are written.

filemode: The mode in which the file specified, if any, is to be opened.

format: Specifies the format of the log message. This is a string with LogRecord attributes.

LogRecord object contains the information of the events that are logged, like the line number, time, name of the logger, etc. Discussing the LogRecord object is outside the scope of this article but more information is available here.

Below is a summary of the steps to be followed to record log events to a file:

If you want to change the way the date/time shows up, you can configure it using the datefmt parameter of the basicConfig method.

Logging Variables

In real-world applications, we need to output logs according to the dynamic changes happening in our application. As seen in the above example, logging methods take a string as an argument. In addition, we can include variables and format the string with placeholders and then pass it to the log method. At run-time, the value of the variables will be output in the log messages.

Starting with Python 3.6 f-Strings can be used as an alternative to string format specifiers, thereby keeping the code easily readable when there are multiple parameters. Using f-strings you can specify any Python expression as part of the message and these are evaluated during run-time and the result will be embedded into the log messages.

Logging Stack Traces

Capturing stack traces in your application is also supported by the logging module. Setting the exc_info parameter to True while calling the logging functions allows us to capture the exception information. By using this function, we can get information about the exception that is currently being handled. The information is specific to the current thread and the current stack frame.

In case exceptions are not handled by the current stack frame, the information is then obtained from its caller (i.e. the calling stack frame) and so on until it finds a stack frame to handle the exception. The stack frame has the information of the most recently handled exception.

If the stack has no exceptions being handled, a tuple having None value is returned. Otherwise the function returns value of type (the type of exception that is handled), value (exception parameter) and traceback (the traceback object which encapsulates the call stack where the exception occurred originally).

Conclusion

The design of the logging module is very practical, providing ready-to-use logging features that can add basic logging to a small project. It can be easily extended by using logger objects and their rich configuration options to support the needs of even the most demanding of applications. In addition to messages, the logging module can also be used to log exceptions and stack traces. This concludes the basic tutorial on implementing logging in Python.