This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: logging.config.dictConfig does not work with callable filters
Type: enhancement Stage: resolved
Components: Versions: Python 3.11
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: Nosy List: PetrPy111, lkollar, lorb, mariocj89, miss-islington, raybb, vinay.sajip
Priority: normal Keywords: patch

Created on 2020-10-02 00:43 by raybb, last changed 2022-04-11 14:59 by admin. This issue is now closed.

Pull Requests
URL Status Linked Edit
PR 30756 merged mariocj89, 2022-01-21 17:22
Messages (11)
msg377791 - (view) Author: (raybb) Date: 2020-10-02 00:43
According to the docs here (https://docs.python.org/3/library/logging.html):

"You don’t need to create specialized Filter classes, or use other classes with a filter method: you can use a function (or other callable) as a filter. The filtering logic will check to see if the filter object has a filter attribute: if it does, it’s assumed to be a Filter and its filter() method is called. Otherwise, it’s assumed to be a callable and called with the record as the single parameter."


If I use this code:

def noErrorLogs(param):
    return 1 if param.levelno < 40 else 0

logconfig_dict = {
    'filters': {
        'myfilter': {
            '()': noErrorLogs,
        }
    },
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "level": "DEBUG",
            "stream": "ext://sys.stdout",
            "filters": ["myfilter"]
        }
    },
    "root": {"level": "DEBUG", "handlers": ["console"]},
    "version": 1,
}
dictConfig(logconfig_dict)

I get the error "Unable to configure filter 'myfilter'" because "noErrorLogs() missing 1 required positional argument: 'param'"


However, If I use this code:


def noErrorLogs(param):
    return 1 if param.levelno < 40 else 0

logconfig_dict = {
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "level": "DEBUG",
            "stream": "ext://sys.stdout",
        }
    },
    "root": {"level": "DEBUG", "handlers": ["console"]},
    "version": 1,
}

logging.basicConfig()
dictConfig(logconfig_dict)
l = logging.getLogger()
l.handlers[0].addFilter(noErrorLogs)

Then the filter works correctly.




The bug I am reporting is that when using logging.config.dictConfig you cannot pass in a callable that acts a filter. You can only pass in a class with a filter method or a function that returns a callable filter.

If this is the expected behavior perhaps the docs should make it clear that callables cannot be used with dictConfig.
msg377800 - (view) Author: Vinay Sajip (vinay.sajip) * (Python committer) Date: 2020-10-02 07:19
The value of the '()' key should be a factory - something that returns a filter (either something that has a filter method or a callable). That returned callable, or the filter method, will be called with a LogRecord and its return value used to determine whether the event should be processed. An example of using factories is here:

https://docs.python.org/3/library/logging.config.html#user-defined-objects

In your first example, you're passing the filter where you need to pass the factory. If you change it to e.g. return a lambda that does the filtering, it should work as expected.

At the moment you can't directly pass a filter callable in the config dictionary - only a factory that returns a filter. I'll look at this as a possible enhancement.
msg377813 - (view) Author: (raybb) Date: 2020-10-02 14:55
Thank you for the clarification.

I think I was most confused by the docs on this page (which I should have included in my initial post): https://docs.python.org/3/howto/logging.html

It says: 

"In Python 3.2, a new means of configuring logging has been introduced, using dictionaries to hold configuration information. This provides a superset of the functionality of the config-file-based approach outlined above, and is the recommended configuration method for new applications and deployments."


Since it is the recommended configuration method I had naively assumed that it would be compatible with the feature of just using callables instead which was mentioned here https://docs.python.org/3/library/logging.html. 


I think it would be a nice enhancement long term to be able too support callables in dictconfigs.

In the short term do you think it is reasonable to update the first page mentioned in this comment to clarify that even though dictConfig is recommended it is not a feature parity with the object oriented api?

It would also be nice to clarify this on the second page to say that callables don't work if using dictConfig.


I understand it does say a factory is required on the page you linked. I think anyone who reads the docs as well as they should will find it. But I think adding information about this too other parts of the docs will make it much easier for newer folks to catch the difference early on.


Thanks for your work on the logging parts of the api in python. I've read many of your docs and they are very helpful.
msg377831 - (view) Author: Vinay Sajip (vinay.sajip) * (Python committer) Date: 2020-10-02 19:43
You make reasonable points. I won't close this issue, and get to those updates when I can/as time allows. Meanwhile, if you or someone else proposes specific changes by way of a pull request, I'll look at that too. And thanks for your kind words about the logging docs.
msg379211 - (view) Author: lorb (lorb) * Date: 2020-10-21 14:41
I looked into implementing this and it's not entirely clear how it could work. The main issue I encountered is how to distinguish between a callable that is a factory that takes one argument and a callable that is a filter.

