winston version?_winston@2winston@3 node -v outputs: v8.11.3Adding multiple transports to a logger causes the info-object to loose meta field on all but one transport.:
const Winston = require("winston");
Winston.add(new Winston.transports.Console({
format: Winston.format.combine(Winston.format(info => {
info.message = info.message + "Console";
return info;
})(), Winston.format.splat(), Winston.format.simple(), Winston.format(info => {
// console.log(info); // meta is there!
return info;
})()),
handleExceptions: true
}));
Winston.add(new Winston.transports.File({
filename: "test.log",
format: Winston.format.combine(Winston.format(info => {
// console.log(info); // meta is gone :(! and so is SPLAT only a array of length 1
return info;
})(), Winston.format.splat(), Winston.format.simple())
}));
for (let index = 0; index < 10; index++) {
Winston.error("test %s" + index, "blub", "metainfo");
}
Output Console:
error: test blub0Console {"meta":"metainfo"}
error: test blub1Console {"meta":"metainfo"}
error: test blub2Console {"meta":"metainfo"}
error: test blub3Console {"meta":"metainfo"}
error: test blub4Console {"meta":"metainfo"}
error: test blub5Console {"meta":"metainfo"}
error: test blub6Console {"meta":"metainfo"}
error: test blub7Console {"meta":"metainfo"}
error: test blub8Console {"meta":"metainfo"}
error: test blub9Console {"meta":"metainfo"}
Output File:
error: test blub0
error: test blub1
error: test blub2
error: test blub3
error: test blub4
error: test blub5
error: test blub6
error: test blub7
error: test blub8
error: test blub9
Sometimes one transport gets the meta and the other doesn't and for the next logentry its the other way around.
Each transport should get a clean "new" info object. At least with a full SPLAT. So {"meta":"metainfo"} is included in File too.
I tried for a moment to find the issue in debugger, but got lost in the streams. So please excuse me, if this issue belongs more to winston-transports. I try to investigate this further.
It seems the info[SPLAT] is preserved from one Transport to the other, but due to the splat() formatter th metadata is removed. Maybe its more related to the splat() altering the array than i thought...
As a workaround i will push the metadata back to SPLAT after splat():
const splatWorkaround = Winston.format.combine(
Winston.format.splat(),
Winston.format(info => {
if (info.meta && info.meta instanceof Array) info[SPLAT].push(...info.meta);
else if (info.meta) info[SPLAT].push(info.meta);
return info;
})()
);
To get this working i will use this workaround:
I was about to open a new issue regarding this exact same issue, and then I found this one. I'll post my description as well. :)
winston version?_node -v outputs:_ v8.12.0The custom data added by the splat formatter as the field meta in the log message is dropped when all the following conditions are met:
%s)format is applied on each transportTo replicate the bug, see the code:
https://gist.github.com/avaly/527eee1e061accb7e1f446ca105c3094#file-run-with-bug-js
Which outputs:
{"level":"info","message":"foo","meta":["bar","ham"]}
{"level":"info","message":"foo"}
{"level":"info","message":"with interpolated strings: bar and ham"}
{"level":"info","message":"with interpolated strings: bar and ham"}
Expected output:
{"level":"info","message":"foo","meta":["bar","ham"]}
{"level":"info","message":"foo","meta":["bar","ham"]}
{"level":"info","message":"with interpolated strings: bar and ham"}
{"level":"info","message":"with interpolated strings: bar and ham"}
If we move the custom format to the top level logger, then the bug does not present itself:
https://gist.github.com/avaly/527eee1e061accb7e1f446ca105c3094#file-run-without-bug-js
But, most of the time, different transports do require different formatting.
i did some digging and i think i found the cause.
The issue is located in the winston stream module:
https://github.com/winstonjs/winston-transport/blob/master/index.js#L90
# index.js#L90
TransportStream.prototype._write = function _write(info, enc, callback) {
...
// We trap(and re-throw) any errors generated by the user-provided format, but also
// guarantee that the streams callback is invoked so that we can continue flowing.
try {
transformed = this.format.transform(Object.assign({}, info), this.format.options);
} catch (err) {
errState = err;
}
...
};
They use Object.assign to create a shallow copy of the info object so the "SPLAT" array gets modified by the formatters.
To fix this we would need to do a deep clone here instead of an Object.assign
Proof:
let clone = Object.assign({}, info);
clone[SPLAT] = [...info[SPLAT]]
transformed = this.format.transform(clone, this.format.options);
fixes the splat issue in your test.
i think i figured out a workaround for now. You can try adding this to your scripts to make the transport use a "deepclone" instead of making shallow clone
you can try this as a workaround for now:
const deepcopy = require('deepcopy');
const TransportStream = require('winston-transport');
const { LEVEL } = require('triple-beam');
TransportStream.prototype._write = function _write(info, enc, callback) {
if (this.silent || (info.exception === true && !this.handleExceptions)) {
return callback(null);
}
// Remark: This has to be handled in the base transport now because we
// cannot conditionally write to our pipe targets as stream. We always
// prefer any explicit level set on the Transport itself falling back to
// any level set on the parent.
const level = this.level || (this.parent && this.parent.level);
if (!level || this.levels[level] >= this.levels[info[LEVEL]]) {
if (info && !this.format) {
return this.log(info, callback);
}
let errState;
let transformed;
// We trap(and re-throw) any errors generated by the user-provided format, but also
// guarantee that the streams callback is invoked so that we can continue flowing.
try {
transformed = this.format.transform(deepcopy(info), this.format.options);
} catch (err) {
errState = err;
}
if (errState || !transformed) {
// eslint-disable-next-line callback-return
callback();
if (errState) throw errState;
return;
}
return this.log(transformed, callback);
}
return callback(null);
};
@Fantus nice workaround, thank you!
I have a PR in for this. Waiting for review from @DABH and @indexzero. https://github.com/winstonjs/logform/pull/85
Using this as a workaround
const deepclone = require('deepclone')
const deepCloneSplat = format((info) => {
const cloned = deepclone(info)
return format.splat().transform(cloned)
})
Use deepCloneSplat in place of format.Splat()
Most helpful comment
I was about to open a new issue regarding this exact same issue, and then I found this one. I'll post my description as well. :)
Please tell us about your environment:
winstonversion?_[email protected]node -voutputs:_v8.12.0What is the problem?
The custom data added by the
splatformatter as the fieldmetain the log message is dropped when all the following conditions are met:%s)formatis applied on each transportTo replicate the bug, see the code:
https://gist.github.com/avaly/527eee1e061accb7e1f446ca105c3094#file-run-with-bug-js
Which outputs:
What do you expect to happen instead?
Expected output:
Other information
If we move the custom
formatto the top level logger, then the bug does not present itself:https://gist.github.com/avaly/527eee1e061accb7e1f446ca105c3094#file-run-without-bug-js
But, most of the time, different transports do require different formatting.