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:
Intercept log records emitted during test phases (setup, call, teardown).
Store and expose captured logs to tests via the
caplogfixture.Format log output with timestamps, colors, and multiline indentation.
Support live logging (real-time streaming of logs to the terminal).
Write logs to files with configurable formats and levels.
Provide context managers and APIs for dynamic log level control and filtering.
Integrate captured logs seamlessly into pytest's test reports.
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
DEFAULT_LOG_FORMAT
Default log format string:"%(levelname)-8s %(name)s:%(filename)s:%(lineno)d %(message)s"DEFAULT_LOG_DATE_FORMAT
Default log date/time format:"%H:%M:%S"_ANSI_ESCAPE_SEQ
Compiled regex to match ANSI escape sequences in text (used to strip color codes)._remove_ansi_escape_sequences(text: str) -> str
Utility function to remove ANSI escape sequences from giventext.
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:**
formatTime(record: LogRecord, datefmt: str | None = None) -> str
Returns formatted time string with microsecond precision if requested.
**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:**
LOGLEVEL_COLOROPTS: Mapping from log level (e.g.logging.ERROR) to set of ANSI color options (e.g.{"red", "bold"}).LEVELNAME_FMT_REGEX: Regex to find the levelname format specifier in the log format string.
**Key Methods:**
__init__(terminalwriter: TerminalWriter, *args, **kwargs)
Initializes the formatter and precomputes colored formats for each level.add_color_level(level: int, *color_opts: str) -> None
Add or update color options for a specific log level.format(record: logging.LogRecord) -> str
Format a log record with color applied to the level name.
**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:**
_auto_indent: Controls indentation behavior:0= off-1= auto-detect indentation>0= fixed indentation level
**Key Methods:**
_get_auto_indent(auto_indent_option: int | str | bool | None) -> int
Parses user configuration to determine auto-indent behavior.format(record: logging.LogRecord) -> str
If the message has multiple lines and auto-indent is enabled, formats subsequent lines with indentation.
**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:
--log-levelto control log capture level.--log-formatfor log message format.--log-cli-levelfor live logging.--log-fileto specify log file path.--log-disableto disable specific loggers.
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:**
emit(record: logging.LogRecord) -> None
Stores theLogRecordand writes the formatted log to the internal buffer.reset() -> None
Clears stored records and resets the internal stream.clear() -> None
Clears stored records and resets the internal stream (similar to reset).handleError(record: logging.LogRecord) -> None
Raises exceptions on logging errors to fail tests if logging goes wrong.
Class: LogCaptureFixture
**Purpose:** Provides pytest tests with an interface to access and control captured logs during test execution.
**Key Properties:**
handler: LogCaptureHandler
The active log capture handler.records: list[logging.LogRecord]
List of captured log records.text: str
Formatted log output as a string without ANSI colors.record_tuples: list[tuple[str, int, str]]
Simplified tuples(logger_name, level, message)useful for assertions.messages: list[str]
List of fully formatted log messages (without timestamps or levels).
**Key Methods:**
get_records(when: Literal["setup", "call", "teardown"]) -> list[logging.LogRecord]
Get records for a specific test phase.clear() -> None
Clear captured logs and reset buffers.set_level(level: int | str, logger: str | None = None) -> None
Temporarily set a logger level during a test.at_level(level: int | str, logger: str | None = None) -> Generator[None]
Context manager to temporarily set logger level.filtering(filter_: logging.Filter) -> Generator[None]
Context manager to temporarily add a logging filter during a test.
**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:**
Initializes logging handlers and formatters based on pytest configuration.
Registers handlers to capture logs during all test phases.
Supports live logging, file logging, and colored output.
Integrates with pytest hooks (
pytest_sessionstart,pytest_runtest_setup, etc.) to start/stop log capture.Adds captured logs as sections in test reports.
Provides methods for configuring file logging paths and disabling specific loggers.
**Notable Methods:**
__init__(config: Config)
Setup handlers, formatters, and log levels based on config._create_formatter(log_format, log_date_format, auto_indent)
Returns a formatter instance — colored or plain with multiline style._disable_loggers(loggers_to_disable: list[str])
Disables specified loggers._log_cli_enabled() -> bool
Determines if live CLI logging is enabled.set_log_path(fname: str) -> None
Sets the file path for file logging, creating directories if needed.Pytest hooks to manage capturing during test lifecycle phases, e.g.,
pytest_runtest_setup,pytest_runtest_call,pytest_runtest_teardown,pytest_sessionstart, etc.Helper method
_runtest_for(item: nodes.Item, when: str)manages capturing logs per test phase.
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:**
Disables capturing (
stdout/stderr) temporarily during emit to ensure live logs appear immediately.Handles spacing and section display for setup/call/teardown phases.
Resets state on new test.
Class: _LiveLoggingNullHandler(logging.NullHandler)
A no-op handler used when live logging is disabled.
Important Implementation Details & Algorithms
Multiline Message Indentation:
PercentStyleMultilineuses a configurable auto-indent mechanism to format multiline log messages cleanly, either auto-detecting indentation or using a fixed number of spaces.Colorizing Level Names:
ColoredLevelFormatterinjects ANSI color escape sequences into the%(levelname)spart of the log format by searching and replacing the formatting string for each log level.Hierarchical Log Level Management:
LogCaptureFixture.set_levelsaves initial levels and disables globallogging.disabledif needed to ensure the requested level is captured, restoring all states after the test.Integration With Pytest Hooks:
LoggingPluginuses generator-basedpytesthooks (which wrap test phases) to start and stop capturing handlers cleanly, isolating logs per phase for rich reporting.Thread-Safe Stream Handling:
The file usesStringIOstreams to capture log output in memory, avoiding file I/O or concurrency issues.
Interaction with Other Parts of the System
Pytest Plugin System:
Registered inpytest_configurehook as"logging-plugin", this plugin manages logging capture alongside other pytest plugins.Test Nodes and Reports:
Captured logs are stored in the test node’s stash (caplog_handler_key,caplog_records_key) and added as report sections, making logs visible in test reports.Terminal Output and Capture Manager:
Works withTerminalReporterandCaptureManagerplugins to coordinate live logging and suspend/resume capturing ofstdoutandstderr.Configuration System:
Reads CLI options and ini-file settings to configure log levels, formats, files, colorization, and live logging behavior.Fixtures:
Thecaplogfixture exposes logs to test functions, enabling assertions and log inspection.
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:
Wrapping Python’s logging with custom formatters and handlers.
Providing the
caplogfixture for easy log inspection.Supporting live and file logging with user-configurable options.
Integrating tightly with pytest’s lifecycle and reporting.
Offering advanced features like colorized levels and multiline message indentation.
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.