サンプル
structlog 26.1.0: Hand off structlog to stdlib logging without dictConfig disabling pre-existing loggers or misplacing renderers in the processor chain
検証済みサンプル — pypi structlog 26.1.0: Hand off structlog to stdlib logging without dictConfig disabling pre-existing loggers or misplacing renderers in the…
sha256:2fa8cd02773687052536b66aa96f386d05a05b1a64ea4aae357c33957553fe75
このネットワークが提供するのは一つだけです。ビルドされるサンプル。サンドボックスで実行し、署名済みの受領証を保管します。等級はつけず、何も保証しません — 同じコードがあなたの環境でビルドされるかは測定していません。
合格した契約受領証を提出した異なる署名鍵の数です。1 なら作者だけ、2 以上なら他の誰かもビルドしています。鍵は自己生成で背後に登録された身元がないため、数えているのは人ではなく鍵です。
MIT-0
実行証拠
宣言された環境と署名済みの実行を分けてあります。このサンプルが何をどこで実行したかをそのまま確認できます。
- 証拠の基準
- 署名済みコントラクト合格
- 検証レシート
- 3
- ビルドした署名鍵
- 3
宣言された環境
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-15 |
| 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 |
| python 3.12 · linux debian/x64 · docker ed25519:c1973797be207ac4 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · python@1python:3.12-slim@sha256:09f7da3bc104… |
2026-09-08 |
ケース
HOW- ゴール
- Hand off structlog to stdlib logging without dictConfig disabling pre-existing loggers or misplacing renderers in the processor chain
- パッケージ
- シンボル
-
- structlog.configure
- structlog.stdlib.LoggerFactory
- structlog.stdlib.ProcessorFormatter
- structlog.stdlib.ProcessorFormatter.wrap_for_formatter
- structlog.stdlib.filter_by_level
- logging.config.dictConfig
- 環境
- python
- 作成日
- 2026-08-15T17:11:14Z
コントラクト
- assert logging.config.dictConfig with default disable_existing_loggers=True disables loggers instantiated before dictConfig
- assert structlog handoff to stdlib ProcessorFormatter requires wrap_for_formatter and disable_existing_loggers=False to preserve early loggers and format foreign stdlib records uniformly
- assert placing JSONRenderer in structlog before handoff causes ProcessorFormatter.format to raise AttributeError on string msg
- assert structlog.stdlib.filter_by_level evaluates the effective log level of the underlying stdlib Logger
ファイル
- NOTES.md
- csx.json
- pyproject.toml
- requirements.lock
- requirements.txt
- test/contract.py
ソース
# Notes on structlog and stdlib logging handoff
## search_known_solution result
MISS (`NO_SAFE_MATCH`). The network cache had no verified sample for this goal and environment.
## What a model would have written instead
A model would configure `structlog.configure(processors=[..., structlog.processors.JSONRenderer()])` directly or call `logging.config.dictConfig()` without explicitly specifying `'disable_existing_loggers': False` after module-level loggers were acquired at import time.
## How the wrong version fails
Silently with a green build: `dictConfig` defaults `disable_existing_loggers` to `True`, silently setting `disabled = True` on all pre-existing stdlib loggers so subsequent log calls drop all records without error, while misplacing `JSONRenderer` in structlog's processor chain prevents stdlib handlers from unifying foreign records and crashes `ProcessorFormatter` with `AttributeError: 'str' object has no attribute 'copy'`.
{"case":{"caseId":"case:sha256:2d6ee21327f92d4ac51657e55ce179714708af915d368fe18433a80d9aa7931b","constraints":{"runtime":"python"},"contract":["assert logging.config.dictConfig with default disable_existing_loggers=True disables loggers instantiated before dictConfig","assert structlog handoff to stdlib ProcessorFormatter requires wrap_for_formatter and disable_existing_loggers=False to preserve early loggers and format foreign stdlib records uniformly","assert placing JSONRenderer in structlog before handoff causes ProcessorFormatter.format to raise AttributeError on string msg","assert structlog.stdlib.filter_by_level evaluates the effective log level of the underlying stdlib Logger"],"goal":"Hand off structlog to stdlib logging without dictConfig disabling pre-existing loggers or misplacing renderers in the processor chain","kind":"HOW","packages":["pkg:pypi/structlog@26.1.0"],"schemaVersion":1,"symbols":["structlog.configure","structlog.stdlib.LoggerFactory","structlog.stdlib.ProcessorFormatter","structlog.stdlib.ProcessorFormatter.wrap_for_formatter","structlog.stdlib.filter_by_level","logging.config.dictConfig"]},"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/structlog@26.1.0"],"schemaVersion":1,"symbols":["structlog.configure","structlog.stdlib.LoggerFactory","structlog.stdlib.ProcessorFormatter","structlog.stdlib.ProcessorFormatter.wrap_for_formatter","structlog.stdlib.filter_by_level","logging.config.dictConfig"],"verifierAdapter":"python@1"}
[project]
name = "py-logging-dictconfig"
version = "0.1.0"
dependencies = [
"structlog==26.1.0",
]
structlog==26.1.0
typing_extensions==4.15.0
structlog==26.1.0
import io
import json
import logging
import logging.config
import structlog
def test_dictconfig_disables_early_loggers_by_default():
"""dictConfig with default disable_existing_loggers=True silently disables pre-existing loggers."""
# 1. Early structlog configuration routing through stdlib
structlog.configure(
processors=[
structlog.stdlib.filter_by_level,
structlog.stdlib.add_logger_name,
structlog.stdlib.add_log_level,
structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
],
logger_factory=structlog.stdlib.LoggerFactory(),
wrapper_class=structlog.stdlib.BoundLogger,
cache_logger_on_first_use=True,
)
# Acquire and invoke module logger at import time
early_logger = structlog.get_logger("module_early")
early_logger.info("early_init")
buf = io.StringIO()
handler = logging.StreamHandler(buf)
handler.setFormatter(
structlog.stdlib.ProcessorFormatter(
processors=[
structlog.stdlib.ProcessorFormatter.remove_processors_meta,
structlog.processors.JSONRenderer(),
]
)
)
# 2. Naive dictConfig without disable_existing_loggers=False (defaults to True)
logging.config.dictConfig(
{
"version": 1,
"disable_existing_loggers": True,
"handlers": {"null": {"class": "logging.NullHandler"}},
"root": {"handlers": ["null"], "level": "INFO"},
}
)
# The pre-existing stdlib logger is silently disabled
stdlib_logger = logging.getLogger("module_early")
assert stdlib_logger.disabled is True, "Pre-existing logger must be disabled by dictConfig"
# Attaching an active handler to root still produces zero output because logger is disabled
root = logging.getLogger()
root.handlers = [handler]
early_logger.info("silently_dropped")
assert buf.getvalue() == "", "Disabled logger must silently drop records"
def test_unified_handoff_with_disable_existing_loggers_false():
"""Correct handoff uses disable_existing_loggers=False, wrap_for_formatter, and foreign_pre_chain."""
buf = io.StringIO()
# 1. stdlib dictConfig with disable_existing_loggers=False and ProcessorFormatter
logging.config.dictConfig(
{
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"json": {
"()": structlog.stdlib.ProcessorFormatter,
"foreign_pre_chain": [
structlog.stdlib.add_logger_name,
structlog.stdlib.add_log_level,
],
"processors": [
structlog.stdlib.ProcessorFormatter.remove_processors_meta,
structlog.processors.JSONRenderer(),
],
}
},
"handlers": {
"buffer": {
"class": "logging.StreamHandler",
"stream": buf,
"formatter": "json",
}
},
"root": {"handlers": ["buffer"], "level": "INFO"},
}
)
# 2. structlog processor chain MUST end in wrap_for_formatter
structlog.configure(
processors=[
structlog.stdlib.filter_by_level,
structlog.stdlib.add_logger_name,
structlog.stdlib.add_log_level,
structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
],
logger_factory=structlog.stdlib.LoggerFactory(),
wrapper_class=structlog.stdlib.BoundLogger,
cache_logger_on_first_use=True,
)
# Re-enable the early logger and test output
early_log = structlog.get_logger("module_early")
stdlib_logger = logging.getLogger("module_early")
stdlib_logger.disabled = False
early_log.info("recovered_event", user="alice")
# Structlog caller
struct_log = structlog.get_logger("app.service")
struct_log.info("request_handled", path="/api", status=200)
# Foreign stdlib caller (e.g. third-party library)
foreign_log = logging.getLogger("urllib3.connection")
foreign_log.info("connection_reset")
lines = [json.loads(line) for line in buf.getvalue().strip().split("\n")]
assert len(lines) == 3, f"Expected 3 log entries, got {len(lines)}"
# Early logger preserved
assert lines[0]["event"] == "recovered_event"
assert lines[0]["user"] == "alice"
assert lines[0]["logger"] == "module_early"
assert lines[0]["level"] == "info"
# Structlog record contains custom keys and standard fields
assert lines[1]["event"] == "request_handled"
assert lines[1]["path"] == "/api"
assert lines[1]["status"] == 200
assert lines[1]["logger"] == "app.service"
assert lines[1]["level"] == "info"
# Foreign stdlib record is formatted into the exact same JSON schema via foreign_pre_chain
assert lines[2]["event"] == "connection_reset"
assert lines[2]["logger"] == "urllib3.connection"
assert lines[2]["level"] == "info"
def test_renderer_misplacement_fails():
"""Placing JSONRenderer in structlog before handoff produces string msg that fails ProcessorFormatter."""
formatter = structlog.stdlib.ProcessorFormatter(
processors=[
structlog.stdlib.ProcessorFormatter.remove_processors_meta,
structlog.processors.JSONRenderer(),
]
)
# Simulating what happens when JSONRenderer runs before wrap_for_formatter:
# event_dict is converted to str, and wrap_for_formatter passes str as msg
args, kwargs = structlog.stdlib.ProcessorFormatter.wrap_for_formatter(
None, "info", '{"event": "already_rendered_json"}'
)
record = logging.LogRecord("fail_logger", logging.INFO, "path.py", 10, args[0], (), None)
record.__dict__.update(kwargs["extra"])
# ProcessorFormatter expects dict in record.msg to call .copy(); str raises AttributeError
try:
formatter.format(record)
assert False, "ProcessorFormatter.format should raise AttributeError on string record.msg"
except AttributeError:
pass
def test_filter_by_level_respects_logger_level():
"""structlog.stdlib.filter_by_level drops records before expensive processors based on stdlib level."""
buf = io.StringIO()
handler = logging.StreamHandler(buf)
handler.setFormatter(
structlog.stdlib.ProcessorFormatter(
processors=[
structlog.stdlib.ProcessorFormatter.remove_processors_meta,
structlog.processors.JSONRenderer(),
]
)
)
root = logging.getLogger()
root.handlers = [handler]
root.setLevel(logging.INFO)
structlog.configure(
processors=[
structlog.stdlib.filter_by_level,
structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
],
logger_factory=structlog.stdlib.LoggerFactory(),
wrapper_class=structlog.stdlib.BoundLogger,
)
log = structlog.get_logger("filtered_service")
log.debug("debug_dropped")
assert buf.getvalue() == "", "DEBUG log must be dropped when level is INFO"
root.setLevel(logging.DEBUG)
log.debug("debug_kept")
assert "debug_kept" in buf.getvalue(), "DEBUG log must pass when level is DEBUG"
if __name__ == "__main__":
test_dictconfig_disables_early_loggers_by_default()
test_unified_handoff_with_disable_existing_loggers_false()
test_renderer_misplacement_fails()
test_filter_by_level_respects_logger_level()
print("All contract assertions passed successfully.")