Why doesn't filter attached to the root logger propagate to descendant loggers?

Viewed 4564

From paragraph 15.7.4 of the python logging documentation:

Note that filters attached to handlers are consulted whenever an event is emitted by the handler, whereas filters attached to loggers are consulted whenever an event is logged to the handler (using debug(), info(), etc.) This means that events which have been generated by descendant loggers will not be filtered by a logger’s filter setting, unless the filter has also been applied to those descendant loggers.

I don't understand this design decision. Would it not make more sense for the root logger's filter to be applied to the descendant loggers as well?

4 Answers

Filters, unlike levels and handlers, do not propagate. However, you can attach it to a handler which will propagate.

Check out this example by Elliot at SaltyCrane Blog:

import logging

class MyFilter(logging.Filter):
    def filter(self, record):
        record.msg = 'MY FILTER: ' + record.msg
        return 1

myfilter = MyFilter()

myformatter = logging.Formatter("MY HANDLER: %(name)s - %(message)s")

myhandler = logging.StreamHandler()
myhandler.setFormatter(myformatter)
myhandler.addFilter(myfilter)

foo_logger = logging.getLogger('foo')
foo_logger.addHandler(myhandler)

foo_bar_logger = logging.getLogger('foo.bar')

foo_logger.error('asdfasdf')
foo_bar_logger.error('zxcvzxcv')
MY HANDLER: foo - MY FILTER: asdfasdf
MY HANDLER: foo.bar - MY FILTER: zxcvzxcv

Analog to MiniQuark here is an imho elegant way for monkey patching (embeded in a context manager):

from typing import Iterable, Iterator
from contextlib import contextmanager


@contextmanager
def apply_filter_for_new_handlers(filters: Iterable[logging.Filter]) -> Iterator[None]:
    originial_addHandler = logging.Logger.addHandler

    def monkey_patched_addHandler(self: logging.Logger, hdlr: logging.Handler) -> None: # noqa:C0103
        for log_filter in filters:
            hdlr.addFilter(log_filter)
        return originial_addHandler(self, hdlr)

    logging.Logger.addHandler = monkey_patched_addHandler
    yield

    logging.Logger.addHandler = originial_addHandler


class MyRootFilter(logging.Filter):
   [...] # same as above


with apply_filter_for_new_handlers(filters=[MyRootFilter]):
    logging.getLogger("some.sub.project").error("hello") 
Related