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.
Related
I am using winston-mongodb modlue to log the request response.
Logger.service.ts
const options = {
console: {
db: DB_URL,
level: 'info',
format: format.combine(format.timestamp(), format.json()),
collection: 'logs',
},
error: {
db: DB_URL,
level: 'error',
format: format.combine(format.timestamp(), format.json()),
collection: 'logs',
}
};
const logger = createLogger({
transports: [
new MongoDB(options.console),
new MongoDB(options.error)
],
});
Server.ts
server.listen(port, () => {
logger.info({message:`Server is up and running on port ${port}`, meta: {
myProp: 'foo'
}});
);
Result:
{
"_id" : ObjectId("5ed8bd2c726d273004b082ca"),
"timestamp" : ISODate("2020-06-04T09:21:48.835Z"),
"level" : "info",
"message" : "Server is up and running on port 4000 HI",
**"meta" : null**
}
I am trying to add meta data to logger. But it is adding null to the result.
To get err object stored in the meta (with all its javascript properties), you need to specify :
format.metadata()
In the createLogger call.
Here is a working sample code:
// Declare winston
const winston = require("winston");
// Import all needed using Object Destructuring
const { createLogger, format, transports } = require("winston");
const { combine, timestamp, printf } = format;
// Export the module
module.exports = function (err, req, res, next) {
const logger = 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 `${timestamp} ${level}: ${message} - ${stack}`;
}),
format.metadata() // >>>> ADD THIS LINE TO STORE the ERR OBJECT IN META field
),
transports: [
new transports.Console(), // show the full stack error on the console
new winston.transports.File({
// log full stack error on the file
filename: "logfile.log",
format: format.combine(
format.colorize({
all: false,
})
),
}),
new winston.transports.MongoDB({
db: "mongodb://localhost/logdb",
// collection: "log",
level: "error",
storeHost: true,
capped: true,
// metaKey: 'meta'
}),
],
});
logger.log({
level: "error",
message: err,
});
// Response sent to client but nothing related to winston
res.status(500).json(err.message);
};
try this
// create new meta object to store in database
let logMeta={
stack: err.stack, // error stack
otherInfo: otherInfoData // any other info you want to store
}
// use metadata propery for meta
logger.error(err.message, {metadata: logMeta });
OR another way, if you are exporting the function as error middleware (using nodejs, express):
module.exports = function (err, req, res, next) {
logger.log({
level: "error",
message: err.message,
stack: err.stack,
metadata: err.stack, // Put what you like as meta
});
res.status(500).send("Uncaught error caught here.");
};
Specify metadata object explicitly:
logger.error(err.message, {metadata: err.stack});
apparently i had the same problem where the meta property in mongoDB transport was null: This is how i fixed it....
const winston = require("winston")
const { format } = require("winstone")
winstone.add(new
winstone.transports.MongoDB({
format:format.metadata()})
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.
I'm trying to integrate winston to a nodeJs Express app. It works but I don't know how could it be auto maintained. I wanted something like when it reach, for example 50000 rows, it delete the first ones before adding the new ones for terms of space. The idea is doing that without something like cron. Is it possible?
Here is my testing code:
//Winston logger
const { createLogger, format, transports } = require('winston');
const { combine, timestamp, label, printf } = format;
const winstonFormat = printf(info => {
return `${info.timestamp} [${info.label}] ${info.level}: ${info.message}`;
});
const logger = createLogger({
level: 'info',
//maxsize:'10000', //It doesn't works for me
format: combine(
label({ label: config.environment }),
timestamp(),
winstonFormat
),
transports: [
new transports.File({ filename: 'error.log', level: 'error' }),
new transports.File({ filename: 'combined.log' })
]
});
if (process.env.NODE_ENV !== 'production') {
logger.add(new transports.Console({
format: format.simple()
}));
}
You're looking for log rotation, so googling "winston log rotation" points to https://github.com/winstonjs/winston-daily-rotate-file, which is probably just the ticket for you.
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
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 () {}
}
}