Skip to content

fix(logger): summarise large binary query parameters - #12810

Open
pri12ya871 wants to merge 2 commits into
typeorm:masterfrom
pri12ya871:fix/logger-large-buffer-params
Open

fix(logger): summarise large binary query parameters#12810
pri12ya871 wants to merge 2 commits into
typeorm:masterfrom
pri12ya871:fix/logger-large-buffer-params

Conversation

@pri12ya871

Copy link
Copy Markdown

Description of change

AbstractLogger.stringifyParams calls JSON.stringify on the whole parameter array. A buffer serialises as one number per byte, so a binary parameter produces a log line several times the size of the data itself β€” enough to exhaust memory before anything is written, as @alumni reported in #10515 with a 200MB attachment.

Current behavior: a 200MB buffer parameter is serialised in full, and the process runs out of memory. AdvancedConsoleLogger makes it worse, since it then tries to syntax-highlight the result.

New behavior: a binary parameter larger than 1KB is logged as its type and byte length β€” ["<Buffer(200000000 bytes)>"] β€” and everything else renders exactly as before.

This follows the approach @alumni sketched on the issue, with the threshold as a named constant so it is easy to argue with. 1KB seemed like the point where the serialised form stops being readable anyway; happy to move it.

Preserving the existing output

Rendering the array element by element rather than in one call is where behaviour could drift, so I checked rather than assumed. Running the old and new implementations side by side over numbers, strings needing escapes, null, undefined, booleans, nested objects, dates, empty arrays, functions, small buffers, circular references and a BigInt, every non-large-binary case produces a byte-identical string.

Two of those are worth calling out, because they are what an element-wise rewrite usually gets wrong:

  • JSON.stringify returns undefined for a value it cannot represent, while the array-level call renders it as null. The element path restores that, so [undefined] and [() => 1] still log as [null].
  • A parameter set that cannot be serialised at all β€” a circular reference, a BigInt β€” still falls back to returning the raw parameters, unchanged.

The elements are joined with , rather than , for the same reason: it keeps existing log lines identical.

Scope

Only stringifyParams. The second observation on the issue β€” that SelectQueryBuilder.loadRawResults also does JSON.stringify(parameters) when building a cache key β€” is left alone deliberately: it is a different code path with different constraints (the string is a cache identity, not a log line, so summarising a parameter there could collide two distinct queries). Glad to look at it separately if you want it fixed too.

How I verified it

pnpm run compile passes, eslint reports no errors, prettier --check is clean, and test:fast passes apart from an unrelated cli init failure that reproduces on a clean master here.

Seven unit tests are added in test/unit/logger/stringify-params.test.ts, covering the large-buffer summary, a non-Buffer binary view, a small buffer still being serialised in full, ordinary parameters being untouched, undefined still rendering as null, and the circular-reference fallback. They need no database.

