Skip to content

Cap the size of logged request and response bodies. - #11

Merged
gustavofreze merged 1 commit into
mainfrom
feat/body-limit
Nov 8, 2026
Merged

gustavofreze merged 1 commit into
mainfrom
feat/body-limit

Conversation

@gustavofreze

Copy link
Copy Markdown
Member

Please follow the contributing guidelines.

Summary

The middleware logged every request and response body whole. A consumer serving a long document (the full text of a legal instrument, about 18 KB) wrote entries longer than the 16 KB line the Docker awslogs driver accepts. The driver splits such a line into fragments that are no longer JSON, so structured parsing breaks, and every byte of the body reaches the log.

Every logged body is now capped:

LogMiddleware::create()
    ->withLogger(logger: $logger)
    ->withBodyLimit(bytes: 4096)
    ->build();
  • The size of a body is the length in bytes of its JSON encoding, written the way a JSON logger writes it (unescaped slashes and unescaped UTF-8, the flags tiny-blocks/logger uses).
  • A body within the limit is logged whole, exactly as before. Small bodies are untouched.
  • A longer body is logged as an excerpt that fits within the limit, and the entry gains body_truncated_from_bytes, the size of the whole body.
  • The excerpt is the body read in document order up to the limit: fields and elements that fit are kept as they are, the string where the limit falls is cut on a whole UTF-8 character and ends with …, an object or array where it falls is read the same way one level down, and everything after is left out. Escaped characters (quotes, control characters) count at their encoded size, so the excerpt never exceeds the limit.
  • Default 2048 bytes. An entry carries one body next to the method, URI, query parameters and correlation id. 2048 keeps the whole entry well under 16 KB even if a logger escapes every non-ASCII character (up to 3x), while leaving room for the payloads that matter when debugging.
  • withBodyLimit(bytes: 0) logs only the size of every body. A negative limit raises the new BodyLimitOutOfRange at build time.
  • LogMiddleware::build() gains ?int $bodyLimit = null, placed after $logger by the parameter-ordering rule. Named-argument callers are unaffected.

Why an excerpt rather than a truncated string

The library has no redaction of its own. Consumers redact in the logger, after this middleware, by field name at any depth (tiny-blocks/logger EmailRedaction, NameRedaction, GenericRedaction and so on). Turning a long body into a cut JSON string would hide its fields from those redactions and put unmasked values into the first 2 KB of the log, which is worse than today. The excerpt stays a JSON object or array with its field names, so the entry stays valid JSON and those redactions keep applying. A test with a redacting logger covers it.

Release

Recommended release: 1.5.0 (minor). The public API only gains a builder method, an optional parameter and an exception. The visible change is that bodies longer than 2048 bytes are now logged as excerpts by default. That is the fix the release exists for, and a consumer who needs whole bodies can raise the limit.

Related issue

None. The need came from a consumer whose entries for long documents were split by the log driver.

Checklist

  • Tests added or updated. 20 new cases, 70 tests in total: at, under and one byte over the default limit, exactly at a custom limit, zero and negative limits, request and response bodies, a string cut after a field that fits, a list element that fits exactly, a number that does not fit, a nested object cut inside and one with room for its brackets only, a string with and without room for the ellipsis, numeric keys, two-byte and four-byte characters at the cut, escaped characters, invalid UTF-8 in a parsed body, and redaction applied to an excerpt.
  • Documentation updated when applicable. README section "Limiting logged bodies" and the overview.
  • make review passes.
  • make tests passes: 70 tests, 134 assertions, 115 mutants, all killed, MSI 100%.

Whole bodies were logged, so a long document produced a line longer
than the 16 KB the awslogs driver accepts, which splits it into
fragments that are no longer JSON, and every byte of it reached the log.

A body longer than the limit (2048 bytes by default, set through
withBodyLimit) is now logged as an excerpt within the limit, next to
body_truncated_from_bytes. The excerpt stays an object or an array
with its field names instead of a string of cut JSON, so the entry is
valid JSON and redactions keyed by field name still apply to it.
@gustavofreze gustavofreze self-assigned this Nov 8, 2026
@gustavofreze
gustavofreze merged commit 1efa94f into main Nov 8, 2026
5 checks passed
@gustavofreze
gustavofreze deleted the feat/body-limit branch November 8, 2026 01:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant