Skip to content

caplog.get_records and caplog.clear conflict #9877

Closed

Description

@sanderr

Description

caplog.get_records() gets decoupled from actual caplog records when caplog.clear() is called. As a result, after caplog.clear() is called, caplog.get_records() is frozen: it does not get cleared, nor does it get new records.

During test set up it is set to the same list as caplog.records, but the latter gets replaced rather than cleared in caplog.clear(), which diverges the two objects.

Reproductive example

import logging

def test(caplog) -> None:
    def verify_consistency() -> None:
        assert caplog.get_records("call") == caplog.records

    verify_consistency()
    logging.warning("test")
    verify_consistency()
    caplog.clear()
    verify_consistency()  # fails: assert [<LogRecord: ...y, 8, "test">] == []

Environment details

Arch Linux, Python 3.9.10:

Package    Version
---------- -------
attrs      21.4.0
iniconfig  1.1.1
packaging  21.3
pip        22.0.4
pluggy     1.0.0
py         1.11.0
pyparsing  3.0.8
pytest     7.1.1
setuptools 60.10.0
tomli      2.0.1
wheel      0.37.1

Activity

  1. added
    type: bugproblem that needs to be addressed
    plugin: capturerelated to the capture builtin plugin
    on Apr 21, 2022
  2. added a commit that references this issue on Jun 15, 2022
    d1d9971
  3. EmptyRabbit commented on Jun 15, 2022

    @EmptyRabbit
    Contributor

    I would like to fix this issue

    It seems that when setup/call/teardown stage begin, will init a stage log records stash, which used in LogCaptureFixture.get_records,
    but after LogCaptureFixture.clear, the stage records in stash not be reset

    I tried to fix with the following changed

    # _pytest/logging.py
    def _runtest_for(self, item: nodes.Item, when: str) -> Generator[None, None, None]:
        """Implement the internals of the pytest_runtest_xxx() hooks."""
        with catching_logs(
                self.caplog_handler,
                level=self.log_level,
        ) as caplog_handler, catching_logs(
            self.report_handler,
            level=self.log_level,
        ) as report_handler:
            caplog_handler.reset()
            caplog_handler.set_when(when)  # Add "when" property to LogCaptureHandler, set stage in runtest
            report_handler.reset()
            item.stash[caplog_records_key][when] = caplog_handler.records
            item.stash[caplog_handler_key] = caplog_handler
    
            yield
    
            log = report_handler.stream.getvalue().strip()
            item.add_report_section(when, "log", log)
    # _pytest/logging.py
    def clear(self) -> None:
        """Reset the list of log records and the captured log text."""
        self.handler.reset()
        self._item.stash[caplog_records_key][self.handler.when] = self.records    # consistent with records after clear
  4. added 4 commits that reference this issue on Jun 15, 2022
    78caa0f
    c7be7bd
    17775a5
    b0b313f
  5. sanderr commented on Jun 17, 2022

    @sanderr
    Author

    It's not really clear to me why you added the set_when line, but that's probably just my inexperience with this codebase. The other line you added seems to address the issue I raised, though I am wondering if it might not be simpler to just add a layer of indirection: instead of storing List[logging.LogRecord], store LogCaptureHandler, then dynamically fetch the records when requested. It would have the advantage that the state is always consistent, and nothing needs to be rebound on clear. Again, I'm really not familiar with this code base so I might be missing something.

  6. EmptyRabbit commented on Jun 20, 2022

    @EmptyRabbit
    Contributor

    My understanding is StashKey[Dict[str, List[logging.LogRecord]]]() is used to storage caplog record in different stage(setup/call/teardown), but when caplog.clear() in stage, LogCaptureFixture can't know which stage caplog to clear. So I added set_when to fix it.

    Can you share more details about the suggestion "instead of storing List[logging.LogRecord], store LogCaptureHandler "?

  7. sanderr commented on Jun 20, 2022

    @sanderr
    Author

    I see, thanks for the explanation, that makes sense.

    While typing out a clarification of what I meant with my suggestion I realized I was missing something and it makes little sense. You can safely ignore that part.

  8. added a commit that references this issue on Jun 24, 2022
    c7e784f
  9. EmptyRabbit commented on Jul 5, 2022

    @EmptyRabbit
    Contributor

    Update root cause and fix

    caplog_handler.reset() set LogCaptureHandler.records a new list object, which lost bind to item.stash

    As each running stage use one caplog_handler object and it's records object reference in item.stash item.stash[caplog_records_key][when] = caplog_handler.records. So after clear, any log records can not be captured to item.stash

    Use self.records.clear() instead of self.records = [] when caplog.clear() to fix it

  10. added 2 commits that reference this issue on Apr 18, 2026
    4ab8f38
    028f141
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

    plugin: capturerelated to the capture builtin plugintype: bugproblem that needs to be addressed

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions