Skip to content

Commit

Permalink
Fix Logfmt escaping (#594)
Browse files Browse the repository at this point in the history
* logfmt: fix escaping of backslashes

Fixes #511, closes #513

* Fix changelog

* Add PR#

* Update tests/processors/test_renderers.py

Co-authored-by: Georges Dubus <[email protected]>

* Fix case of quotes in value

---------

Co-authored-by: Georges Dubus <[email protected]>
  • Loading branch information
hynek and madjar authored Feb 7, 2024
1 parent db834f5 commit 354ef53
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 2 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ You can find our backwards-compatibility policy [here](https://github.com/hynek/
- `structlog.processors.LogfmtRenderer` now escapes newlines.
[#592](https://github.com/hynek/structlog/pull/592)

- `structlog.processors.LogfmtRenderer` now escapes backslashes and double quotes.
[#594](https://github.com/hynek/structlog/pull/594)


## [24.1.0](https://github.com/hynek/structlog/compare/23.3.0...24.1.0) - 2024-01-08

Expand Down
11 changes: 9 additions & 2 deletions src/structlog/processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,9 +170,16 @@ def __call__(
continue
value = "true" if value else "false"

value = f"{value}".replace('"', '\\"').replace("\n", "\\n")
value = str(value)
backslashes_need_escaping = (
" " in value or "=" in value or '"' in value
)
if backslashes_need_escaping and "\\" in value:
value = value.replace("\\", "\\\\")

value = value.replace('"', '\\"').replace("\n", "\\n")

if " " in value or "=" in value:
if backslashes_need_escaping:
value = f'"{value}"'

elements.append(f"{key}={value}")
Expand Down
27 changes: 27 additions & 0 deletions tests/processors/test_renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,33 @@ def test_newline_in_value(self):

assert r"with_newline=some\nvalue" == rv

@pytest.mark.parametrize(
("raw", "escaped"),
[
# Slash by itself does not need to be escaped.
(r"a\slash", r"a\slash"),
# A quote requires quoting, and escaping the quote.
('a"quote', r'"a\"quote"'),
# If anything triggers quoting of the string, then the slash must
# be escaped.
(
r'a\slash with space or a"quote',
r'"a\\slash with space or a\"quote"',
),
(
r"I want to render this \"string\" with logfmtrenderer",
r'"I want to render this \\\"string\\\" with logfmtrenderer"',
),
],
)
def test_escaping(self, raw, escaped):
"""
Backslashes and quotes are escaped.
"""
rv = LogfmtRenderer()(None, None, {"key": raw})

assert f"key={escaped}" == rv


class TestJSONRenderer:
def test_renders_json(self, event_dict):
Expand Down

0 comments on commit 354ef53

Please sign in to comment.