Winston Logging - separate levels to separate Transports - node.js

I am using Winston for logging with 2 different transports - File and MongoDB. I have set the level for File as "INFO" and for MongoDB as "ERROR". If I now log,
log.info('some info...');
log.warn('some Warning...');
log.error('some error...');
All of these would go to the LogFile, and only Error would go to DB. I want only Info messages to go to File, and none other.
I understand the system log levels in Winston, and that only Error goes to MongoDB because its the highest level. Since, INFO is a lower level, any log with the level INFO or higher goes to the file (as per my logger definiton)
I have read here but couldn't find an answer. Even if I create custom levels, how can I possibly restrict each transport to only one logging level?

I've answered this in another post:
According to Winston's documentation, the default behavior is to log all the messages which have at least the specifies importance aka logging level.
Winston allows you to define a level property on each transport which specifies the maximum level of messages that a transport should log.
But there are ways to achieve your requirements.
I'll try to show you some of the possibilities, you can choose the method that works the best for you.
1. Custom Transports (Recommended):
You can create a custom transport and log only the levels you want.
Here is an example just to give you an idea:
let mainLogger = new (winston.Logger)({
transports: [
new (winston.transports.Console)(),
]
});
class CustomTransport extends winston.Transport {
constructor(options) {
super(options);
this.name = 'customLogger';
this.level = options && options.level || 'info';
this.levelOnly = options && options.levelOnly;
this.levels = options && options.levels || [];
}
log(level, msg, meta, callback) {
if (!this.levelOnly || this.levels.indexOf(level) > -1) {
mainLogger[level](msg, meta);
}
callback(null, true);
}
}
winston.transports.CustomTransport = CustomTransport;
let myLogger = new winston.Logger({
transports: [
new (winston.transports.CustomTransport)({
levelOnly: true,
levels: ['info'],
}),
]
});
myLogger.info('will be logged');
myLogger.warn('will NOT be logged');
myLogger.info('will be logged as well');
2. Use winston-levelonly
This is a fork of the original winston package. The fork is at https://github.com/damianof/winston
This version adds a levelOnly option to make winston log only the specified level.
In the end, I would like to encourage you to read these relevant discussions:
https://github.com/winstonjs/winston/issues/614
https://github.com/winstonjs/winston/issues/812
https://github.com/winstonjs/winston/pull/628
Winston Logging - separate levels to separate Transports

Although a custom transport is also a good way of doing this, what I would recommend would be adding custom filters for each level you need.
In the example below I have custom levels where it will save to files in groups
errors, warn & info, debug, all.
const { format, createLogger, transports, addColors } = require("winston");
const { timestamp, combine, printf, errors, json } = format;
require("winston-daily-rotate-file");
function buildDevLogger() {
const consoleFormat = printf(({ level, message, timestamp, stack }) => {
return `${timestamp} ${level}: ${stack || message}`;
});
const infoAndWarnFilter = format((info, opts) => {
return info.level === "info" || info.level === "warn" ? info : false;
});
const errorFilter = format((info, opts) => {
return info.level === "error" ? info : false;
});
const debugFilter = format((info, opts) => {
return info.level === "debug" ? info : false;
});
const customLevels = {
levels: {
error: 0,
warn: 1,
info: 2,
debug: 3,
all: 4,
},
colors: {
error: "red",
warn: "yellow",
info: "green",
debug: "grey",
all: "white",
},
};
var logger = createLogger({
levels: customLevels.levels,
format: combine(
timestamp({ format: "DD-MM-YYYY HH:mm:ss" }),
errors({ stack: true }),
json(),
consoleFormat
),
exitOnError: false,
transports: [
new transports.Console({
format: combine(format.colorize(), _consoleFormat),
level: "all",
}),
new transports.File({
filename: "./logs/0-error.log",
level: "error",
format: combine(errorFilter(), _format),
}),
new transports.File({
filename: "./logs/warn-info.log",
level: "info",
format: combine(infoAndWarnFilter(), _format),
}),
new transports.DailyRotateFile({
filename: "./logs/%DATE% - debug.log",
datePattern: "DD-MM-YYYY",
zippedArchive: true,
level: "debug",
format: combine(debugFilter(), _format),
}),
new transports.DailyRotateFile({
filename: "./logs/%DATE% - general.log",
datePattern: "DD-MM-YYYY",
zippedArchive: true,
level: "all",
}),
],
});
addColors(customLevels.colors);
return logger;
}
module.exports = buildDevLogger;
This topic came first on my search when I needed and I hope the above could help a bit more.
For the example the infoAndWarnFilter will read the level of the generated log and if not info or warn it will return false. When combined in the format for the transports it will filter it
...
const infoAndWarnFilter = format((info, opts) => {
return info.level === "info" || info.level === "warn" ? info : false;
});
...
new transports.File({
filename: "./logs/warn-info.log",
level: "info",
format: combine(infoAndWarnFilter(), _format),
}),
...

see this answer. I have written a wrapper for winston which covers the basic api. it needs extending for logging metadata, but otherwise its a good start. of course you will have to adjust this for your MongoDB needs.

had a similiar issue and this is what I came up with. This can be done by just setting the log functions to empty functions. This is some code of mine I modified that did something similiar. This code takes customSilenceLevelLow and customSilenceLevelHigh and any log function with a value lower or equal to customSilenceLevelLow gets set to an empty function and any log function with value higher or equal to customSilenceLevelHigh gets set to an empty function
so in this code only logs of level info,info2 and info3 get logged to console. the rest do not.
NOTE: I didnt test these changes so there could be some syntax errors but the logic should be good.
winston = require("winston")
var levels = {levels: {
debug: 0,
debug2: 1,
debug3: 2,
verbose: 3,
verbose2: 4,
verbose3: 5,
info: 6,
info2: 7,
info3: 8,
silly: 9,
warn: 10,
error: 11
},
colors: {
debug: "blue",
debug2: "cyan",
debug3: "grey",
verbose: "cyan",
verbose2: "magenta",
verbose3: "blue",
info: "green",
info2: "magenta",
info3: "grey",
silly: "green",
warn: "yellow",
error: "red"
}}
//needed so that new levels and colors are recognized
winston.setLevels(levels.levels)
winston.addColors(levels.colors);
//add color to log text
winston.default.transports.console.colorize = true
winston.default.transports.console.prettyPrint = true
//false is default silences transport
winston.default.transports.console.silent = false
winston.default.transports.console.level = "debug"
var customSilenceLevelLow = "info"
var customSilenceLevelHigh = "info3"
for (var k in levels.levels) {
if (levels.levels[k] <= levels.levels[customSilenceLevelLow] || levels.levels[k] >= levels.levels[customSilenceLevelHigh]) {
//create an empty function to silence logs
winston[k] = function () {}
}
}

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 timer not printing time

I am running this example given in winston timer documentation -
const winston = require('winston');
const logger = winston.createLogger({
level: 'silly',
format: winston.format.simple(),
});
logger.add(new winston.transports.Console({
format: winston.format.simple(),
}));
require('winston-timer')(logger, {"useColors":false});
logger.start_log('something', 'silly');
logger.stop_log('something', 'silly');
and in the output I am supposed to see the time, but I am seeing this instead -
nodejstutorials % node winstontimerbasic.js
silly: Starting timer "something"
silly: Finished timer "something" in
I don't see the elapsed time in the output. I am not sure what am I missing while going through the documentation. Please help.
If you want to use timestamp option you should say that when you're creating winston instance.
Change your winston import with this:
const winston, { format } = require('winston');
Destructure required properties from format:
const { combine, timestamp, printf } = format;
Create custom print option when you're creating your winston instance:
const logger = winston.createLogger({
level: "error",
format: combine(
format.errors({ stack: true }), // log the full stack
timestamp(), // get the time stamp part of the full log message
printf(({ level, message, timestamp, stack }) => {
// formating the log outcome to show/store
return `${level === "error" ? "❌" : "✅"} ${level}: ${message} ${
level === "error" ? "| 🕗 " + timestamp.substring(0, 19) : ""
} ${"\n"} ${level === "error" ? stack : ""}`;
}),
format.metadata() // >>>> ADD THIS LINE TO STORE the ERR OBJECT IN META field
),
transports: [
new winston.transports.Console({ level: "info" }),
new winston.transports.File({ filename: "logs.log", level: "info" }),
],
});
This will create a custom printf option for your winston logger. You can export it and you it like:
logger.error(error)
logger.info("this is an info")
And of course you can custumize printf option accordion to your taste