Pull-Request Checklist

  • Code is up-to-date with the master branch
  • This pull request links a relevant issue using a closing keyword: Closes OOM due to AdvancedConsoleLogger logging large buffer parametersΒ #10515
  • There are new or updated tests validating the change (tests/**.test.ts)
  • Documentation has been updated to reflect this change (docs/docs/**.md) β€” N/A, no user-facing API change

stringifyParams called JSON.stringify on the whole parameter array, and
a buffer serialises as an array of one number per byte. A large binary
parameter therefore produced a log line several times the size of the
data, which exhausts memory before anything is written β€” reported with a
200MB attachment crashing the process.

A binary parameter over 1KB is now logged as its type and byte length
instead. Everything else is rendered exactly as before, including the
cases the old array-level stringify handled implicitly: an
unrepresentable value still becomes null, and a parameter set that
cannot be serialised at all still falls back to the raw value.

Closes typeorm#10515
@github-actions github-actions Bot added the linked-issue PR references an issue label Aug 27, 2026
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Aug 27, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) πŸ“˜ Rule violations (0) πŸ“œ Skill insights (0)

Grey Divider


Action required

1. Binary detection is shallow 🐞 Bug ☼ Reliability
Description
stringifyParam checks only the top-level array element, so a large binary nested in an
ObjectLiteral or an MssqlParameter.value is still fully expanded by JSON.stringify. SQL Server
insert/update parameters are routinely wrapped this way, so logging a large binary column can still
create the huge string and exhaust memory that this PR is intended to prevent.
Code

src/logger/AbstractLogger.ts[R419-422]

+        if (
+            ArrayBuffer.isView(value) &&
+            value.byteLength > MAX_LOGGED_PARAMETER_BYTES
+        )
Evidence
The new guard examines only value itself, after which JSON.stringify(value) recursively
serializes all nested fields. TypeORM wraps MSSQL valuesβ€”including binary/varbinary/image valuesβ€”in
an object whose public value field holds the original value; insert/update builders create that
wrapper, and the runner logs the wrapped parameter array before extracting parameter.value for
execution. The logger and QueryRunner contracts additionally accept ObjectLiteral, and MySQL
passes such parameters directly through its logging path.

src/logger/AbstractLogger.ts[394-425]
src/driver/sqlserver/MssqlParameter.ts[45-58]
src/driver/sqlserver/SqlServerDriver.ts[1025-1072]
src/query-builder/InsertQueryBuilder.ts[1623-1632]
src/query-builder/UpdateQueryBuilder.ts[595-603]
src/driver/sqlserver/SqlServerQueryRunner.ts[228-255]
src/logger/Logger.ts[67-73]
src/driver/mysql/MysqlQueryRunner.ts[198-222]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Large binary values are summarized only when they are direct array elements. Recursively summarize oversized binary views inside supported parameter objects and wrappers while retaining the existing circular-reference fallback.
## Issue Context
SQL Server wraps column values in `MssqlParameter`, and named parameter objects are also accepted by the logger/query APIs. Their binary values currently reach recursive `JSON.stringify` unchanged.
## Fix Focus Areas
- src/logger/AbstractLogger.ts[394-429]
- src/driver/sqlserver/MssqlParameter.ts[53-59]
- src/driver/sqlserver/SqlServerDriver.ts[1032-1072]
- src/query-builder/InsertQueryBuilder.ts[1623-1632]
- src/query-builder/UpdateQueryBuilder.ts[595-603]

β“˜ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Custom iterators hijack logging 🐞 Bug ☼ Reliability ⭐ New
Description
Array.from(parameters, ...) consumes a parameter array's Symbol.iterator, while the previous
JSON.stringify(parameters) serialized indexed elements without using that iterator. An Array
subclass or array with an overridden iterator can therefore log different values, throw, or hang
indefinitely on an unbounded iterator before the query log is emitted.
Code

src/logger/AbstractLogger.ts[403]

+                Array.from(parameters, (value) => this.summariseParam(value)),
Evidence
The changed line passes every array through Array.from, which uses its iterator. The repository's
public query API and logger path accept and forward any[] parameters to this stringifier, with no
plain-array restriction.

src/logger/AbstractLogger.ts[394-404]
src/logger/AbstractLogger.ts[362-370]
src/query-runner/QueryRunner.ts[126-132]
src/data-source/DataSource.ts[503-509]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`Array.from(parameters, ...)` invokes a custom `Symbol.iterator`, changing the previous index-based serialization semantics and potentially causing incorrect output or nontermination.

## Issue Context
The public query and logger APIs accept `any[]`, which includes array subclasses and arrays with overridden iterators. Build the sanitized plain array by reading `length` and numeric indexes instead of iterating `parameters`; preserve holes as values that JSON serializes to `null`.

## Fix Focus Areas
- src/logger/AbstractLogger.ts[398-404]
- test/unit/logger/stringify-params.test.ts[23-87]

β“˜ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Sparse parameters lose nulls βœ“ Resolved 🐞 Bug ≑ Correctness
Description
Using map(...).join(",") skips sparse-array holes, changing JSON.stringify(new Array(2)) from
[null,null] to [,]. This produces a different and invalid-looking parameter log where positional
values appear missing.
Code

src/logger/AbstractLogger.ts[R398-400]

+            return `[${parameters
+                .map((value) => this.stringifyParam(value))
+                .join(",")}]`
Evidence
The changed code uses the array's map, which does not invoke the callback for holes, followed by
join, which emits empty fields. Query parameters are accepted as any[] and passed to
stringifyParams when appended to SQL logs.

src/logger/AbstractLogger.ts[362-370]
src/logger/AbstractLogger.ts[394-400]
src/logger/Logger.ts[67-73]
src/query-runner/QueryRunner.ts[121-131]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The element-wise mapping skips sparse array slots, whereas array-level JSON serialization represents each hole as `null`. Preserve the old representation and add a sparse-array regression test.
## Issue Context
The public query/logging contracts accept arbitrary `any[]` values, including sparse arrays.
## Fix Focus Areas
- src/logger/AbstractLogger.ts[394-400]
- test/unit/logger/stringify-params.test.ts[23-67]

β“˜ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. toJSON receives wrong key βœ“ Resolved 🐞 Bug ≑ Correctness
Description
Serializing each parameter as a root value calls its toJSON with "" instead of the array index
used by the previous array-level serialization. Any legal parameter whose toJSON(key) depends on
that key now produces a different log valueβ€”for example, the old ["0"] can become [""].
Code

src/logger/AbstractLogger.ts[425]

+        const json = JSON.stringify(value)
Evidence
The old implementation serialized the containing parameter array in one call, while the new
implementation invokes JSON.stringify(value) independently for each element. Because parameter
values are unrestricted any, key-sensitive toJSON objects can reach this path and observe the
changed serialization context.

src/logger/AbstractLogger.ts[394-425]
src/logger/Logger.ts[67-73]
src/query-runner/QueryRunner.ts[121-131]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Per-element `JSON.stringify` changes the key passed to parameter objects' `toJSON` methods from their array index to the root key. Preserve array-level serialization context while replacing only oversized binary values, and add a key-sensitive `toJSON` regression test.
## Issue Context
Parameters are typed as `any[]`, so objects implementing `toJSON(key)` are supported values. The existing behavior claim requires byte-identical output outside large binaries.
## Fix Focus Areas
- src/logger/AbstractLogger.ts[394-429]
- test/unit/logger/stringify-params.test.ts[23-67]

β“˜ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


  • Author self-review: I have reviewed the code review findings, and addressed the relevant ones.

Grey Divider

Tip of the day
πŸ’‘ Did you know, you can ask Qodo to dismiss a finding you disagree with, with your reason on record

More tips β†— | Customize Qodo β†— | Qodo docs β†—

Grey Divider

Previous reviews

Review updated until commit 646ffd6 βš–οΈ Balanced

Results up to commit 7ac5d8f


🐞 Bugs (3) πŸ“˜ Rule violations (0) πŸ“œ Skill insights (0)


Action required
1. Binary detection is shallow 🐞 Bug ☼ Reliability
Description
stringifyParam checks only the top-level array element, so a large binary nested in an
ObjectLiteral or an MssqlParameter.value is still fully expanded by JSON.stringify. SQL Server
insert/update parameters are routinely wrapped this way, so logging a large binary column can still
create the huge string and exhaust memory that this PR is intended to prevent.
Code

src/logger/AbstractLogger.ts[R419-422]

+        if (
+            ArrayBuffer.isView(value) &&
+            value.byteLength > MAX_LOGGED_PARAMETER_BYTES
+        )
Evidence
The new guard examines only value itself, after which JSON.stringify(value) recursively
serializes all nested fields. TypeORM wraps MSSQL valuesβ€”including binary/varbinary/image valuesβ€”in
an object whose public value field holds the original value; insert/update builders create that
wrapper, and the runner logs the wrapped parameter array before extracting parameter.value for
execution. The logger and QueryRunner contracts additionally accept ObjectLiteral, and MySQL
passes such parameters directly through its logging path.

src/logger/AbstractLogger.ts[394-425]
src/driver/sqlserver/MssqlParameter.ts[45-58]
src/driver/sqlserver/SqlServerDriver.ts[1025-1072]
src/query-builder/InsertQueryBuilder.ts[1623-1632]
src/query-builder/UpdateQueryBuilder.ts[595-603]
src/driver/sqlserver/SqlServerQueryRunner.ts[228-255]
src/logger/Logger.ts[67-73]
src/driver/mysql/MysqlQueryRunner.ts[198-222]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Large binary values are summarized only when they are direct array elements. Recursively summarize oversized binary views inside supported parameter objects and wrappers while retaining the existing circular-reference fallback.

## Issue Context
SQL Server wraps column values in `MssqlParameter`, and named parameter objects are also accepted by the logger/query APIs. Their binary values currently reach recursive `JSON.stringify` unchanged.

## Fix Focus Areas
- src/logger/AbstractLogger.ts[394-429]
- src/driver/sqlserver/MssqlParameter.ts[53-59]
- src/driver/sqlserver/SqlServerDriver.ts[1032-1072]
- src/query-builder/InsertQueryBuilder.ts[1623-1632]
- src/query-builder/UpdateQueryBuilder.ts[595-603]

β“˜ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended
2. Sparse parameters lose nulls 🐞 Bug ≑ Correctness
Description
Using map(...).join(",") skips sparse-array holes, changing JSON.stringify(new Array(2)) from
[null,null] to [,]. This produces a different and invalid-looking parameter log where positional
values appear missing.
Code

src/logger/AbstractLogger.ts[R398-400]

+            return `[${parameters
+                .map((value) => this.stringifyParam(value))
+                .join(",")}]`
Evidence
The changed code uses the array's map, which does not invoke the callback for holes, followed by
join, which emits empty fields. Query parameters are accepted as any[] and passed to
stringifyParams when appended to SQL logs.

src/logger/AbstractLogger.ts[362-370]
src/logger/AbstractLogger.ts[394-400]
src/logger/Logger.ts[67-73]
src/query-runner/QueryRunner.ts[121-131]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The element-wise mapping skips sparse array slots, whereas array-level JSON serialization represents each hole as `null`. Preserve the old representation and add a sparse-array regression test.

## Issue Context
The public query/logging contracts accept arbitrary `any[]` values, including sparse arrays.

## Fix Focus Areas
- src/logger/AbstractLogger.ts[394-400]
- test/unit/logger/stringify-params.test.ts[23-67]

β“˜ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. toJSON receives wrong key 🐞 Bug ≑ Correctness
Description
Serializing each parameter as a root value calls its toJSON with "" instead of the array index
used by the previous array-level serialization. Any legal parameter whose toJSON(key) depends on
that key now produces a different log valueβ€”for example, the old ["0"] can become [""].
Code

src/logger/AbstractLogger.ts[425]

+        const json = JSON.stringify(value)
Evidence
The old implementation serialized the containing parameter array in one call, while the new
implementation invokes JSON.stringify(value) independently for each element. Because parameter
values are unrestricted any, key-sensitive toJSON objects can reach this path and observe the
changed serialization context.

src/logger/AbstractLogger.ts[394-425]
src/logger/Logger.ts[67-73]
src/query-runner/QueryRunner.ts[121-131]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Per-element `JSON.stringify` changes the key passed to parameter objects' `toJSON` methods from their array index to the root key. Preserve array-level serialization context while replacing only oversized binary values, and add a key-sensitive `toJSON` regression test.

## Issue Context
Parameters are typed as `any[]`, so objects implementing `toJSON(key)` are supported values. The existing behavior claim requires byte-identical output outside large binaries.

## Fix Focus Areas
- src/logger/AbstractLogger.ts[394-429]
- test/unit/logger/stringify-params.test.ts[23-67]

β“˜ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Rendering the array element by element changed two things it should not
have. Array.prototype.map skips holes, so a sparse parameter array lost
them β€” a hole logged as an empty slot rather than null β€” and serialising
each element separately meant a custom toJSON received an empty key
instead of the element's index.

Substituting the oversized binary values first and then stringifying the
array in one call keeps the summary while leaving every other rendering
detail exactly as it was.
@pri12ya871

Copy link
Copy Markdown
Author

Pushed 646ffd6. Two of the three findings above were real, and both were mine.

Rendering the array element by element was the mistake. Array.prototype.map skips holes, so a sparse parameter array lost them β€” a hole rendered as an empty slot instead of null β€” and serialising each element on its own meant a custom toJSON was called with an empty key rather than the element's index. Neither showed up in my original check because I had not thought to test a sparse array or a key-sensitive toJSON.

The fix is simpler than what it replaces: substitute the oversized binary values first, then stringify the array in a single call. The summary still happens, and every other rendering detail is the array-level JSON.stringify it always was β€” holes, indices, separators and all. I re-ran the old and new implementations over 13 shapes including both of the above; all non-binary cases are byte-identical. Two regression tests are added for them.

On the third finding, that the binary check is shallow: that is true, and deliberate for now. It matches the approach sketched on #10515, and going deeper means walking arbitrary nested structures on every logged query, which costs something on a hot path. If you would rather it recursed, say so and I will do it β€” but it seemed worth keeping this change to the case that actually crashes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

linked-issue PR references an issue

Development

Successfully merging this pull request may close these issues.

OOM due to AdvancedConsoleLogger logging large buffer parameters

1 participant