Logging
The Python logging
module is a powerful, highly configurable logger for applications. The downside to logging
is that it can be a little difficult to wrap your head around at first, and there are many, many, many ways to configure it.
I have settled on using logging.config.dictConfig
to configure my logging, and that is how my notes/code are written.
Todo
- Document formatters
- Document format strings
- Document date format strings
- Document handlers
- Console/Stream
- File
- RotatingFile
- TimedRotatingFile
- Socket
- Document loggers
- Document adding handlers & formatters
- Document "pre-defining" loggers for loggers that don't exist yet/haven't been called yet.
- Document overriding 3rd-party loggers
Configuring logging module
The logging
module can be configured a number of ways, most notably with basicConfig()
, but my personal favorite is with dictConfig()
, which lets you pre-define your entire applications' logging (all current and potential future loggers) in a Python dict
. No need to import handlers, formatters, and other miscellaneous classes from the logging
module; with dictConfig()
, everything is a string or integer!
Shameless-plug
I wrote a module, red-logging
to aid with logging
boilerplate. This module is specific to my own needs and uses, and may not be suitable for you or your project(s), but can serve as a helpful reference for some patterns.
red-logging
does not import any dependencies, it simply organizes some of logging
's functionality into classes, context managers, and functions.
Configure with dictConfig()
You can use a custom dict
to configure an entire app's logging all in one place.
Warning
When using logging.config.dictConfig(logging_config_dict)
, be careful to only call dictConfig()
once per execution. Calling this method multiple times can lead to instability in your logging as the logging config dict
s overwrite each other.
dictConfig()
should be called very early in your program's execution. You can put it in your root __init__.py
file, or very early in the main.py
file (or whatever entrypoint you run). If your project has multiple entrypoints, you can put dictConfig()
below your if __name__ == "__main__"
, but imports may fire log messages that do not get caught before the logger is configured, or they may be missed entirely.
Finally, you can use a setup_logging()
function, which you can store in a module and import into whatever entrypoint script you target. This is a configurable and flexible way to manage logging.
dictConfig keys
The following options are available as dict
keys for your logging config dict
.
Key | Description |
---|---|
version | an integer indicating the schema version that is being used. If the logging configuration schema changes in the future, the version key will be used to indicate which version of the schema the dictConfig is using. This allows the dictConfig function to handle both current and future schema versions. |
formatters | a dictionary with each key being a formatter id and its value describing how to configure the corresponding Formatter instance. |
filters | a dictionary with each key being a filter id and its value describing how to configure the corresponding Filter instance. |
handlers | a dictionary with each key being a handler id and its value describing how to configure the corresponding Handler instance. All other keys are passed through as keyword arguments to the handler’s constructor. |
loggers | a dictionary with each key being a logger name and its value describing how to configure the corresponding Logger instance. |
root | the configuration for the root logger. It’s processed like any other logger, except that the propagate setting is not applicable. |
incremental | a boolean indicating whether the configuration specified in the dictionary should be merged with any existing configuration, or should replace entirely. Its default value is False , which means that the specified configuration replaces any existing configuration. |
disable_existing_loggers | a boolean indicating whether any non-root loggers that currently exist should be disabled. If absent, this parameter defaults to True . Its value is ignored when incremental is True . |
Logging config dict template
This logging_config_template
dict
can serve as a base/root for your project. Copy/paste the code and modify it to your needs, adding formatters, handlers, etc.
logging_config dict template | |
---|---|
Example logging config dict
This is an example of a fully configured logging
config dict. It includes a couple of console handlers, one which filters messages to only show CRITICAL
and above, a custom format, and a number of loggers.
Note
You only need to configure the root (""
) logger once; the configuration below configures te root logger in the "root"
dictionary key, as well as in "loggers": {"": {}}
. In a real logging configuration dict, you would only do one of these. I personally prefer putting my configuration in the top-level "root"
key.
Log Levels
Value | Int Level | Description |
---|---|---|
NOTSET |
0 |
It is the lowest level in the logging hierarchy and is used to indicate that no specific logging level has been set for a logger or a handler (more on handler later on in the article). It is essentially a placeholder level that is used when the logging level is not explicitly defined. |
DEBUG |
10 |
It is used for low-level debugging messages that provide detailed information about the code’s behavior. These messages are typically used during development and are not required in production. |
INFO |
20 |
It is used to log informational messages about the program’s behavior. These messages can be used to track the program’s progress or to provide context about the user. |
WARNING |
30 |
It is used to log messages that indicate potential issues or unexpected behavior in the program. These messages do not necessarily indicate an error but are useful for diagnosing problems. |
ERROR |
40 |
It is used to log messages that indicate an error has occurred in the program. These messages can be used to identify and diagnose problems in the code. |
CRITICAL |
50 |
It is used to log messages that indicate a critical error has occurred that prevents the program from functioning correctly. These messages should be used sparingly and only when necessary. |
Creating a custom log level
Example: VERBOSE
Links
Title | URL |
---|---|
Python docs: logging |
https://docs.python.org/3/library/logging.html |
Python docs: logging.config |
https://docs.python.org/3/library/logging.config.html |
Python docs: logging.config.dictConfig |
https://docs.python.org/3/library/logging.config.html#logging.config.dictConfig |
Python docs: LogRecord attributes (formatting) |
https://docs.python.org/3/library/logging.html#logrecord-attributes |
Python docs: time.strftime (timestamp formatting) |
https://docs.python.org/3/library/time.html#time.strftime |
Python docs: logging Handler objects |
https://docs.python.org/3/library/logging.html#handler-objects |