Skip to content

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

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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