how to produce the same output with splat using winstonjs 3.2+ - node.js

I've been postponing upgrading winstonjs from 3.1.0 to 3.2.x, because I can't manage to get it to handle the extra meta data the same way.
For example, I have log messages throughout the app that include meta data in strings and object form.
In 3.1.0 this setup works well to create the output I'm looking for:
const { createLogger, format, transports } = require('winston');
const { combine, timestamp, label, printf, colorize } = format;
const formatStr = printf(info => {
return `[${info.timestamp}] ${info.level}\t ${info.label} ${info.message} ${JSON.stringify(info.meta)}`
});
let logger = createLogger({
transports: [new transports.Console({ level: 'info' })],
format: combine(
colorize({message: true, level: true}),
timestamp({format: 'MMM D, YYYY HH:mm'}),
format.splat(),
formatStr
)
});
logger.info('any message',{extra: true});
logger.info('simple', "one", "two",{extra: true});
Where the output is like this:
[Jan 3, 2020 14:36] info undefined any message {"extra":true}
[Jan 3, 2020 14:36] info undefined simple ["one","two",{"extra":true}]
But in 3.2.1 the closest I can get to my ideal log format is:
[Jan 3, 2020 14:31] info undefined any message {"extra":true,"timestamp":"Jan 3, 2020 14:31"}
[Jan 3, 2020 14:31] info undefined simple {"0":"t","1":"w","2":"o","timestamp":"Jan 3, 2020 14:31","extra":true}
using the following code:
const { createLogger, format, transports } = require('winston');
const { combine, timestamp, label, printf, colorize } = format;
// helper for stringifying all remaining properties
function rest(info) {
return JSON.stringify(Object.assign({}, info, {
level: undefined,
message: undefined,
splat: undefined,
label: undefined
}));
}
const formatStr = printf(info => {
return `[${info.timestamp}] ${info.level}\t ${info.label} ${info.message} ${rest(info)}`
});
let logger = createLogger({
transports: [new transports.Console({ level: 'info' })],
format: combine(
colorize({message: true, level: true}),
timestamp({format: 'MMM D, YYYY HH:mm'}),
format.splat(),
formatStr
)
});
logger.info('any message',{extra: true});
logger.info('simple', "one", "two",{extra: true});
With the removal of meta data in 3.2.0, https://github.com/winstonjs/winston/blob/master/CHANGELOG.md#new-splat-behavior, how am I suppose to make use of this with the existing logging structure that I have everywhere in my app?

I've encountered a similar issue. I'm using winston 3.2.1.
I found a solution by not using the splat format and retrieving it from the metadata. Here is an example of how to get the output you're looking for.
const formatMeta = (meta) => {
// You can format the splat yourself
const splat = meta[Symbol.for('splat')];
if (splat && splat.length) {
return splat.length === 1 ? JSON.stringify(splat[0]) : JSON.stringify(splat);
}
return '';
};
const customFormat = winston.format.printf(({
timestamp,
level,
message,
label = '',
...meta
}) => `[${timestamp}] ${level}\t ${label} ${message} ${formatMeta(meta)}`);
const logger = createLogger({
transports: [new transports.Console()],
format: combine(
format.colorize(),
format.timestamp({format: 'MMM D, YYYY HH:mm'}),
customFormat,
// Do not use splat format
)
});
logger.info('any message', { extra: true });
logger.info('simple', "one", "two", { extra: true });
The output:
[Mar 26, 2020 13:03] info any message {"extra":true}
[Mar 26, 2020 13:03] info simple ["one","two",{"extra":true}]

Related

Why error logs appears in warning logs file - Node.Js & Winston

In my Node.js REST API I am using Winston Logging to print the errors and info logs in a separated files.
This is my Winston setup:
const { createLogger, format, transports } = require("winston");
const { timestamp, printf, errors } = format;
const logFormat = printf(({ level, message, timestamp, stack }) => {
return `${timestamp} ${level}: ${stack || message}`;
});
const logger = createLogger({
transports: [
//new transports.Console(),
new transports.File({
level: "info",
filename: "logsWarnings.log",
}),
new transports.File({
level: "error",
filename: "logsErrors.log",
}),
],
format: format.combine(
timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
format.json(),
format.prettyPrint(),
errors({ stack: true })
//logFormat
),
statusLevels: true,
});
module.exports = logger;
This is an example of one of my requests (info log is for req details):
deleteUser: (req, res) => {
logger.info("This is an info log");
mySqlConnection.query(
"DELETE FROM Users WHERE user_id=?",
req.params.userid,
(err, rows) => {
try {
if (rows.affectedRows >= 1) {
res.send("user deleted successfully");
} else {
res.status(500).send("Can't delete row");
}
} catch (err) {
logger.error("this is error", { err });
}
}
);
},
I have a problem that error logs appears both in the error logs file and in the warning logs file. How can I fix it?
What is going on?
As mentioned in the documentation
Logging levels in winston conform to the severity ordering specified by RFC5424: severity of all levels is assumed to be numerically ascending from most important to least important.
const levels = {
error: 0,
warn: 1,
info: 2,
http: 3,
verbose: 4,
debug: 5,
silly: 6
};
and
winston allows you to define a level property on each transport which specifies the maximum level of messages that a transport should log.
What that means? For example:
You used "info" level for logsWarnings.log file. it means that you are going to log itself and more important levels into the file (info, warn and error).
For logsErrors.log file you used "error" level. That means it will only log the error level logs because "error" has the highest importance.
That is why you are getting both in your logsWarnings.log file.
Solution:
You can separate logger like:
...
const formatConf = format.combine(
timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
format.json(),
format.prettyPrint(),
errors({ stack: true })
//logFormat
);
const infoLogger = createLogger({
transports: [
//new transports.Console(),
new transports.File({
level: "info",
filename: "./logs/logsWarnings.log",
}),
],
format: formatConf,
statusLevels: true,
});
const errLogger = createLogger({
transports: [
new transports.File({
level: "error",
filename: "./logs/logsErrors.log",
}),
],
format: formatConf,
statusLevels: true,
});
...
And use like:
errLogger.error("this is error", { err });
// or
infoLogger.info("This is an info log");
For more customization, please see the documentation.

winston custom logs color

I wanna create custom log levels. Creation is correct and I can use them in the future, but messages are not colorized when I use custom levels. As I can see - colors are added before levels and that's why I can't use colors for custom levels. My code is bellow and when I use warn or custom - it crashed with error:
TypeError: colors[Colorizer.allColors[lookup]] is not a function
Also code of calling:
const Winston = require('./logger');
Winston.error('1 This is a info statement');
Winston.data('2 This is a warning statement');
Winston.info('3 This is a warning statement');
Winston.debug('4 This is a error statement');
Winston.verbose('6 This is a warning statement');
Winston.silly('7 This is a error statement');
Winston.warn('5 This is a debug statement');
Winston.custom('8 This is a error statement');
and logger.js
const winston = require('winston');
const colorizer = winston.format.colorize();
const {
combine, timestamp, printf, simple,
} = winston.format;
const myCustomLevels = {
levels: {
error: 0,
warn: 1,
data: 2,
info: 3,
debug: 4,
verbose: 5,
silly: 6,
custom: 7,
},
colors: {
error: 'red',
warn: 'orange',
data: 'grey',
info: 'green',
debug: 'yellow',
verbose: 'cyan',
silly: 'magenta',
custom: 'blue',
},
};
colorizer.addColors(myCustomLevels.colors);
const logger = winston.createLogger({
colorize: true,
prettyPrint: true,
level: 'custom',
levels: myCustomLevels.levels,
format: combine(
simple(),
printf(
(msg) => {
return colorizer.colorize(
msg.level,
`${msg.level} - ${msg.message}`,
);
},
),
timestamp(),
),
transports: [
new winston.transports.Console(),
],
});
module.exports = logger;
How I can use colorize with custom levels?
Usnig colorize:true will break your custom format, if you want to colorize all your log text you can do it manually like this:
const { combine, timestamp, label, printf } = winston.format;
const color = {
'info': "\x1b[36m",
'error': "\x1b[31m",
'warn': "\x1b[33m"
.
.
.
};
const myFormat = printf(({ level, message, label, timestamp }) => {
return `${level}: ${color[level] || ''} ${label} || ${timestamp} || ${message}\x1b[0m `;
});
then use this in createLogger function:
levels: myLevels,
format: combine(
winston.format.prettyPrint(),
winston.format.metadata(),
winston.format.json(),
label({ label }),
timestamp(),
myFormat
),
.
.
.

How to force nodejs winston log to file before process exit

