classification
Title: Allow basicConfig to configure any logger, not just root
Type: enhancement Stage: resolved
Components: Library (Lib) Versions: Python 3.11
process
Status: closed Resolution: rejected
Dependencies: Superseder:
Assigned To: Nosy List: gwerbin, vinay.sajip
Priority: normal Keywords: patch

Created on 2021-08-27 03:14 by gwerbin, last changed 2021-09-20 08:20 by vinay.sajip. This issue is now closed.

Pull Requests
URL Status Linked Edit
PR 28010 closed gwerbin, 2021-08-28 04:03
Messages (6)
msg400391 - (view) Author: Greg Werbin (gwerbin) * Date: 2021-08-27 03:14
Hello all!

I am proposing to add a "logger=" kwarg to logging.basicConfig(), which would cause the configuration to be applied to the specified logger. The value of this parameter could be a string or a logging.Logger object. Omitting logger= or passing logger=None would be equivalent to the current behavior, using the root logger.

My rationale for this proposal is that the Python logging can be verbose to configure for "simple" use cases, and can be intimidating for new users, especially those who don't have prior experience with comparable logging frameworks in other languages. The simplicity of basicConfig() is great, but currently there is a very big usability gap between the "root logger only" case and the "fully manual configuration" case. This enhancement proposal would help to fill that gap.

I observe that many Python users tend to use basicConfig() even when they would be better served by configuring only the logger(s) needed for their own app/library. And I think many of these same Python users would appreciate the reduced verbosity and greater convenience of having a "basic config" option that they could apply to various loggers independently.

I know that I personally would use this enhanced basicConfig() all the time, and I hope that others feel the same way. I also believe that it would encourage adoption of sensible logging setups in a greater number of projects.

Here are the Git diffs, as rendered by Github:

* CPython: https://github.com/python/cpython/compare/main...gwerbin:gwerbin/basicconfig-any-logger

* Mypy (typeshed): https://github.com/python/mypy/compare/master...gwerbin:gwerbin/basicconfig-any-logger
msg400944 - (view) Author: Vinay Sajip (vinay.sajip) * (Python committer) Date: 2021-09-02 18:59
Thanks, but ...

> I observe that many Python users tend to use basicConfig() even when they would be better served by configuring only the logger(s) needed for their own app/library.

First of all, library developers should not be configuring loggers at all, other than adding a NullHandler to their top-level logger.

Secondly, it is fine to configure the root logger and then log to any other logger. The default mechanisms mean that handlers added to the root logger are used by loggers which are anywhere in the logger hierarchy. So users that are using basicConfig() but other loggers than the root logger may be using the system as designed.

Thirdly, logging.config.dictConfig() allows usage of a dictionary to configure multiple loggers, handlers etc. all at once, for use cases which are more demanding than basicConfig(). It doesn't make sense to do what you're proposing, since it appears to offer a different way of configuring other loggers than the root logger, when dictConfig() already offers that.

So, I am minded to not agree to this change, and would prefer that the PR and this issue be closed.
msg401800 - (view) Author: Greg Werbin (gwerbin) * Date: 2021-09-14 21:22
Hi, thanks for the comment.

> First of all, library developers should not be configuring loggers at all, other than adding a NullHandler to their top-level logger.

This is true, and I agree. I am not expecting people to start using basicConfig() inside their libraries.

> Secondly, it is fine to configure the root logger and then log to any other logger. The default mechanisms mean that handlers added to the root logger are used by loggers which are anywhere in the logger hierarchy. So users that are using basicConfig() but other loggers than the root logger may be using the system as designed.

This change serves the purpose of making it easier to configure non-root loggers. Library developers often add logging to their libraries, expecting that end users will never see those log messages. However, if you configure the root logger in your application, you will see those log messages. This change is intended to let users more easily configure the specific loggers that they want to configure, without also getting a lot of extraneous output from loggers that they don't care about.

I have seen experienced Python developers misunderstand how this works, and go through all kinds of ugly contortions (setting filters on handlers, etc.) to avoid the boilerplate of instantiating Handler and Formatter objects.

> Thirdly, logging.config.dictConfig() allows usage of a dictionary to configure multiple loggers, handlers etc. all at once, for use cases which are more demanding than basicConfig(). It doesn't make sense to do what you're proposing, since it appears to offer a different way of configuring other loggers than the root logger, when dictConfig() already offers that.

Good point. I am proposing an alternative interface to the functionality provided by dictConfig().

I would frame this proposal as a third configuration option, with complexity somewhere between the current basicConfig() and dictConfig().

Also, using the kwarg means better support for IDE tab-completion and type checking, and another opportunity for discoverability in the docs.

