Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ You can find our backwards-compatibility policy [here](https://github.com/hynek/
- `structlog.BytesLogger`, `structlog.PrintLogger`, and `structlog.WriteLogger` now hold *weak* references to the files they use for output.
This prevents their leakage in long-running processes that open many logfiles, such as task executors that create a per-task `BytesLogger` or `WriteLogger`.
[#807](https://github.com/hynek/structlog/pull/807)
- `structlog.stdlib.ProcessorFormatter` no longer clears `LogRecord.args` before running its processor chain, so processors that inspect `event_dict["_record"].args` can see the original positional arguments.
`args` is still cleared before the record is handed to the underlying `logging.Formatter`.
[#771](https://github.com/hynek/structlog/issues/771)


### Changed
Expand Down
8 changes: 6 additions & 2 deletions src/structlog/stdlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -1141,8 +1141,6 @@ def format(self, record: logging.LogRecord) -> str:
if self.pass_foreign_args:
ed["positional_args"] = record.args

record.args = ()

# Add stack-related attributes to the event dict
if record.exc_info:
ed["exc_info"] = record.exc_info
Expand Down Expand Up @@ -1176,6 +1174,12 @@ def format(self, record: logging.LogRecord) -> str:
)
ed = cast(str, ed)

# Clear args after processors have run so user processors can access
# ``record.args`` via ``_record``. We render our log records before
# sending them back to logging, so ``LogRecord.args`` must be cleared,
# otherwise stdlib's formatter would raise ``TypeError: not all
# arguments converted during string formatting``.
record.args = ()
record.msg = ed

return super().format(record)
Expand Down
17 changes: 17 additions & 0 deletions tests/test_stdlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -1105,6 +1105,23 @@ def test_pass_foreign_args_true_sets_positional_args_key(self):
assert "positional_args" in event_dict
assert positional_args == event_dict["positional_args"]

def test_record_args_accessible_in_processor(self):
"""
Processors can access ``record.args`` via ``_record``: args are not
cleared on the record before the formatter's processors run.
"""
seen = []

def capture_args(_, __, event_dict):
seen.append(event_dict["_record"].args)
return event_dict

configure_logging((capture_args,))

logging.getLogger().info("test a=%s b=%s", "a", "b")

assert [("a", "b")] == seen

def test_log_dict(self, capsys):
"""
dicts can be logged with std library loggers.
Expand Down
Loading