Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions splat.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,15 @@ class Splatter {
const expectedSplat = tokens.length - escapes;
const extraSplat = expectedSplat - splat.length;
const metas = extraSplat < 0
? splat.splice(extraSplat, -1 * extraSplat)
? splat.slice(extraSplat)
: [];

// `splat` may be the same array referenced by info[SPLAT], which is
// shared across every transport's format pipeline for this log call -
// mutating it (e.g. via splice) here would drop the extra metadata for
// every transport after the first, so this only ever reads from it.
const formatArgs = extraSplat < 0 ? splat.slice(0, extraSplat) : splat;

// Now that { splat } has been separated from any potential { meta }. we
// can assign this to the `info` object and write it to our format stream.
// If the additional metas are **NOT** objects or **LACK** enumerable properties
Expand All @@ -68,7 +74,7 @@ class Splatter {
}
}

info.message = util.format(msg, ...splat);
info.message = util.format(msg, ...formatArgs);
return info;
}

Expand Down Expand Up @@ -97,8 +103,10 @@ class Splatter {
// This condition will take care of inputs with info[SPLAT]
// but no tokens present
if (!tokens && (splat || splat.length)) {
// Read-only: splat may be the same array referenced by info[SPLAT],
// shared across every transport's format pipeline for this log call.
const metas = splat.length > 1
? splat.splice(0)
? splat.slice(0)
: splat;

// Now that { splat } has been separated from any potential { meta }. we
Expand Down
33 changes: 33 additions & 0 deletions test/splat.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,4 +155,37 @@ describe('splat', () => {
}
));

// See: https://github.com/winstonjs/winston/issues/2365
// winston shallow-clones info per transport, so each transport's format
// pipeline gets its own info object but the same info[SPLAT] array
// reference. Mutating that array in place during the first transport's
// pass silently drops the meta for every subsequent transport.
it('does not mutate info[SPLAT], so a second transport still sees the meta', () => {
const sharedSplat = ['ball', 'cool', { extraData: 'traceId' }];
const infoA = { level: 'info', message: 'this %s is %s', [SPLAT]: sharedSplat };
const infoB = { level: 'info', message: 'this %s is %s', [SPLAT]: sharedSplat };

const first = splat().transform(infoA);
assume(first.message).equals('this ball is cool');
assume(first.extraData).equals('traceId');

const second = splat().transform(infoB);
assume(second.message).equals('this ball is cool');
assume(second.extraData).equals('traceId');
});

it('no tokens | does not mutate info[SPLAT] either', () => {
const sharedSplat = [{ extraData: 'traceId' }, { more: 'meta' }];
const infoA = { level: 'info', message: 'no tokens here', [SPLAT]: sharedSplat };
const infoB = { level: 'info', message: 'no tokens here', [SPLAT]: sharedSplat };

const first = splat().transform(infoA);
assume(first.extraData).equals('traceId');
assume(first.more).equals('meta');

const second = splat().transform(infoB);
assume(second.extraData).equals('traceId');
assume(second.more).equals('meta');
});

});