On that note, it would be nice to adjust the type stubs for dictConfig() to use a TypedDict with types corresponding to the "dictionary configuration schema" (https://docs.python.org/3/library/logging.config.html#logging-config-dictschema) instead of Any (see https://github.com/python/typeshed/blob/ee48730/stdlib/logging/config.pyi#L22). But that's a separate change. I'm happy to make a PR to Typeshed for it, though!

Regardless of whether this change is accepted, I also think there should be a note about dictConfig() in the docs for basicConfig(): https://docs.python.org/3/library/logging.html#logging.basicConfig. I believe a lot of users simply don't know about the "low-boilerplate" options for logging configuration in Python, so they either don't do it right or don't do it at all and use print(). Both are bad outcomes. Again, I'm happy to make a separate PR for that change.
msg401942 - (view) Author: Vinay Sajip (vinay.sajip) * (Python committer) Date: 2021-09-16 13:18
> This change serves the purpose of making it easier to configure non-root loggers.

My point was, they don't need to configure non-root loggers (in terms of adding handlers) because the root logger's handlers will normally be used for all loggers.

> However, if you configure the root logger in your application, you will see those [other libraries'] log messages.

Users certainly don't need an API change to prevent that, should they wish to. The following simple call will suffice (using 'gnupg' just as an example):

logging.getLogger('gnupg').setLevel(logging.CRITICAL + 1)

> I would frame this proposal as a third configuration option

As you can see, it's not needed for the purpose you described (avoiding messages from third-party loggers).

> it would be nice to adjust the type stubs for dictConfig() to use a TypedDict ... I'm happy to make a PR to Typeshed for it, though!

Sure, sounds like a good idea.

> I also think there should be a note about dictConfig() in the docs for basicConfig() ... Again, I'm happy to make a separate PR for that change.

By all means. You could also look at updating the cookbook content, if you feel that parts relating to the concerns expressed in this issue could be expanded on.
msg402088 - (view) Author: Greg Werbin (gwerbin) * Date: 2021-09-17 20:25
> Users certainly don't need an API change to prevent that, should they wish to. The following simple call will suffice (using 'gnupg' just as an example):
>
> logging.getLogger('gnupg').setLevel(logging.CRITICAL + 1)

This selectively _disables_ a logger. That isn't the same thing as selectively _enabling_ only the loggers you want to enable.

Something like this would be equivalent to this proposal:

    logging.basicConfig(format='...', level=logging.CRITICAL + 1)
    logging.getLogger('my_app').setLevel(logging.INFO)

> My point was, they don't need to configure non-root loggers (in terms of adding handlers) because the root logger's handlers will normally be used for all loggers.

That's fair enough, but why make people do the above (which is somewhat unintuitive), when they could instead just do this:

    basicConfig(format='...'. level=logging.INFO, logger='my_app')

Moreover, what if you want multiple different formatters and handlers for different sub-loggers?

Sure, you could use dictConfig(), but why force people to do this:

    logging.dictConfig({
        'formatters': {
            'format1': {'format': '...'},
            'format2': {'format': '...'},
            'format3': {'format': '...'},
        },
        'handlers': 
            'handler1': {'class': 'logging.StreamHandler', 'formatter': 'format1'},
            'handler2': {'class': 'logging.StreamHandler', 'formatter': 'format2'},
            'handler3': {'class': 'logging.StreamHandler', 'formatter': 'format3'},
        },
        'loggers': {
            'my_app': {'level': 'WARNING', 'handlers': ['handler1']},
            'my_app.client': {'level': 'INFO', 'handlers': ['handler2']},
            'my_app.parser': {'level': 'INFO', 'handlers': ['handler3']},
        }
    })

When they could instead do this:

    basicConfig(format='...', level=logging.WARNING, logger='my_app')
    basicConfig(format='...', level=logging.INFO, logger='my_app.client')
    basicConfig(format='...', level=logging.INFO, logger='my_app.parser')

Not to mention the confusing bug-trap that is the `incremental` option in dictConfig().

> As you can see, it's not needed for the purpose you described (avoiding messages from third-party loggers).

Not "needed", of course. Plenty of people have been logging in Python for years without it!

But IMO it's a _better_ way to do it compared to the current solutions.

I believe that this proposal could even become the "one obvious way to do it": run `basicConfig(logger=my_logger)` on the top-level logger in your application.

My perspective is one of being an application developer, ad-hoc "script developer", and being active helper in many Python forums and chatrooms. I've seen ugly misuse of logging in serious application developed by serious organizations, by people who are not full-time Python devs, that would have been largely avoided by having an "easy" entry point like this. And I've seen countless newbies intimidated and scared away from logging "properly" in Python because of the complexity and non-obviousness of the existing interfaces.

I'm open to alternative suggestions of course. But I maintain that the current dictConfig() is not a suitable alternative, nor is selectively disabling loggers.

I am very aware of the high maintenance burden on the Python stdlib already, so I understand the resistance to adding new functionality. From my perspective, this is a very simple addition, can be tested with a small number of additional test assertions, and can be easily covered by in the type stubs.

I could also publish this as a separate package on PyPI. If you think this proposal places an undue burden on Python implementation developers, then I'll happily drop the PR and publish my own thing. But why reinvent the wheel, especially for something so small?
msg402211 - (view) Author: Vinay Sajip (vinay.sajip) * (Python committer) Date: 2021-09-20 08:20
> This selectively _disables_ a logger. That isn't the same thing as selectively _enabling_ only the loggers you want to enable.

Your earlier comment referred to getting unwanted messages from third-party loggers. Suppressing the messages implies disabling them in some way.

> That's fair enough, but why make people do the above (which is somewhat unintuitive)

That's your opinion, but it doesn't seem that this view is widespread. We provide basicConfig() for simple configuration and dictConfig() for more elaborate requirements; dictConfig() was reviewed by Python developers in PEP 391 and accepted.

> I could also publish this as a separate package on PyPI

By all means, do that. As I've already said, I've no wish to add yet another way of configuring logging. If your PyPI package gains wide adoption, we can always revisit this.
History
Date User Action Args
2021-09-20 08:20:02vinay.sajipsetstatus: open -> closed
resolution: rejected
messages: + msg402211

stage: patch review -> resolved
2021-09-17 20:25:20gwerbinsetmessages: + msg402088
2021-09-16 13:18:33vinay.sajipsetmessages: + msg401942
2021-09-14 21:22:45gwerbinsetmessages: + msg401800
2021-09-02 18:59:52vinay.sajipsetnosy: + vinay.sajip
messages: + msg400944
2021-08-28 04:03:14gwerbinsetkeywords: + patch
stage: patch review
pull_requests: + pull_request26452
2021-08-27 03:14:24gwerbincreate