Logging
Logging
*************
Author:
Vinay Sajip <vinay_sajip at red-dove dot com>
+---------------------------------------+----------------------------------------+
| Task you want to perform | The best tool for the task |
|=======================================|========================================|
| Display console output for ordinary | "print()" |
| usage of a command line script or | |
| program | |
+---------------------------------------+----------------------------------------+
| Report events that occur during | "logging.info()" (or "logging.debug()" |
| normal operation of a program (e.g. | for very detailed output for |
| for status monitoring or fault | diagnostic purposes) |
| investigation) | |
+---------------------------------------+----------------------------------------+
| Issue a warning regarding a | "warnings.warn()" in library code if |
| particular runtime event | the issue is avoidable and the client |
| | application should be modified to |
| | eliminate the warning |
| | "logging.warning()" if there is |
| | nothing the client application can do |
| | about the situation, but the event |
| | should still be noted |
+---------------------------------------+----------------------------------------+
| Report an error regarding a | Raise an exception |
| particular runtime event | |
+---------------------------------------+----------------------------------------+
| Report suppression of an error | "logging.error()", |
| without raising an exception (e.g. | "logging.exception()" or |
| error handler in a long-running | "logging.critical()" as appropriate |
| server process) | for the specific error and application |
| | domain |
+---------------------------------------+----------------------------------------+
The logging functions are named after the level or severity of the
events they are used to track. The standard levels and their
applicability are described below (in increasing order of severity):
+----------------+-----------------------------------------------+
| Level | When it's used |
|================|===============================================|
| "DEBUG" | Detailed information, typically of interest |
| | only when diagnosing problems. |
+----------------+-----------------------------------------------+
| "INFO" | Confirmation that things are working as |
| | expected. |
+----------------+-----------------------------------------------+
| "WARNING" | An indication that something unexpected |
| | happened, or indicative of some problem in |
| | the near future (e.g. 'disk space low'). The |
| | software is still working as expected. |
+----------------+-----------------------------------------------+
| "ERROR" | Due to a more serious problem, the software |
| | has not been able to perform some function. |
+----------------+-----------------------------------------------+
| "CRITICAL" | A serious error, indicating that the program |
| | itself may be unable to continue running. |
+----------------+-----------------------------------------------+
The default level is "WARNING", which means that only events of this
level and above will be tracked, unless the logging package is
configured to do otherwise.
Events that are tracked can be handled in different ways. The simplest
way of handling tracked events is to print them to the console.
Another common way is to write them to a disk file.
A simple example
----------------
import logging
logging.warning('Watch out!') # will print a message to the console
logging.info('I told you so') # will not print anything
If you type these lines into a script and run it, you'll see:
WARNING:root:Watch out!
printed out on the console. The "INFO" message doesn't appear because
the default level is "WARNING". The printed message includes the
indication of the level and the description of the event provided in
the logging call, i.e. 'Watch out!'. Don't worry about the 'root' part
for now: it will be explained later. The actual output can be
formatted quite flexibly if you need that; formatting options will
also be explained later.
Logging to a file
-----------------
import logging
logging.basicConfig(filename='example.log', encoding='utf-8',
level=logging.DEBUG)
logging.debug('This message should go to the log file')
logging.info('So should this')
logging.warning('And this, too')
logging.error('And non-ASCII stuff, too, like Øresund and Malmö')
And now if we open the file and look at what we have, we should find
the log messages:
This example also shows how you can set the logging level which acts
as the threshold for tracking. In this case, because we set the
threshold to "DEBUG", all of the messages were printed.
If you want to set the logging level from a command-line option such
as:
--log=INFO
and you have the value of the parameter passed for "--log" in some
variable *loglevel*, you can use:
getattr(logging, loglevel.upper())
to get the value which you'll pass to "basicConfig()" via the *level*
argument. You may want to error check any user input value, perhaps as
in the following example:
If you run the above script several times, the messages from
successive runs are appended to the file *example.log*. If you want
each run to start afresh, not remembering the messages from earlier
runs, you can specify the *filemode* argument, by changing the call in
the above example to:
The output will be the same as before, but the log file is no longer
appended to, so the messages from earlier runs are lost.
# myapp.py
import logging
import mylib
def main():
logging.basicConfig(filename='myapp.log', level=logging.INFO)
logging.info('Started')
mylib.do_something()
logging.info('Finished')
if __name__ == '__main__':
main()
# mylib.py
import logging
def do_something():
logging.info('Doing something')
INFO:root:Started
INFO:root:Doing something
INFO:root:Finished
which is hopefully what you were expecting to see. You can generalize
this to multiple modules, using the pattern in *mylib.py*. Note that
for this simple usage pattern, you won't know, by looking in the log
file, *where* in your application your messages came from, apart from
looking at the event description. If you want to track the location of
your messages, you'll need to refer to the documentation beyond the
tutorial level -- see Advanced Logging Tutorial.
import logging
logging.warning('%s before you %s', 'Look', 'leap!')
will display:
As you can see, merging of variable data into the event description
message uses the old, %-style of string formatting. This is for
backwards compatibility: the logging package pre-dates newer
formatting options such as "str.format()" and "string.Template". These
newer formatting options *are* supported, but exploring them is
outside the scope of this tutorial: see Using particular formatting
styles throughout your application for more information.
import logging
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
logging.debug('This message should appear on the console')
logging.info('So should this')
logging.warning('And this, too')
import logging
logging.basicConfig(format='%(asctime)s %(message)s')
logging.warning('is when this event was logged.')
import logging
logging.basicConfig(format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S
%p')
logging.warning('is when this event was logged.')
Next Steps
----------
If your logging needs are simple, then use the above examples to
incorporate logging into your own scripts, and if you run into
problems or don't understand something, please post a question on the
comp.lang.python Usenet group (available at
https://groups.google.com/g/comp.lang.python) and you should receive
help before too long.
Still here? You can carry on reading the next few sections, which
provide a slightly more advanced/in-depth tutorial than the basic one
above. After that, you can take a look at the Logging Cookbook.
logger = logging.getLogger(__name__)
This means that logger names track the package/module hierarchy, and
it's intuitively obvious where events are logged just from the logger
name.
The root of the hierarchy of loggers is called the root logger. That's
the logger used by the functions "debug()", "info()", "warning()",
"error()" and "critical()", which just call the same-named method of
the root logger. The functions and the methods have the same
signatures. The root logger's name is printed as 'root' in the logged
output.
severity:logger name:message
Logging Flow
------------
[image]
Loggers
-------
The most widely used methods on logger objects fall into two
categories: configuration and message sending.
You don't need to always call these methods on every logger you
create. See the last two paragraphs in this section.
With the logger object configured, the following methods create log
messages:
Handlers
--------
The standard library includes quite a few handler types (see Useful
Handlers); the tutorials use mainly "StreamHandler" and "FileHandler"
in its examples.
Formatters
----------
%Y-%m-%d %H:%M:%S
The following message format string will log the time in a human-
readable format, the severity of the message, and the contents of the
message, in that order:
Configuring Logging
-------------------
import logging
# create logger
logger = logging.getLogger('simple_example')
logger.setLevel(logging.DEBUG)
# create formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %
(message)s')
# add formatter to ch
ch.setFormatter(formatter)
# add ch to logger
logger.addHandler(ch)
# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')
Running this module from the command line produces the following
output:
$ python simple_logging_module.py
2005-03-19 15:10:26,618 - simple_example - DEBUG - debug message
2005-03-19 15:10:26,620 - simple_example - INFO - info message
2005-03-19 15:10:26,695 - simple_example - WARNING - warn message
2005-03-19 15:10:26,697 - simple_example - ERROR - error message
2005-03-19 15:10:26,773 - simple_example - CRITICAL - critical message
import logging
import logging.config
logging.config.fileConfig('logging.conf')
# create logger
logger = logging.getLogger('simpleExample')
# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')
[loggers]
keys=root,simpleExample
[handlers]
keys=consoleHandler
[formatters]
keys=simpleFormatter
[logger_root]
level=DEBUG
handlers=consoleHandler
[logger_simpleExample]
level=DEBUG
handlers=consoleHandler
qualname=simpleExample
propagate=0
[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)
[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
$ python simple_logging_config.py
2005-03-19 15:38:55,977 - simpleExample - DEBUG - debug message
2005-03-19 15:38:55,979 - simpleExample - INFO - info message
2005-03-19 15:38:56,054 - simpleExample - WARNING - warn message
2005-03-19 15:38:56,055 - simpleExample - ERROR - error message
2005-03-19 15:38:56,130 - simpleExample - CRITICAL - critical message
You can see that the config file approach has a few advantages over
the Python code approach, mainly separation of configuration and code
and the ability of noncoders to easily modify the logging properties.
Warning:
Note that the class names referenced in config files need to be either
relative to the logging module, or absolute values which can be
resolved using normal import mechanisms. Thus, you could use either
"WatchedFileHandler" (relative to the logging module) or
"mypackage.mymodule.MyHandler" (for a class defined in package
"mypackage" and module "mymodule", where "mypackage" is available on
the Python import path).
version: 1
formatters:
simple:
format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
handlers:
console:
class: logging.StreamHandler
level: DEBUG
formatter: simple
stream: ext://sys.stdout
loggers:
simpleExample:
level: DEBUG
handlers: [console]
propagate: no
root:
level: DEBUG
handlers: [console]
When developing a library which uses logging, you should take care to
document how the library uses logging - for example, the names of
loggers used. Some consideration also needs to be given to its logging
configuration. If the using application does not use logging, and
library code makes logging calls, then (as described in the previous
section) events of severity "WARNING" and greater will be printed to
"sys.stderr". This is regarded as the best default behaviour.
If for some reason you *don't* want these messages printed in the
absence of any logging configuration, you can attach a do-nothing
handler to the top-level logger for your library. This avoids the
message being printed, since a handler will always be found for the
library's events: it just doesn't produce any output. If the library
user configures logging for application use, presumably that
configuration will add some handlers, and if levels are suitably
configured then logging calls made in library code will send output to
those handlers, as normal.
Note:
It is strongly advised that you *do not log to the root logger* in
your library. Instead, use a logger with a unique and easily
identifiable name, such as the "__name__" for your library's top-
level package or module. Logging to the root logger will make it
difficult or impossible for the application developer to configure
the logging verbosity or handlers of your library as they wish.
Note:
It is strongly advised that you *do not add any handlers other than*
"NullHandler" *to your library's loggers*. This is because the
configuration of handlers is the prerogative of the application
developer who uses your library. The application developer knows
their target audience and what handlers are most appropriate for
their application: if you add handlers 'under the hood', you might
well interfere with their ability to carry out unit tests and
deliver logs which suit their requirements.
Logging Levels
==============
The numeric values of logging levels are given in the following table.
These are primarily of interest if you want to define your own levels,
and need them to have specific values relative to the predefined
levels. If you define a level with the same numeric value, it
overwrites the predefined value; the predefined name is lost.
+----------------+-----------------+
| Level | Numeric value |
|================|=================|
| "CRITICAL" | 50 |
+----------------+-----------------+
| "ERROR" | 40 |
+----------------+-----------------+
| "WARNING" | 30 |
+----------------+-----------------+
| "INFO" | 20 |
+----------------+-----------------+
| "DEBUG" | 10 |
+----------------+-----------------+
| "NOTSET" | 0 |
+----------------+-----------------+
Levels can also be associated with loggers, being set either by the
developer or through loading a saved logging configuration. When a
logging method is called on a logger, the logger compares its own
level with the level associated with the method call. If the logger's
level is higher than the method call's, no logging message is actually
generated. This is the basic mechanism controlling the verbosity of
logging output.
Just as for loggers, handlers can have levels associated with them. A
handler's level acts as a filter in the same way as a logger's level
does. If a handler decides to actually dispatch an event, the "emit()"
method is used to send the message to its destination. Most user-
defined subclasses of "Handler" will need to override this "emit()".
Custom Levels
-------------
Useful Handlers
===============
13. "WatchedFileHandler" instances watch the file they are logging to.
If the file changes, it is closed and reopened using the file
name. This handler is only useful on Unix-like systems; Windows
does not support the underlying mechanism used.
Note:
In the preceding sections and examples, it has been assumed that the
message passed when logging the event is a string. However, this is
not the only possibility. You can pass an arbitrary object as a
message, and its "__str__()" method will be called when the logging
system needs to convert it to a string representation. In fact, if you
want to, you can avoid computing a string representation altogether -
for example, the "SocketHandler" emits an event by pickling it and
sending it over the wire.
Optimization
============
if logger.isEnabledFor(logging.DEBUG):
logger.debug('Message with %s, %s', expensive_func1(),
expensive_func2())
Note:
+-------------------------------------------------------
+-----------------------------------------------------+
| What you don't want to collect | How to avoid collecting
it |
|=======================================================|
=====================================================|
| Information about where calls were made from. | Set "logging._srcfile" to
"None". This avoids |
| | calling
"sys._getframe()", which may help to speed |
| | up your code in
environments like PyPy (which can't |
| | speed up code that uses
"sys._getframe()"). |
+-------------------------------------------------------
+-----------------------------------------------------+
| Threading information. | Set "logging.logThreads"
to "False". |
+-------------------------------------------------------
+-----------------------------------------------------+
| Current process ID ("os.getpid()") | Set
"logging.logProcesses" to "False". |
+-------------------------------------------------------
+-----------------------------------------------------+
| Current process name when using "multiprocessing" to | Set
"logging.logMultiprocessing" to "False". |
| manage multiple processes. |
|
+-------------------------------------------------------
+-----------------------------------------------------+
| Current "asyncio.Task" name when using "asyncio". | Set
"logging.logAsyncioTasks" to "False". |
+-------------------------------------------------------
+-----------------------------------------------------+
Also note that the core logging module only includes the basic
handlers. If you don't import "logging.handlers" and "logging.config",
they won't take up any memory.
Other resources
===============
See also:
Module "logging"
API reference for the logging module.
Module "logging.config"
Configuration API for the logging module.
Module "logging.handlers"
Useful handlers included with the logging module.
A logging cookbook