Skip to content

The first attempt to hande the format property during logging.Formatter initilization removes the . dictionary from the config #110875

Closed
@he-dev

Description

@he-dev

Bug report

Bug description:

When the library tries to initialize a formatter and comes across the old format property, it falls back to an error handler, but before it does that, it pops the . dicationary from the config making it impossible to process it later during the second call to self.configure_custom(config)

https://github.com/python/cpython/blob/main/Lib/logging/config.py#L480

This is whrere configure_custom calls props = config.pop('.', None), but it does that before result = c(**kwargs) which throws an exception when it finds the format property.

    def configure_custom(self, config):
        """Configure an object with a user-supplied factory."""
        c = config.pop('()')
        if not callable(c):
            c = self.resolve(c)
        props = config.pop('.', None) # <-- '.' gets removed on first try and is gone on the second attempt
        # Check for valid identifiers
        kwargs = {k: config[k] for k in config if valid_ident(k)}
        result = c(**kwargs) # <-- throws an error when `format` property is used
        # props = config.pop('.', None) # <-- this is where it probably needs to get called so that '.' remains in 'config'
        if props:
            for name, value in props.items():
                setattr(result, name, value)
        return result

Then then initialization continues here inside the except that calls configure_custom for the second time, but this time without the . in the config so it's skipped.

https://github.com/python/cpython/blob/main/Lib/logging/config.py#L670

    def configure_formatter(self, config):
        """Configure a formatter from a dictionary."""
        if '()' in config:
            factory = config['()'] # for use in exception handler
            try:
                result = self.configure_custom(config)
            except TypeError as te:
                if "'format'" not in str(te):
                    raise
                #Name of parameter changed from fmt to format.
                #Retry with old name.
                #This is so that code can be used with older Python versions
                #(e.g. by Django)
                config['fmt'] = config.pop('format')
                config['()'] = factory
                result = self.configure_custom(config)

I guess the function configure_custom should call props = config.pop('.', None) after result = c(**kwargs) so that the . remains in the config for the second call in case an exception is thrown during the first try.

Example

This config won't initialize the custom_property of MyFormatter:

            "custom_formatter": {
                "()": MyFormatter,
                "style": "{",
                "datefmt": "%Y-%m-%d %H:%M:%S",
                "format": "<custom-format>", # <-- when removed or changed to `fmt` then the '.' works
                ".": {
                    "custom_property": "value"
                }
            }

The formatter is implemented like this:

class MyFormatter(logging.Formatter):
    custom_property: str = "."

    def format(self, record: logging.LogRecord) -> str:
        # ...
        return super().format(record)

CPython versions tested on:

3.10

Operating systems tested on:

Windows

Linked PRs

Metadata

Metadata

Assignees

Labels

stdlibPython modules in the Lib dirtype-bugAn unexpected behavior, bug, or error

Projects

Status

Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions