Skip to content

Bug: Logging of non-circular references are removed by default JSON replacer function #4721

@phipag

Description

@phipag

Discussed in #4718

Originally posted by yvele November 6, 2025
When logging an object that has multiple references to the same object (non-circular), it seems that the logger removes the duplicate reference entirely, even though it’s not circular.

cont ref = { name: "Bob" };
const obj = { ref1: ref, ref2: ref };
logger.info(obj);
// Output:
// {"ref1":{"name":"Bob"}}

You can see that ref2, even though it’s not circular, is completely removed due to the implementation of circular reference removal.

This comes from the current implementation here:

/**
* A custom JSON replacer function that is used to serialize the log items.
*
* By default, we already extend the default serialization behavior to handle `BigInt` and `Error` objects, as well as remove circular references.
* When a custom JSON replacer function is passed to the Logger constructor, it will be called **before** our custom rules for each key-value pair in the object being stringified.
*
* This allows you to customize the serialization while still benefiting from the default behavior.
*
* @see {@link ConstructorOptions.jsonReplacerFn}
*/
protected getJsonReplacer(): (key: string, value: unknown) => void {
const references = new WeakSet();
return (key, value) => {
let replacedValue = value;
if (this.#jsonReplacerFn)
replacedValue = this.#jsonReplacerFn?.(key, replacedValue);
if (replacedValue instanceof Error) {
replacedValue = this.getLogFormatter().formatError(replacedValue);
}
if (typeof replacedValue === 'bigint') {
return replacedValue.toString();
}
if (typeof replacedValue === 'object' && replacedValue !== null) {
if (references.has(replacedValue)) {
return;
}
references.add(replacedValue);
}
return replacedValue;
};
}

I would expect both references (ref1 and ref2) to appear in the log output, since the structure is not circular, it’s just a shared reference.

If it’s intentional, could you explain the reasoning, e.g., is the goal to enforce unique object serialization or minimize payload size?

It might also be useful to add a unit test to explicitly cover this case (or document the current behavior) to make the intention clear for contributors.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingloggerThis item relates to the Logger Utility

    Type

    No type

    Projects

    Status

    Pending customer

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions