logging.py


Overview

The `logging.py` file is a core component of pytest responsible for **capturing, controlling, formatting, and displaying logging output** generated during test execution. It integrates tightly with Python's standard `logging` module and pytest’s plugin and fixture system to:

This file implements custom logging handlers, formatters, context managers, and the `LoggingPlugin` which manages the entire log capture lifecycle in pytest.


Detailed Explanation of Classes, Functions, and Methods

Constants and Utilities


Class: DatetimeFormatter(logging.Formatter)

**Purpose:** A logging formatter subclass that uses [datetime.datetime.strftime](/projects/286/67343) for formatting timestamps if the format string includes microseconds (`%f`). Otherwise, it falls back to default formatting.

**Key Method:**

**Usage Example:**

formatter = DatetimeFormatter("%Y-%m-%d %H:%M:%S.%f", "%Y-%m-%d %H:%M:%S.%f")
formatted_time = formatter.formatTime(log_record)

Class: ColoredLevelFormatter(DatetimeFormatter)

**Purpose:** Extends `DatetimeFormatter` to colorize the `%(levelname)s` portion of log messages when outputting to a terminal, enhancing readability.

**Important Properties:**

**Key Methods:**

**Usage Example:**

tw = TerminalWriter()
formatter = ColoredLevelFormatter(tw, DEFAULT_LOG_FORMAT, DEFAULT_LOG_DATE_FORMAT)
log_msg = formatter.format(log_record)

Class: PercentStyleMultiline(logging.PercentStyle)

**Purpose:** Custom logging style that supports *auto-indenting* of multiline log messages, formatting each line as if logged separately.

**Key Attributes:**

**Key Methods:**

**Usage Example:**

style = PercentStyleMultiline("%(levelname)s: %(message)s", auto_indent="on")
formatted = style.format(log_record)

Function: get_option_ini(config: Config, *names: str)

Returns the first non-empty value found among command line options or ini settings specified by `names`.


Function: pytest_addoption(parser: Parser) -> None

Pytest hook function that adds command-line options and ini-file settings related to logging configuration to pytest’s CLI parser.

Examples of options added:


Class: catching_logs(Generic[_HandlerType])

**Purpose:** Context manager to temporarily attach a logging handler (e.g., for capturing logs) to the root logger at a specified level, and restore the original state on exit.

**Usage Example:**

handler = LogCaptureHandler()
with catching_logs(handler, level=logging.INFO):
    logger.info("Captured log message")
# handler is removed and log level restored here

Class: LogCaptureHandler(logging.StreamHandler)

**Purpose:** Logging handler that captures all log records into an internal list and stores formatted log text in a `StringIO` stream.

**Key Methods:**


Class: LogCaptureFixture

**Purpose:** Provides pytest tests with an interface to access and control captured logs during test execution.

**Key Properties:**

**Key Methods:**

**Usage Example:**

def test_logging_example(caplog):
    logger = logging.getLogger("myapp")
    logger.info("Start")

    # Access captured log messages
    assert "Start" in caplog.text

    # Temporarily increase log level
    with caplog.at_level(logging.DEBUG):
        logger.debug("Debug message")
        assert "Debug message" in caplog.text

Fixture: caplog

Pytest fixture providing a `LogCaptureFixture` instance bound to the currently running test node. It yields the fixture and finalizes (restores log levels) after the test finishes.


Function: get_log_level_for_setting(config: Config, *setting_names: str) -> int | None

Fetches a log level from pytest options or ini settings, normalizes it to an integer logging level, and raises `UsageError` if invalid.


Class: LoggingPlugin

**Purpose:** The main pytest plugin managing the lifecycle of logging capture during test runs.

**Key Responsibilities:**

**Notable Methods:**


Class: _FileHandler(logging.FileHandler)

A thin subclass of `logging.FileHandler` customized for pytest that overrides `handleError` to silence logging errors (handled upstream).


Class: _LiveLoggingStreamHandler(logging.StreamHandler)

**Purpose:** Handler that streams live logs to the terminal during test execution, writing a newline before the first message in each test phase, and manages terminal section headers.

**Key Features:**


Class: _LiveLoggingNullHandler(logging.NullHandler)

A no-op handler used when live logging is disabled.