I am using winston 3 to log my data. But sometimes it didn't log the error before process exit.
The following process will exit, without log to the logfile.log:
const winston = require('winston');
winston.add(new winston.transports.File({
filename: 'logfile.log'
}));
winston.info('please log me in');
process.exit(1);
One of attempted solution is used callback:
const winston = require('winston');
const logger = new winston.createLogger({
new winston.transports.File({
filename: 'logfile.log'
}));
winston.info('please log me in', () => {
process.exit(1);
});
setTimeout(() => {}, 100000000000); // pause the process until process.exit is call
But Winston 3.x have callback issue, so the above code won't work, the process will not exit.
I am looking for a workable solution? Any help will be greatly appreciated. My OS is Ubuntu 16.04, Node 10.17.
Edit 1:
I also have try Prabhjot Singh Kainth's suggestion use finish event to trigger process exit:
const winston = require('winston');
const logger = winston.createLogger({
transports: [
new winston.transports.File({
filename: 'logfile.log'
})
]
});
logger.info('please log me in');
logger.on('finish', () => {
process.exit();
});
logger.end();
setTimeout(() => {}, 100000000000); // pause the process until process.exit is call
In the above case, the process will exit, but no log file will be created.
How about this one?
logger.info('First message...')
logger.info('Last message', () => process.exit(1))
Finally, I find a workable solution. Instead of listening to logger finish event, it should listen to file._dest finish event. But file._dest is only created after file open event. So it need to wait for file open event in initialization process.
const winston = require('winston');
const file = new winston.transports.File({
filename: 'logfile.log'
});
const logger = winston.createLogger({
transports: [file]
});
file.on('open', () => { // wait until file._dest is ready
logger.info('please log me in');
logger.error('logging error message');
logger.warn('logging warning message');
file._dest.on('finish', () => {
process.exit();
});
logger.end();
});
setTimeout(() => {}, 100000000000); // pause the process until process.exit is call
Each instance of winston.Logger is also a Node.js stream.
A finish event will be raised when all logs have flushed to all transports after the stream has been ended.
Just try using this code :
const transport = new winston.transports.Console();
const logger = winston.createLogger({
transports: [transport]
});
logger.on('finish', function (info) {
// All `info` log messages has now been logged
});
logger.info('CHILL WINSTON!', { seriously: true });
logger.end();
The answer from #WanChap was good, but not when there is multiple file transports. I will post my entire logging solution below, which handles exiting the process correctly, including support for Development and Production.
The only caveat is that it does not work with the exceptions log at the moment, but it will work with every other transport based log. If anyone figures out how to make this work with the exception log as well, please post in the comments.
First, you want to create a folder in the same directory that your file that is doing the logs is in, and call it logger. In this folder, create three .js files, one called dev-logger.js, the other prod-logger.js, and finally index.js
In node, when you require a directory, it will assume you want to load the index.js file.
TheFileYouWantToLogFrom.js
const logger = require('./logger');
logger.info("it works!");
index.js
This file will look at your node process environment state and use the correct logger.
const buildDevLogger = require('./dev-logger');
const buildProdLogger = require('./prod-logger');
let logger = null;
if(process.env.NODE_ENV === 'development'){
logger = buildDevLogger();
}else{
logger = buildProdLogger();
}
module.exports = logger;
You can test this later, by simply calling your node process (in my case cron.js) in your terminal, by saying something like NODE_ENV=production node cron.js or NODE_ENV=development node cron.js
dev-logger.js
const { format, createLogger, transports} = require('winston');
const { timestamp, combine, printf, errors } = format;
var numOpenTransports = 0;
function buildDevLogger(){
const logFormat = printf(({ level, message, timestamp, stack, durationMs }) => {
return `${timestamp} ${level}: ${stack || message}${durationMs ? " | "+durationMs+'ms' : ''}`;
});
//For info on the options go here.
//https://github.com/winstonjs/winston/blob/master/docs/transports.md
var options = {
console: {
handleExceptions: true,
level: 'debug',
format: combine(
format.colorize(),
timestamp({format: 'YYYY-MM-DD HH:mm:ss'}),
errors({stack: true}),
logFormat
),
},
combined: {
filename: 'logs/dev/combined.log',
maxsize: 10000000,
maxFiles: 10,
tailable:true,
format: combine(
timestamp({format: 'YYYY-MM-DD HH:mm:ss'}),
errors({stack: true}),
logFormat
),
},
error: {
filename: 'logs/dev/error.log',
level: 'error',
maxsize: 10000000,
maxFiles: 10,
tailable:true ,
format: combine(
timestamp({format: 'YYYY-MM-DD HH:mm:ss'}),
errors({stack: true}),
logFormat
),
},
exception : {
filename: 'logs/dev/exceptions.log',
maxsize: 10000000,
maxFiles: 10,
tailable:true,
format: combine(
timestamp({format: 'YYYY-MM-DD HH:mm:ss'}),
errors({stack: true}),
logFormat
),
}
};
function fileFinished(){
numOpenTransports--;
if(numOpenTransports==0){
process.exit(0);
}
}
var combinedFile = new transports.File(options.combined);
var errorFile = new transports.File(options.error);
var exceptionFile = new transports.File(options.exception);
combinedFile.on('open', () => { // wait until file._dest is ready
numOpenTransports++;
combinedFile._dest.on('finish', () => {
fileFinished();
});
});
errorFile.on('open', () => { // wait until file._dest is ready
numOpenTransports++;
errorFile._dest.on('finish', () => {
fileFinished();
});
});
return createLogger({
defaultMeta: {service:'cron-dev'},
transports: [
new transports.Console(options.console),
combinedFile,
errorFile,
],
exceptionHandlers: [
exceptionFile
]
});
}
module.exports = buildDevLogger;
prod-logger.js
const { format, createLogger, transports} = require('winston');
const { timestamp, combine, errors, json } = format;
var numOpenTransports = 0;
function buildProdLogger(){
var options = {
console: {
handleExceptions: true,
level: 'debug',
format: combine(
timestamp({format: 'YYYY-MM-DD HH:mm:ss'}),
errors({stack: true}),
json()
),
},
combined: {
filename: 'logs/prod/combined.log',
maxsize: 10000000,
maxFiles: 10,
tailable:true,
format: combine(
timestamp({format: 'YYYY-MM-DD HH:mm:ss'}),
errors({stack: true}),
json()
),
},
error: {
filename: 'logs/prod/error.log',
level: 'error',
maxsize: 10000000,
maxFiles: 10,
tailable:true ,
format: combine(
timestamp({format: 'YYYY-MM-DD HH:mm:ss'}),
errors({stack: true}),
json()
),
},
exception : {
filename: 'logs/prod/exceptions.log',
maxsize: 10000000,
maxFiles: 10,
tailable:true,
format: combine(
timestamp({format: 'YYYY-MM-DD HH:mm:ss'}),
errors({stack: true}),
json()
),
}
};
function fileFinished(){
numOpenTransports--;
if(numOpenTransports==0){
process.exit(0);
}
}
var combinedFile = new transports.File(options.combined);
var errorFile = new transports.File(options.error);
var exceptionFile = new transports.File(options.exception);
combinedFile.on('open', () => { // wait until file._dest is ready
numOpenTransports++;
combinedFile._dest.on('finish', () => {
fileFinished();
});
});
errorFile.on('open', () => { // wait until file._dest is ready
numOpenTransports++;
errorFile._dest.on('finish', () => {
fileFinished();
});
});
return createLogger({
defaultMeta: {service:'cron-prod'},
transports: [
new transports.Console(options.console),
combinedFile,
errorFile,
],
exceptionHandlers: [
exceptionFile
]
});
}
module.exports = buildProdLogger;
The way the dev and prod logger files work is by using the numOpenTransports variable to keep track of which 'files transports' are currently open, and once they are all closed, then, and only then, exit the process.

log json with date and time format with winston node logger

I am trying to log json with winston that includes a timestamp, I have the following configuration:
'use strict';
import * as winston from 'winston';
const LOG = !!process.env.LOG;
export const { error, info, debug } = winston.createLogger({
transports: [new winston.transports.Console()],
silent: process.env.NODE_ENV === 'test' && !LOG,
format: winston.format.combine(
winston.format.timestamp({
format: 'YYYY-MM-DD HH:mm:ss'
}),
winston.format.colorize({ all: true }),
winston.format.simple()
)
});
But it is logging messages like this:
info: connecting /closebanner {"timestamp":"2018-08-22 22:09:35"}
Only the timestamp is in json format and not the message.
You can use winston json formatter:
const { createLogger, format, transports } = require('winston');
const { combine, timestamp, json } = format;
const logger = createLogger({
format: combine(
timestamp({
format: 'YYYY-MM-DD HH:mm:ss'
}),
json(),
),
transports: [new transports.Console()]
})
So for example:
app.get('/', (req, res) => {
logger.info('request came');
logger.error({ "errorMessage": "something went wrong " })
return res.status(500).end();
})
app.listen(8080, () => {
logger.info('app started')
})
When You request localhost:8080 using GET HTTP method, it will result in:
{"message":"app started","level":"info","timestamp":"2018-08-23 00:56:48"}
{"message":"request came","level":"info","timestamp":"2018-08-23 00:56:54"}
{"message":{"errorMessage":"something went wrong"},"level":"error","timestamp":"2018-08-23 00:56:54"}
Please note that:
colorize will not work with JSON - check github discussion
format.simple() is not needed since it is just another type (string literal), more info in doc

How do I change my node winston JSON output to be single line

When I create a nodejs winston console logger and set json:true, it always output JSON logs in multiline format. If I pipe these to a file and try to grep that file, my grep hits only include part of the log line. I want winston to output my log lines in JSON format, but not to pretty print the JSON
Here is my config (coffeescript, apologies):
winston = require 'winston'
logger = new (winston.Logger)(
transports: [
new winston.transports.Console({
json: true
})
]
)
And some sample output:
{
"name": "User4",
"level": "info",
"message": "multi line whyyyyy"
}
winston 3.x (current version)
Default formatter
const winston = require('winston');
const logger = winston.createLogger({
format: winston.format.json(),
transports: [
new winston.transports.Console()
]
});
Example
const test = { t: 'test', array: [1, 2, 3] };
logger.info('your message', test);
// logger output:
// {"t":"test","array":[1,2,3],"level":"info","message":"your message"}
Custom formatter
const winston = require('winston');
const { splat, combine, timestamp, printf } = winston.format;
// meta param is ensured by splat()
const myFormat = printf(({ timestamp, level, message, meta }) => {
return `${timestamp};${level};${message};${meta? JSON.stringify(meta) : ''}`;
});
const logger = winston.createLogger({
format: combine(
timestamp(),
splat(),
myFormat
),
transports: [
new winston.transports.Console()
]
});
Example:
const test = { t: 'test', array: [1, 2, 3] };
// NOTE: wrapping object name in `{...}` ensures that JSON.stringify will never
// return an empty string e.g. if `test = 0` you won't get any info if
// you pass `test` instead of `{ test }` to the logger.info(...)
logger.info('your message', { test });
// logger output:
// 2018-09-18T20:21:10.899Z;info;your message;{"test": {"t":"test","array":[1,2,3]}}
winston 2.x (legacy version)
It seems that the accepted answer is outdated. Here is how to do this for current winston version (2.3.1):
var winston = require('winston');
var logger = new (winston.Logger)({
transports: [
new (winston.transports.Console)({
json: true,
stringify: (obj) => JSON.stringify(obj),
})
]
})
Note the parenthesis around winston.transports.Console.
The winston transports provide a way to override the stringify method, so by modifying the config above I got single line JSON output.
New config:
winston = require('winston')
logger = new (winston.Logger)({
transports: [
new winston.transports.Console({
json: true,
stringify: (obj) => JSON.stringify(obj)
})
]
})
"winston": "^3.0.0"
function createAppLogger() {
const { combine, timestamp, printf, colorize } = format;
return createLogger({
level: 'info',
format: combine(
colorize(),
timestamp(),
printf(info => {
return `${info.timestamp} [${info.level}] : ${JSON.stringify(info.message)}`;
})
),
transports: [new transports.Console()]
});
}
Output:
2018-08-11T13:13:37.554Z [info] : {"data":{"hello":"Hello, World"}}
On "winston": "^3.2.1"
This is works great for me
const {createLogger, format} = require('winston');
// instantiate a new Winston Logger with the settings defined above
var logger = createLogger({
format: format.combine(
format.timestamp(),
// format.timestamp({format:'MM/DD/YYYY hh:mm:ss.SSS'}),
format.json(),
format.printf(info => {
return `${info.timestamp} [${info.level}] : ${info.message}`;
})
),
transports: [
new winston.transports.File(options.file),
new winston.transports.Console(options.console)
],
exitOnError: false, // do not exit on handled exceptions
});
winston.format.printf(
info => `${info.timestamp} ${info.level}: ${JSON.stringify(info.message, null, 2)}`))
will pretty print the json object

Resources