Skip to content

Use relaxed JSON escaping for log bodies - #340

Merged
mishamyte merged 3 commits into
serilog-contrib:masterfrom
olsh:fix/relaxed-json-escaping
Jun 24, 2026
Merged

Use relaxed JSON escaping for log bodies#340
mishamyte merged 3 commits into
serilog-contrib:masterfrom
olsh:fix/relaxed-json-escaping

Conversation

@olsh

@olsh olsh commented Jun 24, 2026

Copy link
Copy Markdown
Member

Fixes #339.

Problem

The body formatter (LokiJsonTextFormatter) and the batch serializer build their Utf8JsonWriters with no options, so they use System.Text.Json's default HTML-safe JavaScriptEncoder. It escapes ", ', <, >, & and all non-ASCII as \uXXXX. Because Serilog quotes string property values by default, this makes most stored Loki lines unreadable — a regression from v8.x, which used Serilog's JsonValueFormatter (standard escaping). See #339 for details.

Fix

Construct the writers with JsonWriterOptions(Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping), applied at all three writer sites (formatter body, batch envelope, reused body buffer) via a shared value.

Before:

{"Message":"Initialize \u0022SAGO\u0022", ...}

After:

{"Message":"Initialize \"SAGO\"", ...}

Notes

  • Output stays valid JSON and valid UTF-8, so consumers (including | json queries) are unaffected. "Unsafe" in the encoder name only refers to embedding JSON directly into HTML, which does not apply to Loki log bodies.
  • Added a unit test asserting <, >, & and non-ASCII survive verbatim.
  • dotnet test (UnitTests, net10.0): 122/122 pass.

Summary by CodeRabbit

  • Bug Fixes
    • Improved Loki JSON formatting so quotes, markup characters, and non-ASCII text (e.g., Cyrillic) remain readable instead of being converted to escaped/unicode forms, while still producing valid JSON.
    • Ensured the same improved escaping behavior is applied consistently across log event serialization and the public formatter output.
  • Tests
    • Added unit tests covering the public formatter path and promoted labels, verifying markup/non-ASCII remain verbatim and that quote escaping follows standard JSON rules.

The body formatter and batch serializer build Utf8JsonWriters with no
options, so they use the default HTML-safe JavaScriptEncoder, which
unicode-escapes every quote, ', <, >, & and all non-ASCII characters.
Because Serilog quotes string property values by default, this makes most
stored Loki lines unreadable (a regression from v8.x, which formatted
bodies with Serilog's JsonValueFormatter and standard escaping).

Construct the writers with JsonWriterOptions(Encoder =
JavaScriptEncoder.UnsafeRelaxedJsonEscaping). Output stays valid JSON and
UTF-8, so consumers (including the json parser) are unaffected. Add a unit
test covering markup and non-ASCII characters.
@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 122161f2-6ff8-4d2e-8c1d-127b9c7386d7

📥 Commits

Reviewing files that changed from the base of the PR and between 39f3274 and bcb9a84.

📒 Files selected for processing (5)
  • src/Serilog.Sinks.Grafana.Loki/Infrastructure/JsonWriterDefaults.fs
  • src/Serilog.Sinks.Grafana.Loki/LokiJsonTextFormatter.fs
  • src/Serilog.Sinks.Grafana.Loki/Serialization.fs
  • src/Serilog.Sinks.Grafana.Loki/Serilog.Sinks.Grafana.Loki.fsproj
  • tests/Serilog.Sinks.Grafana.Loki.UnitTests/WireFormatTests.fs
✅ Files skipped from review due to trivial changes (1)
  • src/Serilog.Sinks.Grafana.Loki/Serilog.Sinks.Grafana.Loki.fsproj
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/Serilog.Sinks.Grafana.Loki/Serialization.fs

📝 Walkthrough

Walkthrough

The PR adds a shared JSON writer helper and switches formatter and payload serialization to use relaxed escaping. Unit tests verify that quotes, markup, and non-ASCII characters remain readable in the emitted output.

Changes

Relaxed JSON escaping update

Layer / File(s) Summary
Shared relaxed writer defaults
src/Serilog.Sinks.Grafana.Loki/Infrastructure/JsonWriterDefaults.fs, src/Serilog.Sinks.Grafana.Loki/Serilog.Sinks.Grafana.Loki.fsproj
Adds a shared internal writer factory with relaxed JSON escaping and includes it in the project build.
Formatter and payload serialization
src/Serilog.Sinks.Grafana.Loki/LokiJsonTextFormatter.fs, src/Serilog.Sinks.Grafana.Loki/Serialization.fs
Routes the public formatter path and Loki payload serialization through the shared writer factory.
Wire format coverage
tests/Serilog.Sinks.Grafana.Loki.UnitTests/WireFormatTests.fs
Adds tests covering readable body text, public formatter output, and promoted label values with markup and non-ASCII text.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 I nibble logs with a merry grin,
Less \u-tingle lets the true chars in.
Quotes stay tidy, glyphs stay bright,
Loki reads them clear and light.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately describes the main change: enabling relaxed JSON escaping for log bodies.
Linked Issues check ✅ Passed The changes implement #339 by using relaxed JSON escaping for the Loki body writers and adding coverage for the intended output.
Out of Scope Changes check ✅ Passed The added helper, project file update, and tests all support the escaping fix and do not introduce unrelated scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/Serilog.Sinks.Grafana.Loki.UnitTests/WireFormatTests.fs`:
- Around line 255-267: The WireFormatTests ``body: quotes, markup and non-ASCII
are not unicode-escaped`` test currently checks angle brackets, ampersands, and
non-ASCII, but it never verifies quote handling. Update the assertions on
handler.LastBodyText to explicitly check that the quote character from mkInfo in
the payload is emitted as a literal quote and not escaped as \u0022, using the
existing test setup and the raw string checks already in place.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7bc04844-db9c-4a82-95f0-f3e0706a8169

📥 Commits

Reviewing files that changed from the base of the PR and between fa376ad and 375be12.

📒 Files selected for processing (3)
  • src/Serilog.Sinks.Grafana.Loki/LokiJsonTextFormatter.fs
  • src/Serilog.Sinks.Grafana.Loki/Serialization.fs
  • tests/Serilog.Sinks.Grafana.Loki.UnitTests/WireFormatTests.fs

Comment thread tests/Serilog.Sinks.Grafana.Loki.UnitTests/WireFormatTests.fs
@mishamyte mishamyte self-assigned this Jun 24, 2026
olsh and others added 2 commits June 24, 2026 16:26
Addresses CodeRabbit review on serilog-contrib#340. Assert at the decoded body level (the raw
HTTP payload embeds the body as a JSON string, double-escaping its quotes), so
a regression back to " for quotes is caught.
The relaxed-encoder JsonWriterOptions was defined twice (formatter and
serializer) with drifting comments, and each Utf8JsonWriter opted in per
construction site: a future writer that forgot the options argument would
silently revert to HTML-safe \uXXXX escaping with no compiler warning.

Extract a single Infrastructure.JsonWriterDefaults.createWriter as the one
way the sink builds a writer, so escaping cannot drift between sites. The
shared comment also corrects the description: the relaxed encoder still
escapes U+2028/U+2029 and DEL, so it is not "only what JSON mandates".

Add the two missing wire-format guards:
- the public LokiJsonTextFormatter.Format entry point (the sink fast path
  bypasses it, so its writer site had no test coverage);
- promoted label values (the envelope writer escapes label keys/values too).

The escaping tests sample neutral, multi-script non-ASCII (accented Latin
plus CJK) rather than a single script.
@mishamyte
mishamyte merged commit 59f4b79 into serilog-contrib:master Jun 24, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

v9 over-escapes log bodies: quotes, <, >, & and non-ASCII are unicode-escaped (regression from v8)

2 participants