One possible approach I see is to rely on the absence of any other keys in the configuration sub-directory. Not entirely happy with that (and have to think how to treat factory with optional parameter vs filter with optional parameter).
So this would be assumed to be a filter, if inspection reveals that fltr takes an argument:
    'myfilter': {
        '()': fltr,
    }
while this would be assumed to be a factory:
    'myfilter': {
        '()': fctry,
        'bar': 'baz',
    }

A different approach that I think would be not optimal is to just call the callable with a dummy LogRecord passed ind and see if it returns a boolean or callable (or throws).
msg389549 - (view) Author: Mario Corchero (mariocj89) * (Python triager) Date: 2021-03-26 13:59
Vinay would you consider a patch for logging where dictConfig allows taking objects directly in addition to the reference id?

That would allow the following:

```

def noErrorLogs(param):
    return 1 if param.levelno < 40 else 0

logconfig_dict = {
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "level": "DEBUG",
            "stream": "ext://sys.stdout",
            "filters": [noErrorLogs]
        }
    },
    "root": {"level": "DEBUG", "handlers": ["console"]},
    "version": 1,
}
dictConfig(logconfig_dict)
```

or alternatively passing them on declaration:

```
logconfig_dict = {
    'filters': {
        'myfilter': noErrorLogs,
    },
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "level": "DEBUG",
            "stream": "ext://sys.stdout",
            "filters": ["myfilter"]
        }
    },
    "root": {"level": "DEBUG", "handlers": ["console"]},
    "version": 1,
}
dictConfig(logconfig_dict)
```

I'm happy to put a patch together if that looks good to you.
msg410979 - (view) Author: Petr (PetrPy111) Date: 2022-01-19 20:43
I would definitely vote for implementing this enhancement. I have just ran into the very same issue and my search ended here. Using dictConfig e.g. with lambdas seems very natural to me and I understood the docs incorrectly exactly as had been reported.
msg410993 - (view) Author: Vinay Sajip (vinay.sajip) * (Python committer) Date: 2022-01-20 00:07
> Vinay would you consider a patch for logging where dictConfig allows taking objects directly in addition to the reference id?

Sorry I didn't respond to this; it dropped off my radar. Certainly, I would consider such a patch.
msg411013 - (view) Author: Mario Corchero (mariocj89) * (Python triager) Date: 2022-01-20 09:03
Great! I'll put something together then. If you have any preference about the implementation or any pointer on the way you think should be done, please let me know.
msg411019 - (view) Author: Vinay Sajip (vinay.sajip) * (Python committer) Date: 2022-01-20 11:59
> If you have any preference about the implementation or any pointer

Nothing particular, just try to fit in with the existing code conventions even where they don't follow modern idioms (e.g. a lot of the code in this package predates PEP 8 and new styles of string formatting).
msg411470 - (view) Author: miss-islington (miss-islington) Date: 2022-01-24 12:40
New changeset d7c68639795a576ff58b6479c8bb34c113df3618 by Mario Corchero in branch 'main':
bpo-41906: Accept built filters in dictConfig (GH-30756)
https://github.com/python/cpython/commit/d7c68639795a576ff58b6479c8bb34c113df3618
History
Date User Action Args
2022-04-11 14:59:36adminsetgithub: 86072
2022-01-24 12:42:07vinay.sajipsetstatus: open -> closed
stage: patch review -> resolved
resolution: fixed
versions: + Python 3.11, - Python 3.10
2022-01-24 12:40:01miss-islingtonsetnosy: + miss-islington
messages: + msg411470
2022-01-21 17:22:27mariocj89setkeywords: + patch
stage: patch review
pull_requests: + pull_request28943
2022-01-20 11:59:04vinay.sajipsetmessages: + msg411019
2022-01-20 09:03:52mariocj89setmessages: + msg411013
2022-01-20 00:07:54vinay.sajipsetmessages: + msg410993
2022-01-19 20:43:16PetrPy111setnosy: + PetrPy111
messages: + msg410979
2021-03-29 18:33:22lkollarsetnosy: + lkollar
2021-03-26 13:59:35mariocj89setnosy: + mariocj89
messages: + msg389549
2020-10-21 14:41:57lorbsetnosy: + lorb
messages: + msg379211
2020-10-02 19:43:02vinay.sajipsetmessages: + msg377831
2020-10-02 14:55:50raybbsetmessages: + msg377813
2020-10-02 07:19:22vinay.sajipsettype: behavior -> enhancement
messages: + msg377800
versions: + Python 3.10
2020-10-02 01:56:15xtreaksetnosy: + vinay.sajip
2020-10-02 00:43:26raybbcreate