how to produce the same output with splat using winstonjs 3.2+

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}]

How to add session id to each log in winston logger

In my node application I'm using winston module to store my application logs. I am getting the log as:
2017-11-22T07:16:38.632Z - info: asset type is attached successfully
Now I want to add sessionID after timestamp. I want my log as:
2017-11-22T07:16:38.632Z -**sessionId here**- info: asset type is attached successfully.
My code which I used for winston logging is:
var winston = require('winston');
require('winston-daily-rotate-file');
const levels = {
error: 0,
warn: 1,
info: 2,
http: 3,
verbose: 4,
debug: 5,
silly: 6,
trace: 7
};
var transport = new (winston.transports.DailyRotateFile)({
filename: 'logs/./log',
datePattern: 'yyyy-MM-dd.',
prepend: true,
json: false,
level: process.env.ENV === 'development' ? 'debug' : 'info'
});
var logger = new (winston.Logger)({
levels: levels,
transports: [
transport
]
});
module.exports = logger;
You have to custom the log format https://github.com/winstonjs/winston/tree/2.x#custom-log-format
First update your transport :
var transport = new (winston...
...
level: process.env.ENV === 'development' ? 'debug' : 'info',
timestamp: () => {
let today = new Date();
return today.toISOString();
},
formatter: options => `${options.timestamp()} -${options.meta.sessionId}- ${options.level}: ${options.message}`
});
Then, just pass the session ID to your logger meta feature :
logger.info('asset type is attached successfully', {sessionId: 'mySessionID'});
and you get
2017-11-22T14:13:17.697Z -mySessionID- info: asset type is attached successfully
Edit : instead of exporting only the winston.logger object, we export an object which requires a sessionId as a parameter, and contains the winston.logger. We also update the transport, so we customize its formatter property in the new Logger object. In the formatter property, we replace the meta declaration with the new this.sessionId variable, so we don't use the meta property anymore.
logger.js :
var transport = new (winston...
...
level: process.env.ENV === 'development' ? 'debug' : 'info',
timestamp: () => {
let today = new Date();
return today.toISOString();
}
});
class Logger {
constructor(session) {
this.sessionId = session;
this.transport = transport;
this.transport.formatter = options => `${options.timestamp()} -${this.sessionId}- ${options.level}: ${options.message}`;
this.logger = new (winston.Logger)({
levels: levels,
transports: [this.transport]
});
}
}
module.exports = Logger;
server.js :
const Logger = require('./logger.js');
let logman = new Logger('my_session_id');
let logger = logman.logger;
logger.info('asset type is attached successfully');
2017-11-23T13:13:08.769Z -my_session_id- info: asset type is attached successfully
I had the same request and wasn't wild about the solution... I liked the way the standard logger was formatting logs and didn't want to reinvent that wheel. Also, I was hoping for something more compact and I think I found it. I'm not a javascript programmer, so it might not be good coding practice, but it seems to work well for me...
server.js
//Initiate winston logging please
const logger = require('winston');
const common = require('winston/lib/winston/common');
function myFormat(options) {
options.formatter = null
options.label = helpers.getSessionId(logger.req)
return common.log(options);
}
var consoleLoggingConfig = {
timestamp: true,
level: process.env.LOG_LEVEL ? process.env.LOG_LEVEL : "info",
handleExceptions: true,
humanReadableUnhandledException: true,
formatter: myFormat
}
logger.remove(logger.transports.Console);
logger.add(logger.transports.Console, consoleLoggingConfig)
logger.info('Winston logging initiated', consoleLoggingConfig)
//Helper to ensure that logger has access to the request object to obtain the session id
app.use(helpers.attachReqToLogger)
module.exports=logger
helpers.js
// Helper to ensure that the winston logger has the request object on it to obtain the sessionId
helpers.attachReqToLogger = function(req, res, next) {
logger.req = req
next();
}
import winston from "winston";
import { v4 as uuidv4 } from "uuid";
const { format } = winston;
const commonFormat = [format.timestamp(), format.json()];
const withId = [
format.printf(({ message, id, ...rest }) => {
return JSON.stringify(
{
// if frontend privide id, use it, else create one
id: id ?? uuidv4(),
message,
...rest,
},
null,
// prettyPrint seems will overload printf's output
// so i mannually do this
4,
);
}),
];
export const logger = winston.createLogger({
level: "debug",
format: format.combine(...commonFormat, ...withId),
// ...

Add module name in winston log entries

Is there a possibility to save the current module name in order to be printed automatically in winston log entries when they are called later?
Currently, when I want to print the module name in logs, I have to add it manually:
var logHeader = 'mymodule'
log.info(logHeader + 'Hello')
For example, with debug, you can do (ignore the log format feature %s for now):
var debug = require('debug')('http')
, name = 'My App'
debug('booting %s', name);
This will prin http prefix before the log:
http booting My App
Can this be done in winston? I have searched in the documentation but I couldn't find anything relevant.
I found a better way to do this.
I added an additional layer over the winston logger, in this case a function, that keeps the module name for each module that needs the logger. So when a module requires my new logger, it actually calls the exported function with the module name, in this case __filename.
log.js
var winston = require('winston')
var winstonLogger = new (winston.Logger)({
transports: [
new (winston.transports.File) ({
filename: 'MyLogs.txt',
handleExceptions: true,
humanReadableUnhandledException: true,
level: 'info',
timestamp: true,
json: false
}),
new (winston.transports.Console) ({
level: 'info',
prettyPrint: true,
colorize: true,
timestamp: true
})
]
})
module.exports = function(fileName) {
var myLogger = {
error: function(text) {
winstonLogger.error(fileName + ': ' + text)
},
info: function(text) {
winstonLogger.info(fileName + ': ' + text)
}
}
return myLogger
}
module1.js
var log = require('log')(__filename)
log.info('Info log example')
info: C:\Users\user1\project\module1.js: Info log example
module2.js
var log = require('log')(__filename)
log.error('Error log example')
error: C:\Users\user1\project\module2.js: Error log example
Also, this way, I didn't need to change anywhere the way I submit a text log; log.info('text') stays exactly like before.
This is what Child Loggers are for. They might not have been available when this question was asked, but for future reference, you can create your main logger instance and then export a function that creates a child logger with some default options.
// logging.js
const winston = require('winston')
const logger = winston.createLogger({
transports: [
new winston.transports.Console({
format: winston.format.printf(options => {
// you can pass any custom variable in options by calling
// logger.log({level: 'debug', message: 'hi', moduleName: 'my_module' })
return `[${options.moduleName}] ${options.level}: ${options.message}$`;
})
})
]
});
module.exports = function(name) {
// set the default moduleName of the child
return logger.child({moduleName: name});
}
Then at the top of each module, import the child using:
// my_module.js
const logger = require('./logging.js')('my_module_name');
logger.error('computer says no');
// output:
// [my_module_name] error: computer says no
You can specify a custom log format with Winston -
var moduleName = 'myModule';
var logger = new (winston.Logger)({
transports: [
new (winston.transports.Console)({
formatter: function(options) {
// Return string will be passed to logger.
return moduleName + ' - ' + (options.message ? options.message : '')
}
})
]
});
logger.info('This is a log message');
This will print -
myModule - This is a log message
So your module name will be appended to every log messsage.
With winston v3.3.3 you can do this by using winston.format.label and a customization of winston.format.printf:
const winston = require('winston');
const logger = winston.createLogger({
format: winston.format.combine(
winston.format.label({label: 'mymodule'}),
winston.format.printf(({label, message}) => {
return `${label}: ${message}`;
})
),
transports: [
new winston.transports.Console(),
],
});
logger.info('Hello, World!'); // prints "mymodule: Hello, World!"

Resources