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
4 changes: 3 additions & 1 deletion src/structlog/stdlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from __future__ import annotations

import asyncio
import contextlib
import contextvars
import functools
import logging
Expand Down Expand Up @@ -802,7 +803,8 @@ def __call__(
if len(args) == 1 and isinstance(args[0], dict) and args[0]:
args = args[0]

event_dict["event"] %= args
with contextlib.suppress(TypeError, ValueError):
event_dict["event"] %= args

if self.remove_positional_args and args is not None:
del event_dict["positional_args"]
Expand Down
21 changes: 21 additions & 0 deletions tests/test_stdlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,27 @@ def test_args_removed_if_empty(self):

assert {} == formatter(None, None, {"positional_args": ()})

def test_graceful_failure_on_formatting_error(self):
"""
If positional arguments do not match the formatting placeholders
in the event string, PositionalArgumentsFormatter should not crash
with a TypeError or ValueError. It should leave the event string
unformatted.

Regression test for https://github.com/hynek/structlog/issues/258.
"""
formatter = PositionalArgumentsFormatter()

# Mismatched placeholders (event has no %s but args are passed)
event_dict = formatter(
None,
None,
{"event": "Info message", "positional_args": ("x",)},
)

assert "Info message" == event_dict["event"]
assert "positional_args" not in event_dict


class TestAddLogLevelNumber:
@pytest.mark.parametrize(("level", "number"), NAME_TO_LEVEL.items())
Expand Down