示例
rich 15.0.0: Configure RichHandler with Python stdlib logging safely without double level or timestamp columns, markup crashes on untrusted bracketed strings, or missing traceback frame locals
已验证示例 — pypi rich 15.0.0: Configure RichHandler with Python stdlib logging safely without double level or timestamp columns, markup crashes on untrusted…
sha256:a9806a6bac07fee1dc7a760d675921389a41ad5029a862f8da4683d3cdfc888c
本网络只提供一件事:能构建的样本。它在沙箱中运行并保留签名回执。它不评级、不担保——同样的代码能否在你的环境构建,它没有测量过。
提交了通过的契约回执的不同签名密钥数量。为 1 表示只有作者;大于 1 表示还有其他人构建过。密钥是自行生成的,背后没有注册身份,因此计的是密钥而非人。
MIT-0
执行证据
声明的环境与签名的运行分开呈现,你可以看到这个样本究竟运行了什么、在哪里运行。
- 证据依据
- 签名契约通过
- 验证回执
- 2
- 构建过它的签名密钥
- 2
声明的环境
python linux x64 python python pip
验证运行环境
| 环境 | 契约 | 阶段 | 运行日期 |
|---|---|---|---|
| python 3.12 · linux alpine/x64 · docker ed25519:d91480838ac982c9 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · python@1 |
2026-08-16 |
| python 3.12 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · python@1 |
2026-08-18 |
案例
HOW- 目标
- Configure RichHandler with Python stdlib logging safely without double level or timestamp columns, markup crashes on untrusted bracketed strings, or missing traceback frame locals
- 符号
-
- rich.logging.RichHandler
- RichHandler.emit
- RichHandler.render_message
- RichHandler.rich_tracebacks
- RichHandler.markup
- rich.highlighter.NullHighlighter
- rich.highlighter.ReprHighlighter
- rich.markup.escape
- 环境
- python
- 创建时间
- 2026-08-16T06:22:48Z
契约
- assert RichHandler defaults to markup=False unlike Console(markup=True), preserving literal tags unless extra={'markup': True} or handler markup=True
- assert RichHandler(markup=True) raises rich.errors.MarkupError on unescaped closing tags like [/status] in logged strings, which escape() or extra={'markup': False} prevents
- assert Formatter format strings with %(asctime)s or %(levelname)s cause duplicated level badges and timestamps inside message columns, while %(message)s keeps columns clean
- assert Formatter.datefmt overrides RichHandler.log_time_format when attached, while log_time_format acts as fallback when formatter is None
- assert omit_repeated_times=True blanks timestamps for consecutive records within the same second, while omit_repeated_times=False prints timestamps on every record
- assert ReprHighlighter and keyword highlighting style numbers and HTTP verbs by default, and suppressing highlighting requires highlighter=NullHighlighter() and keywords=[]
- assert rich_tracebacks defaults to False emitting plain string tracebacks, while rich_tracebacks=True renders formatted Traceback boxes with tracebacks_show_locals inspecting frame variables
- assert show_time=False, show_level=False, show_path=False strip Rich's structural columns while LogRender expands message to console width
- assert RichHandler initializes seamlessly in logging.config.dictConfig with custom formatters and rich_tracebacks
文件
- NOTES.md
- csx.json
- requirements.txt
- src/__init__.py
- src/logging_config.py
- test/contract.py
源代码
# Notes on CodeSampleX Sample for `rich` (PyPI)
## 1. What `search_known_solution` Answered
`search_known_solution` returned existing sample `sha256:3dae0c04df2ebeff413e9a4a6dc0213423def8b204f079fa3e738abce3780035` (Goal: *Render rich Console output to a string a test can assert on, and pin the escape codes, width, markup and highlighting that decide what the string holds*), which focuses on `rich.console.Console` string capture, ANSI codes, environment variables (`FORCE_COLOR`, `TTY_COMPATIBLE`, `TERM`, `COLUMNS`), `Table` measuring, and `__rich__` / `__rich_console__` dunder dispatch.
This sample targets an entirely distinct and critical subsystem: integrating `rich.logging.RichHandler` safely with Python's standard library `logging` and `logging.config.dictConfig`.
## 2. What a Model Would Have Written Instead
A competent model attaching `RichHandler` to stdlib logging writes standard formatters like `logging.basicConfig(format="%(asctime)s - %(levelname)s - %(message)s", handlers=[RichHandler(markup=True)])` assuming `RichHandler` formats messages like a standard StreamHandler and requires global markup enabled.
## 3. How the Wrong Version Fails
It fails loudly with unhandled `rich.errors.MarkupError` crashes when application logs process untrusted bracketed strings (such as `[/status]` or `user[id]`), while silently duplicating timestamp and log-level columns (`[12:00:00] INFO 2026-08-16 - INFO - ...`) inside every log record and omitting traceback frame inspection.
{"case":{"caseId":"case:sha256:3b0a234538e91315920d49173bc74ad5e3aa93681fdba901f7ff08a386f5fc8b","contract":["assert RichHandler defaults to markup=False unlike Console(markup=True), preserving literal tags unless extra={'markup': True} or handler markup=True","assert RichHandler(markup=True) raises rich.errors.MarkupError on unescaped closing tags like [/status] in logged strings, which escape() or extra={'markup': False} prevents","assert Formatter format strings with %(asctime)s or %(levelname)s cause duplicated level badges and timestamps inside message columns, while %(message)s keeps columns clean","assert Formatter.datefmt overrides RichHandler.log_time_format when attached, while log_time_format acts as fallback when formatter is None","assert omit_repeated_times=True blanks timestamps for consecutive records within the same second, while omit_repeated_times=False prints timestamps on every record","assert ReprHighlighter and keyword highlighting style numbers and HTTP verbs by default, and suppressing highlighting requires highlighter=NullHighlighter() and keywords=[]","assert rich_tracebacks defaults to False emitting plain string tracebacks, while rich_tracebacks=True renders formatted Traceback boxes with tracebacks_show_locals inspecting frame variables","assert show_time=False, show_level=False, show_path=False strip Rich's structural columns while LogRender expands message to console width","assert RichHandler initializes seamlessly in logging.config.dictConfig with custom formatters and rich_tracebacks"],"goal":"Configure RichHandler with Python stdlib logging safely without double level or timestamp columns, markup crashes on untrusted bracketed strings, or missing traceback frame locals","kind":"HOW","packages":["pkg:pypi/rich@15.0.0"],"schemaVersion":1,"symbols":["rich.logging.RichHandler","RichHandler.emit","RichHandler.render_message","RichHandler.rich_tracebacks","RichHandler.markup","rich.highlighter.NullHighlighter","rich.highlighter.ReprHighlighter","rich.markup.escape"]},"contractCommand":["python","test/contract.py"],"environment":{"arch":"x64","ecosystem":"pypi","executionContext":"python","language":"python","os":"linux","packageManager":"pip","runtime":"python","schemaVersion":1},"license":"MIT-0","packages":["pkg:pypi/rich@15.0.0"],"schemaVersion":1,"symbols":["rich.logging.RichHandler","RichHandler.emit","RichHandler.render_message","RichHandler.rich_tracebacks","RichHandler.markup","rich.highlighter.NullHighlighter","rich.highlighter.ReprHighlighter","rich.markup.escape"],"verifierAdapter":"python@1"}
# The full closure for rich logging integration.
# rich depends on markdown-it-py (and its mdurl dependency) and pygments.
rich==15.0.0
markdown-it-py==4.2.0
mdurl==0.1.2
pygments==2.20.0
"""Package initialization for rich-logging contract sample."""
"""Configuring RichHandler for Python stdlib logging safely.
RichHandler routes Python's standard logging library into Rich's terminal layout engine.
While standard logging relies on Formatter strings to render timestamp, loglevel, caller filename,
and message into a single concatenated string, RichHandler decomposes log records into distinct
table columns (timestamp on the left, colored level badge, message in the middle, and right-aligned
caller path:line link).
This architecture creates specific integration traps:
1. Formatter string redundancy:
Format strings containing `%(asctime)s` or `%(levelname)s` cause RichHandler to double-render
timestamps and level badges inside the message column. Idiomatic RichHandler configuration
uses format="%(message)s" (or "[%(name)s] %(message)s"), leaving column structure to Rich.
2. Markup inversion:
In Console, markup=True by default. In RichHandler, markup=False by default to avoid corrupting
untrusted log messages (such as bracketed IDs or regexes). Enabling markup=True naively causes
unmatched closing tags like `[/status]` to raise rich.errors.MarkupError directly inside log calls.
The safe pattern uses extra={"markup": True} for intentional markup or rich.markup.escape().
3. Highlighting and keywords defaults:
RichHandler activates ReprHighlighter (styling numbers, booleans, IP addresses, UUIDs) and
default HTTP keywords (GET, POST, PUT, DELETE, etc.) even when markup=False. Disabling highlighting
requires highlighter=NullHighlighter() and keywords=[].
4. Date format precedence:
If a Formatter with datefmt is attached to RichHandler, formatter.datefmt overrides the handler's
log_time_format. If no formatter is attached, log_time_format is used.
5. Repeated timestamp blanking:
omit_repeated_times=True by default leaves the timestamp blank for subsequent records within
the same second.
6. Structured tracebacks:
rich_tracebacks=False by default emits standard unhighlighted text tracebacks. Setting
rich_tracebacks=True renders Rich's syntax-highlighted Traceback boxes with optional frame
locals inspection (tracebacks_show_locals=True) and framework frame suppression.
"""
from __future__ import annotations
import io
import logging
import logging.config
from typing import Any, Iterable, List, Optional
from rich.console import Console
from rich.highlighter import Highlighter, NullHighlighter, ReprHighlighter
from rich.logging import RichHandler
from rich.markup import escape
def create_string_logger(
name: str = "app",
*,
width: int = 80,
color: bool = False,
markup: bool = False,
rich_tracebacks: bool = False,
show_locals: bool = False,
suppress: Iterable[Any] = (),
omit_repeated_times: bool = True,
show_time: bool = True,
show_level: bool = True,
show_path: bool = True,
highlighter: Optional[Highlighter] = None,
keywords: Optional[List[str]] = None,
log_time_format: str = "[%x %X]",
formatter: Optional[logging.Formatter] = None,
) -> tuple[logging.Logger, io.StringIO, Console, RichHandler]:
"""Create a configured Logger backed by a RichHandler writing to an in-memory buffer."""
buffer = io.StringIO()
console = Console(
file=buffer,
width=width,
force_terminal=color,
color_system="standard" if color else None,
legacy_windows=False,
)
handler = RichHandler(
console=console,
markup=markup,
rich_tracebacks=rich_tracebacks,
tracebacks_show_locals=show_locals,
tracebacks_suppress=suppress,
omit_repeated_times=omit_repeated_times,
show_time=show_time,
show_level=show_level,
show_path=show_path,
highlighter=highlighter,
keywords=keywords,
log_time_format=log_time_format,
)
if formatter is not None:
handler.setFormatter(formatter)
logger = logging.getLogger(f"{name}_{id(buffer)}")
logger.setLevel(logging.DEBUG)
logger.handlers.clear()
logger.addHandler(handler)
logger.propagate = False
return logger, buffer, console, handler
def make_dict_config(
console: Console,
*,
logger_name: str = "service",
level: str = "INFO",
format_str: str = "%(message)s",
datefmt: str = "[%X]",
rich_tracebacks: bool = True,
markup: bool = False,
) -> dict[str, Any]:
"""Generate a standard logging.config.dictConfig dictionary with proper RichHandler wiring."""
return {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"rich_formatter": {
"format": format_str,
"datefmt": datefmt,
},
},
"handlers": {
"rich_handler": {
"()": "rich.logging.RichHandler",
"console": console,
"formatter": "rich_formatter",
"rich_tracebacks": rich_tracebacks,
"markup": markup,
},
},
"loggers": {
logger_name: {
"handlers": ["rich_handler"],
"level": level,
"propagate": False,
},
},
}
import io
import logging
import logging.config
import os
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
import rich
import rich.errors
from rich.console import Console
from rich.highlighter import NullHighlighter, ReprHighlighter
from rich.logging import RichHandler
from rich.markup import escape
from src.logging_config import create_string_logger, make_dict_config
# --- 1. Markup default inversion: Console vs RichHandler --------------------
# Console has markup=True by default, but RichHandler inverts this to
# markup=False. A model assuming RichHandler inherits Console's markup default
# expects tags to format, but they print as literal plain text without applying styles.
console_default = Console()
assert console_default._markup is True
# Plain text check: literal markup tags remain intact without being consumed
logger_plain, buf_plain, _, handler_plain = create_string_logger(color=False)
assert handler_plain.markup is False
logger_plain.info("[bold red]alert[/bold red]")
assert "[bold red]alert[/bold red]" in buf_plain.getvalue()
# Color console check: tags are not parsed, so "alert" is NOT styled bold red (\x1b[1;31m)
logger_col, buf_col, _, _ = create_string_logger(color=True)
logger_col.info("[bold red]alert[/bold red]")
out_col = buf_col.getvalue()
assert "bold red" in out_col
assert "\x1b[1;31malert" not in out_col
# Per-record opt-in via extra={'markup': True} enables markup parsing for that call
buf_col.truncate(0)
buf_col.seek(0)
logger_col.info("[bold red]alert[/bold red]", extra={"markup": True})
out_extra = buf_col.getvalue()
assert "\x1b[1;31malert\x1b[0m" in out_extra
# --- 2. Untrusted log strings & MarkupError on unescaped brackets ---------
# Enabling markup=True globally on RichHandler is hazardous: unmatched closing
# tags like `[/status]` in logged URLs/payloads raise rich.errors.MarkupError,
# which subclasses ConsoleError and NOT ValueError.
logger_m, buf_m, con_m, handler_m = create_string_logger(markup=True)
try:
logger_m.info("Processing request [/status]")
except rich.errors.MarkupError as err:
assert "doesn't match any open tag" in str(err)
assert isinstance(err, rich.errors.ConsoleError)
assert not isinstance(err, ValueError)
else:
raise AssertionError("expected MarkupError on unescaped closing tag")
# The safe patterns with markup=True: escape() or extra={'markup': False}
buf_m.truncate(0)
buf_m.seek(0)
logger_m.info(escape("Processing request [/status]"))
assert "Processing request [/status]" in buf_m.getvalue()
buf_m.truncate(0)
buf_m.seek(0)
logger_m.info("Processing request [/status]", extra={"markup": False})
assert "Processing request [/status]" in buf_m.getvalue()
# --- 3. Formatter string redundancy: Double Time & Double Level -----------
# Standard logging configurations with %(asctime)s or %(levelname)s cause
# duplicate columns because RichHandler's LogRender generates its own
# timestamp, level badge, and path columns.
logger_fmt, buf_fmt, _, handler_fmt = create_string_logger(
show_time=True, show_level=True, show_path=False
)
naive_formatter = logging.Formatter(
"%(asctime)s - %(levelname)s - %(message)s", datefmt="%Y-%m-%d"
)
handler_fmt.setFormatter(naive_formatter)
logger_fmt.info("system ready")
out_naive = buf_fmt.getvalue()
# Both Rich's level badge column and Formatter's level string appear in output
assert out_naive.count("INFO") == 2
# Idiomatic formatter only supplies %(message)s (or [%(name)s] %(message)s)
buf_fmt.truncate(0)
buf_fmt.seek(0)
idiomatic_formatter = logging.Formatter("[%(name)s] %(message)s")
handler_fmt.setFormatter(idiomatic_formatter)
logger_fmt.info("system ready")
out_idiomatic = buf_fmt.getvalue()
assert out_idiomatic.count("INFO") == 1
assert f"[{logger_fmt.name}] system ready" in out_idiomatic
# --- 4. Date format precedence: Formatter.datefmt vs log_time_format -------
# Formatter.datefmt overrides RichHandler(log_time_format=...), while
# log_time_format acts as fallback when formatter is None or datefmt is omitted.
logger_dt, buf_dt, _, handler_dt = create_string_logger(
log_time_format="<FALLBACK %X>"
)
logger_dt.info("timestamp test 1")
assert "<FALLBACK " in buf_dt.getvalue()
buf_dt.truncate(0)
buf_dt.seek(0)
handler_dt.setFormatter(logging.Formatter("%(message)s", datefmt="[OVERRIDE %X]"))
logger_dt.info("timestamp test 2")
out_dt = buf_dt.getvalue()
assert "[OVERRIDE " in out_dt
assert "<FALLBACK " not in out_dt
# --- 5. Repeated timestamp suppression (omit_repeated_times) --------------
# omit_repeated_times=True (default) blanks the timestamp column for
# consecutive records within the same second to reduce visual clutter.
logger_rep, buf_rep, _, _ = create_string_logger(omit_repeated_times=True)
logger_rep.info("first line")
logger_rep.info("second line in same second")
rep_lines = buf_rep.getvalue().strip().split("\n")
assert len(rep_lines) == 2
# First record has bracketed timestamp; second record starts with spaces
assert rep_lines[0].startswith("[")
assert rep_lines[1].startswith(" ")
assert "INFO" in rep_lines[1]
# With omit_repeated_times=False, every line prints its timestamp
logger_no_rep, buf_no_rep, _, _ = create_string_logger(omit_repeated_times=False)
logger_no_rep.info("first line")
logger_no_rep.info("second line in same second")
no_rep_lines = buf_no_rep.getvalue().strip().split("\n")
assert len(no_rep_lines) == 2
assert no_rep_lines[0].startswith("[")
assert no_rep_lines[1].startswith("[")
# --- 6. Highlighter & Keywords: defaults and suppression -------------------
# RichHandler runs ReprHighlighter and keyword highlighting by default.
# HTTP verbs (GET, POST, etc.) and numbers (404, 200) are highlighted even
# when markup=False.
logger_hl, buf_hl, _, handler_hl = create_string_logger(color=True)
logger_hl.info("GET /api/v1/items 404 Not Found")
out_hl = buf_hl.getvalue()
# GET is styled with bold yellow (33) and 404 with bold cyan (36)
assert "\x1b[1;33mGET\x1b[0m" in out_hl
assert "\x1b[1;36m404\x1b[0m" in out_hl
# keywords=None restores defaults; only keywords=[] disables keyword highlighting.
# Disabling both requires keywords=[] and highlighter=NullHighlighter().
logger_plain_hl, buf_plain_hl, _, _ = create_string_logger(
color=True,
highlighter=NullHighlighter(),
keywords=[],
)
logger_plain_hl.info("GET /api/v1/items 404 Not Found")
out_plain_hl = buf_plain_hl.getvalue()
assert "\x1b[1;33mGET\x1b[0m" not in out_plain_hl
assert "\x1b[1;36m404\x1b[0m" not in out_plain_hl
assert "GET /api/v1/items 404 Not Found" in out_plain_hl
# Per-record highlighter override using extra={'highlighter': NullHighlighter()}
buf_hl.truncate(0)
buf_hl.seek(0)
logger_hl.info(
"POST /api/v1/items 200 OK",
extra={"highlighter": NullHighlighter()},
)
out_override = buf_hl.getvalue()
# 200 is unhighlighted, but keyword POST still highlights unless keywords=[]
assert "\x1b[1;36m200\x1b[0m" not in out_override
assert "\x1b[1;33mPOST\x1b[0m" in out_override
# --- 7. rich_tracebacks and tracebacks_show_locals -------------------------
# rich_tracebacks=False (default) renders standard string tracebacks.
# rich_tracebacks=True renders formatted Rich Traceback boxes and allows
# inspecting stack frame local variables.
def execute_failing_task():
secret_token = "auth_token_xyz"
raise ValueError("database connection timeout")
logger_tb_def, buf_tb_def, _, _ = create_string_logger(rich_tracebacks=False)
try:
execute_failing_task()
except ValueError:
logger_tb_def.exception("operation failed")
out_tb_def = buf_tb_def.getvalue()
assert "Traceback (most recent call last):" in out_tb_def
assert "locals" not in out_tb_def
assert "secret_token" not in out_tb_def
# With rich_tracebacks=True and show_locals=True:
logger_tb_rich, buf_tb_rich, _, _ = create_string_logger(
rich_tracebacks=True, show_locals=True
)
try:
execute_failing_task()
except ValueError:
logger_tb_rich.exception("operation failed")
out_tb_rich = buf_tb_rich.getvalue()
assert "╭─ Traceback" in out_tb_rich
assert "locals" in out_tb_rich
assert "secret_token" in out_tb_rich
# --- 8. Structural columns stripping and caller path tracking -------------
# show_time=False, show_level=False, show_path=False removes Rich columns.
# LogRender expands to console width (80 columns).
logger_min, buf_min, _, _ = create_string_logger(
show_time=False, show_level=False, show_path=False, width=80
)
logger_min.info("minimal message")
out_min = buf_min.getvalue()
assert out_min.strip() == "minimal message"
assert len(out_min.rstrip("\n")) == 80
# Default show_path=True extracts record.pathname and lineno
logger_path, buf_path, _, _ = create_string_logger(show_path=True)
logger_path.info("path tracking check")
out_path = buf_path.getvalue()
assert "contract.py:" in out_path
# --- 9. Integration via logging.config.dictConfig -------------------------
dict_buf = io.StringIO()
dict_console = Console(file=dict_buf, force_terminal=False, color_system=None, width=80)
dict_cfg = make_dict_config(
dict_console,
logger_name="dict_service",
format_str="[%(name)s] %(message)s",
datefmt="[%X]",
rich_tracebacks=True,
markup=False,
)
logging.config.dictConfig(dict_cfg)
dict_logger = logging.getLogger("dict_service")
dict_logger.info("service initialized")
out_dict = dict_buf.getvalue()
assert "[dict_service] service initialized" in out_dict
assert "INFO" in out_dict
print("contract ok: rich.logging.RichHandler verified across all configuration boundaries")