Skip to content

Commit 097e440

Browse files
authored
fix(logger): correctly serialize shared object references in JSON replacer (#5186)
1 parent ee3b401 commit 097e440

2 files changed

Lines changed: 51 additions & 7 deletions

File tree

packages/logger/src/Logger.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -761,25 +761,29 @@ class Logger extends Utility implements LoggerInterface {
761761
* @see {@link ConstructorOptions.jsonReplacerFn}
762762
*/
763763
protected getJsonReplacer(): (key: string, value: unknown) => void {
764-
const references = new WeakSet();
764+
const ancestors: unknown[] = [];
765+
const jsonReplacerFn = this.#jsonReplacerFn;
766+
const logFormatter = this.getLogFormatter();
765767

766-
return (key, value) => {
768+
return function (this: unknown, key: string, value: unknown) {
767769
let replacedValue = value;
768-
if (this.#jsonReplacerFn)
769-
replacedValue = this.#jsonReplacerFn?.(key, replacedValue);
770+
if (jsonReplacerFn) replacedValue = jsonReplacerFn(key, replacedValue);
770771

771772
if (replacedValue instanceof Error) {
772-
replacedValue = this.getLogFormatter().formatError(replacedValue);
773+
replacedValue = logFormatter.formatError(replacedValue);
773774
}
774775
if (typeof replacedValue === 'bigint') {
775776
return replacedValue.toString();
776777
}
777778
if (typeof replacedValue === 'object' && replacedValue !== null) {
779+
while (ancestors.length > 0 && ancestors.at(-1) !== this) {
780+
ancestors.pop();
781+
}
778782
/* v8 ignore next -- @preserve */
779-
if (references.has(replacedValue)) {
783+
if (ancestors.includes(replacedValue)) {
780784
return;
781785
}
782-
references.add(replacedValue);
786+
ancestors.push(replacedValue);
783787
}
784788

785789
return replacedValue;

packages/logger/tests/unit/formatters.test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,46 @@ describe('Formatters', () => {
727727

728728
// #region custom JSON replacer
729729

730+
it('correctly serializes shared object references across different keys', () => {
731+
// Prepare
732+
const x1 = [{ value: '1' }];
733+
734+
// Act
735+
logger.info('foo', { x1, destructed: [...x1] });
736+
737+
// Assess
738+
expect(console.info).toHaveBeenCalledTimes(1);
739+
expect(console.info).toHaveLoggedNth(
740+
1,
741+
expect.objectContaining({
742+
level: 'INFO',
743+
message: 'foo',
744+
x1: [{ value: '1' }],
745+
destructed: [{ value: '1' }],
746+
})
747+
);
748+
});
749+
750+
it('correctly serializes multiple spread arrays sharing the same elements', () => {
751+
// Prepare
752+
const x1 = [{ value: '1' }];
753+
754+
// Act
755+
logger.info('foo', { destructed1: [...x1], destructed2: [...x1] });
756+
757+
// Assess
758+
expect(console.info).toHaveBeenCalledTimes(1);
759+
expect(console.info).toHaveLoggedNth(
760+
1,
761+
expect.objectContaining({
762+
level: 'INFO',
763+
message: 'foo',
764+
destructed1: [{ value: '1' }],
765+
destructed2: [{ value: '1' }],
766+
})
767+
);
768+
});
769+
730770
it('ignores keys with circular references when stringifying', () => {
731771
// Prepare
732772
const circularObject = {

0 commit comments

Comments
 (0)