Important Implementation Details & Algorithms


Interaction with Other Parts of the System


Usage Examples

Accessing captured logs in tests via caplog

def test_example(caplog):
    logger = logging.getLogger("myapp")
    logger.info("Hello")
    logger.error("Something failed")

    # Access messages
    assert "Hello" in caplog.text
    assert ("myapp", logging.ERROR, "Something failed") in caplog.record_tuples

Temporarily changing log level

def test_debug_logs(caplog):
    with caplog.at_level("DEBUG"):
        logging.getLogger("myapp").debug("Debug message")
        assert "Debug message" in caplog.text

Using log filtering

class MyFilter(logging.Filter):
    def filter(self, record):
        return "ignore" not in record.getMessage()

def test_filtering(caplog):
    caplog.handler.addFilter(MyFilter())
    logging.info("This will be captured")
    logging.info("ignore this message")
    assert "ignore" not in caplog.text

Mermaid Diagram: Class Structure of logging.py

classDiagram
    class DatetimeFormatter {
        +formatTime(record: LogRecord, datefmt: str | None) str
    }
    class ColoredLevelFormatter {
        -LOGLEVEL_COLOROPTS: Mapping[int, Set[str]]
        -LEVELNAME_FMT_REGEX: Pattern
        +__init__(terminalwriter: TerminalWriter, *args, **kwargs)
        +add_color_level(level: int, *color_opts: str)
        +format(record: LogRecord) str
    }
    class PercentStyleMultiline {
        +__init__(fmt: str, auto_indent: int | str | bool | None)
        +format(record: LogRecord) str
        -_get_auto_indent(auto_indent_option) int
    }
    class catching_logs {
        +__init__(handler: logging.Handler, level: int | None)
        +__enter__() logging.Handler
        +__exit__(exc_type, exc_val, exc_tb)
    }
    class LogCaptureHandler {
        +records: list[LogRecord]
        +emit(record: LogRecord)
        +reset()
        +clear()
        +handleError(record: LogRecord)
    }
    class LogCaptureFixture {
        +handler: LogCaptureHandler
        +records: list[LogRecord]
        +text: str
        +record_tuples: list[tuple[str, int, str]]
        +messages: list[str]
        +get_records(when: Literal)
        +clear()
        +set_level(level: int | str, logger: str | None)
        +at_level(level: int | str, logger: str | None)
        +filtering(filter_: logging.Filter)
    }
    class LoggingPlugin {
        -_config: Config
        -formatter: logging.Formatter
        -caplog_handler: LogCaptureHandler
        -report_handler: LogCaptureHandler
        -log_file_handler: _FileHandler
        -log_cli_handler: _LiveLoggingStreamHandler | _LiveLoggingNullHandler
        +__init__(config: Config)
        +_create_formatter(log_format, log_date_format, auto_indent)
        +set_log_path(fname: str)
        +_log_cli_enabled() bool
        +pytest_sessionstart()
        +pytest_collection()
        +pytest_runtestloop(session: Session)
        +pytest_runtest_setup(item: nodes.Item)
        +pytest_runtest_call(item: nodes.Item)
        +pytest_runtest_teardown(item: nodes.Item)
        +pytest_sessionfinish()
        +pytest_unconfigure()
    }
    class _FileHandler {
        +handleError(record: LogRecord)
    }
    class _LiveLoggingStreamHandler {
        +reset()
        +set_when(when: str | None)
        +emit(record: LogRecord)
        +handleError(record: LogRecord)
    }
    class _LiveLoggingNullHandler {
        +reset()
        +set_when(when: str)
        +handleError(record: LogRecord)
    }

    ColoredLevelFormatter --|> DatetimeFormatter
    PercentStyleMultiline --|> logging.PercentStyle
    LogCaptureHandler --|> logging.StreamHandler
    _FileHandler --|> logging.FileHandler
    _LiveLoggingStreamHandler --|> logging.StreamHandler
    _LiveLoggingNullHandler --|> logging.NullHandler

Summary

The `logging.py` file delivers a sophisticated logging capture and control framework for pytest tests by:

This module is essential for users who need detailed logging control and visibility during automated testing, enabling debugging, assertions on logs, and clean integration into test reports.


End of Documentation for logging.py