[3.11] [doc] Update logging cookbook with an example of custom handli… (GH-98296) by vsajip · Pull Request #98296 · python/cpython
.. _custom-level-handling:
Custom handling of levels -------------------------
Sometimes, you might want to do something slightly different from the standard handling of levels in handlers, where all levels above a threshold get processed by a handler. To do this, you need to use filters. Let's look at a scenario where you want to arrange things as follows:
* Send messages of severity ``INFO`` and ``WARNING`` to ``sys.stdout`` * Send messages of severity ``ERROR`` and above to ``sys.stderr`` * Send messages of severity ``DEBUG`` and above to file ``app.log``
Suppose you configure logging with the following JSON:
.. code-block:: json
{ "version": 1, "disable_existing_loggers": false, "formatters": { "simple": { "format": "%(levelname)-8s - %(message)s" } }, "handlers": { "stdout": { "class": "logging.StreamHandler", "level": "INFO", "formatter": "simple", "stream": "ext://sys.stdout", }, "stderr": { "class": "logging.StreamHandler", "level": "ERROR", "formatter": "simple", "stream": "ext://sys.stderr" }, "file": { "class": "logging.FileHandler", "formatter": "simple", "filename": "app.log", "mode": "w" } }, "root": { "level": "DEBUG", "handlers": [ "stderr", "stdout", "file" ] } }
This configuration does *almost* what we want, except that ``sys.stdout`` would show messages of severity ``ERROR`` and above as well as ``INFO`` and ``WARNING`` messages. To prevent this, we can set up a filter which excludes those messages and add it to the relevant handler. This can be configured by adding a ``filters`` section parallel to ``formatters`` and ``handlers``:
.. code-block:: json
"filters": { "warnings_and_below": { "()" : "__main__.filter_maker", "level": "WARNING" } }
and changing the section on the ``stdout`` handler to add it:
.. code-block:: json
"stdout": { "class": "logging.StreamHandler", "level": "INFO", "formatter": "simple", "stream": "ext://sys.stdout", "filters": ["warnings_and_below"] }
A filter is just a function, so we can define the ``filter_maker`` (a factory function) as follows:
.. code-block:: python
def filter_maker(level): level = getattr(logging, level)
def filter(record): return record.levelno <= level
return filter
This converts the string argument passed in to a numeric level, and returns a function which only returns ``True`` if the level of the passed in record is at or below the specified level. Note that in this example I have defined the ``filter_maker`` in a test script ``main.py`` that I run from the command line, so its module will be ``__main__`` - hence the ``__main__.filter_maker`` in the filter configuration. You will need to change that if you define it in a different module.
With the filter added, we can run ``main.py``, which in full is:
.. code-block:: python
import json import logging import logging.config
CONFIG = ''' { "version": 1, "disable_existing_loggers": false, "formatters": { "simple": { "format": "%(levelname)-8s - %(message)s" } }, "filters": { "warnings_and_below": { "()" : "__main__.filter_maker", "level": "WARNING" } }, "handlers": { "stdout": { "class": "logging.StreamHandler", "level": "INFO", "formatter": "simple", "stream": "ext://sys.stdout", "filters": ["warnings_and_below"] }, "stderr": { "class": "logging.StreamHandler", "level": "ERROR", "formatter": "simple", "stream": "ext://sys.stderr" }, "file": { "class": "logging.FileHandler", "formatter": "simple", "filename": "app.log", "mode": "w" } }, "root": { "level": "DEBUG", "handlers": [ "stderr", "stdout", "file" ] } } '''
def filter_maker(level): level = getattr(logging, level)
def filter(record): return record.levelno <= level
return filter
logging.config.dictConfig(json.loads(CONFIG)) logging.debug('A DEBUG message') logging.info('An INFO message') logging.warning('A WARNING message') logging.error('An ERROR message') logging.critical('A CRITICAL message')
And after running it like this:
.. code-block:: shell
python main.py 2>stderr.log >stdout.log
We can see the results are as expected:
.. code-block:: shell
$ more *.log :::::::::::::: app.log :::::::::::::: DEBUG - A DEBUG message INFO - An INFO message WARNING - A WARNING message ERROR - An ERROR message CRITICAL - A CRITICAL message :::::::::::::: stderr.log :::::::::::::: ERROR - An ERROR message CRITICAL - A CRITICAL message :::::::::::::: stdout.log :::::::::::::: INFO - An INFO message WARNING - A WARNING message
Configuration server example ----------------------------
Of course, these above examples show output according to the format used by Of course, the examples above show output according to the format used by :func:`~logging.basicConfig`, but you can use a different formatter when you configure logging.
Opening the same log file multiple times ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Adding handlers other than :class:`NullHandler` to a logger in a library ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Creating a lot of loggers ^^^^^^^^^^^^^^^^^^^^^^^^^