Skip to content

Documented pattern for adding contextual information to LogRecord objects fails type checking #7833

Closed
@LHCGreg

Description

@LHCGreg

https://docs.python.org/3/library/logging.html#logrecord-objects gives the following pattern for adding contextual information to LogRecord objects which can then be used in log formats:

old_factory = logging.getLogRecordFactory()

def record_factory(*args, **kwargs):
    record = old_factory(*args, **kwargs)
    record.custom_attribute = 0xdecafbad
    return record

logging.setLogRecordFactory(record_factory)

However, if you use this pattern and run mypy, you'll get an error like error: "LogRecord" has no attribute "custom_attribute". You can get around this error by using setattr(record, "custom_attribute", 0xdecafbad) instead of record.custom_attribute = 0xdecafbad.

Should the stub for the LogRecord class define a __setattr__ method because this sort of usage of assigning to arbitrary attributes is expected for the LogRecord class? Would that have undesirable side effects?

Here is a complete example:

import logging

custom_contextual_data = "XYZ"


def main() -> None:
    logging.basicConfig(
        format="[%(asctime)s] [%(custom_contextual_data)s] [%(name)s] %(levelname)s: %(message)s", level=logging.DEBUG
    )

    old_factory = logging.getLogRecordFactory()

    def record_factory(*args: object, **kwargs: object) -> logging.LogRecord:
        global custom_contextual_data
        record = old_factory(*args, **kwargs)

        # mypy: error: "LogRecord" has no attribute "custom_contextual_data"
        record.custom_contextual_data = custom_contextual_data

        # mypy: no error
        # setattr(record, "custom_contextual_data", custom_contextual_data)

        return record

    logging.setLogRecordFactory(record_factory)

    logger = logging.getLogger(__name__)
    logger.info("Hello XYZ world!")
    global custom_contextual_data
    custom_contextual_data = "ABC"
    logger.info("Hello ABC world!")


if __name__ == "__main__":
    main()

mypy.ini:

[mypy]
files = mypy_log_record_repro/
strict_equality = True
disallow_untyped_defs = True
disallow_untyped_calls = True
disallow_untyped_decorators = True
no_implicit_optional = True
strict_optional = True
warn_unused_ignores = True
warn_redundant_casts = True
warn_no_return = True
warn_return_any = True
warn_unreachable = True

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions