WinstonJS custom level to file - node.js

I changed the default levels in Winston.JS, to remove 2 levels and add 1 custom level. I want to log that new level in an separated file. So that file will only contains logs from that level.
const appRoot = require('app-root-path');
const winston = require('winston');
require('winston-daily-rotate-file');
const levels = {
levels: {
product: 0,
error: 1,
warn: 2,
info: 3,
debug: 4,
product: 5,
},
colors: {
product: 'magenta',
error: 'red',
warning: 'yellow',
info: 'green',
debug: 'blue',
}
}
const logger = winston.createLogger({
levels: levels.levels,
format: winston.format.combine(
winston.format.colorize(),
winston.format.timestamp({
format: 'DD-MM-YYYY HH:mm:ss'
}),
winston.format.printf(info => `[${info.timestamp}] [${info.level}]: ${info.message}`)
),
transports: [
new winston.transports.DailyRotateFile({
filename: `${appRoot}/logs/error-%DATE%.log`,
level: 'error',
datePattern: 'YYYY-MM-DD',
maxSize: '20m',
maxFiles: '14d'
}),
new winston.transports.DailyRotateFile({
filename: `${appRoot}/logs/product-%DATE%.log`,
level: 'product',
datePattern: 'YYYY-MM-DD',
maxSize: '20m',
maxFiles: '14d'
})
]
})
winston.addColors(levels.colors);
logger.stream = {
write: (message, encoding) => {
logger.info(message);
}
}
In this case, I want to place all the console.product() logs in the product-%DATE%.log file.
Currently the file is filled, but only with the information which is also showed in the log about from express (app.use(morgan("combined", {stream: winston.stream}));)
In development I also adding an extra transport for debug on the console:
let env = process.env.NODE_ENV;
if (env !== 'production'){
logger.add(new winston.transports.Console({
level: 'debug',
prettyPrint: function ( object ){
return JSON.stringify(object);
}
}))
}
How can I get only the console.product() logs into the product.log file?

I have fixed this with the help of this code snippet: https://github.com/winstonjs/winston/issues/614#issuecomment-405015322
So my whole config now looks like this:
const appRoot = require('app-root-path');
const winston = require('winston');
const path = require('path')
require('winston-daily-rotate-file');
const levels = {
levels: {
product: 1,
error: 1,
warn: 3,
info: 4,
debug: 5
},
colors: {
product: 'magenta',
error: 'red',
warning: 'yellow',
info: 'green',
debug: 'blue',
}
}
let getLabel = function(callingModule){
//To support files which don't have an module
if(typeof callingModule == 'string'){
return callingModule;
}
let parts = callingModule.filename.split(path.sep);
return path.join(parts[parts.length -2], parts.pop());
}
const errorFilter = winston.format( (info, opts) => {
return info.level == 'error' ? info : false;
})
const productFilter = winston.format( (info, opts) => {
return info.level == 'product' ? info : false
})
module.exports = function(callingModule){
const logger = winston.createLogger({
levels: levels.levels,
format: winston.format.combine(
winston.format.colorize(),
winston.format.timestamp({
format: 'DD-MM-YYYY HH:mm:ss'
}),
winston.format.label({label: getLabel(callingModule)}),
winston.format.printf(info => `[${info.timestamp}] [${info.level}] [${info.label}]: ${info.message}`)
),
transports: [
new winston.transports.DailyRotateFile({
name: "Error logs",
filename: `${appRoot}/logs/error-%DATE%.log`,
level: 'error',
label: getLabel(callingModule),
datePattern: 'YYYY-MM-DD',
maxSize: '20m',
maxFiles: '14d',
format: winston.format.combine(
errorFilter(),
winston.format.colorize(),
winston.format.timestamp({
format: 'DD-MM-YYYY HH:mm:ss'
}),
winston.format.label({label: getLabel(callingModule)}),
winston.format.printf(info => `[${info.timestamp}] [${info.level}] [${info.label}]: ${info.message}`)
),
}),
new winston.transports.DailyRotateFile({
name: "Product logs",
filename: `${appRoot}/logs/product-%DATE%.log`,
level: 'product',
label: getLabel(callingModule),
datePattern: 'YYYY-MM-DD',
maxSize: '20m',
maxFiles: '14d',
format: winston.format.combine(
productFilter(),
winston.format.colorize(),
winston.format.timestamp({
format: 'DD-MM-YYYY HH:mm:ss'
}),
winston.format.label({label: getLabel(callingModule)}),
winston.format.printf(info => `[${info.timestamp}] [${info.level}] [${info.label}]: ${info.message}`)
),
})
]
})
winston.addColors(levels.colors);
let env = process.env.NODE_ENV;
if (env !== 'production'){
logger.add(new winston.transports.Console({
name: "Debug logs",
level: 'debug',
label: getLabel(callingModule),
prettyPrint: function ( object ){
return JSON.stringify(object);
}
}))
}
else if (env !== 'development') {
logger.add(winston.transports.DailyRotateFile({
name: "Combined logs",
filename: `${appRoot}/logs/combined-%DATE%.log`,
level: 'error',
datePattern: 'YYYY-MM-DD-HH',
maxSize: '20m',
maxFiles: '14d'
}))
}
logger.stream = {
write: (message, encoding) => {
logger.info(message);
}
}
return logger;
}
I also noticed that I was declaring the product level twice with priority 1 and 5.
On the different transports, I added an filter which is based on the code which I found on Github. It will filter the printed logs to only the set level.
To show the filename of the calling file in the log I added the callingModule.
Now I can require the console with
const console = require(path.join(__dirname, 'winston.config'))(module);
The call
console.info('NODE_ENV is undefined or its value was not understood. Default to development mode. ');
Will look as [28-08-2019 11:39:51] [info] [config\env.config.js]: NODE_ENV is undefined or its value was not understood. Default to development mode. in the log.

Related

Winson with timestamp not adding timestamps

I created a simple Winston logger for my node application, but the configuration with timestamps not working, what it means? It means that all of the logs isn't with the timestamp.
Example:
const { createLogger, format, transports } = require('winston');
const logger = createLogger({
transports: [
new transports.File({
maxsize: 5120000,
maxFiles: 20,
filename: `logs/logs.log`,
colorize: true,
json: true,
timestamp: true
}),
new transports.Console({
level: "debug",
timestamp: true,
format: format.combine(
format.colorize(),
format.simple(),
format.timestamp()
)
})
]
});
module.exports = { logger };
simple() format does not output timestamp. so you should define a custom format using printf:
const { createLogger, format, transports } = require('winston');
const myFormat = format.printf(({ level, message, label, timestamp }) => {
return `${timestamp} [${label}] ${level}: ${message}`;
});
const logger = createLogger({
transports: [
new transports.File({
maxsize: 5120000,
maxFiles: 20,
filename: `logs/logs.log`,
timestamp: true,
json: true,
}),
new transports.Console({
level: "debug",
timestamp: true,
format: format.combine(
format.timestamp(),
format.colorize(),
myFormat,
)
})
]
});
module.exports = { logger };
i also moved timestamp() format before the myFormat as it should fill timestamp of message before it reaches the printf.

Logging metadata in Winston with custom formatter

I'm trying to log stacktrace along with error message using Winston.
My logger is configured with custom formatter:
this.errorLogger = winston.createLogger({
levels: this.levels,
level: 'error',
transports: [
new WinstonFileRotator({
filename: '%DATE%.log',
dirname: 'logs/error',
zippedArchive: true,
maxSize: '20m',
maxFiles: '14d',
handleExceptions: true,
json: false,
format: winston.format.combine(
winston.format.timestamp({
format: 'YYYY-MM-DD HH:mm:ss'
}),
winston.format.printf(info => {
return '[${info.timestamp}] -> ${info.message}';
}),
),
})
]
});
I log the error along with stacktrace:
this.errorLogger.error('My message', ex.Stack);
In my log I have a line:
[2018-09-03 23:41:14] -> My message
How can I access in my custom formatter the metadata that I passed to error function along with the message?
I have been working on a similar issue. In the end, I did:
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.label({ label: 'MY-SILLY-APP' }),
winston.format.timestamp(),
winston.format.metadata({ fillExcept: ['message', 'level', 'timestamp', 'label'] }),
winston.format.colorize(),
winston.format.printf(info => {
let out = `${info.timestamp} [${info.label}] ${info.level}: ${info.message}`;
if (info.metadata.error) {
out = out + ' ' + info.metadata.error;
if (info.metadata.error.stack) {
out = out + ' ' + info.metadata.error.stack;
}
}
return out;
}),
),
transports: [
new winston.transports.Console()
]
});
logger.info('running');
try {
throw new Error('failed');
} catch (err) {
logger.error('failing', { error: err });
}
logger.info('stopping');

Winstonjs printing json to console

logger.js
var winston = require('winston');
var logger = new winston.createLogger({
transports: [
new winston.transports.Console({
level: 'info',
handleExceptions: true,
json: false,
colorize: true,
timestamp: true
}),
new winston.transports.Console({
level: 'error',
handleExceptions: true,
json: false,
colorize: true,
timestamp: true
}),
],
exitOnError: false
});
module.exports = logger;
module.exports.stream = {
write: function(message, encoding){
logger.info(message);
}
};
Whenever I call logger.info or logger.error, it'll log a json object to the screen without colors. What is wrong with my logger that is causing this issue? The timestamp isn't printing on this as well.
Looks like you are mixing the old syntax with the v3 syntax. According to the documentation you can write it something like this:
const { createLogger, format, transports } = require('winston');
const { combine, timestamp, printf, colorize } = format;
const myFormat = printf(info => {
return `${info.timestamp} ${info.level}: ${info.message}`;
});
const logger = createLogger({
format: combine(
colorize(),
timestamp(),
myFormat
),
transports: [
new transports.Console({
level: 'info',
handleExceptions: true
}),
new transports.Console({
level: 'error',
handleExceptions: true
}),
],
exitOnError: false
});
module.exports = logger;
module.exports.stream = {
write: function(message, encoding){
logger.info(message);
}
};
This will show timestamps and colors.
There is also an upgrade guide here that shows the differences between v2 and v3.

How add two or more logger using different files?

I am working on node Js logging library. I am using winston for this.I created log files but I stuck when I want to create two log files.Suppose I have to create all log file which contain all logs and error log file which contain only error logs.But I stuck here:
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir);
}
const tsFormat = () => (new Date()).toLocaleTimeString();
let logger = new(winston.Logger)({
transports: [
new(winston.transports.Console)({
level: 'debug',
timestamp: tsFormat,
handleExceptions: true,
colorize: true,
json: false
}),
new(winston.transports.File)({
name: 'all-file',
handleExceptions: true,
filename: `${logDir}/all-file.log`,
level: 'debug',
maxsize: 100000000,
json: true
}),
new(winston.transports.File)({
name: 'error-file',
handleExceptions: true,
filename: `${logDir}/error-file.log`,
level: 'error',
maxsize: 100000000,
json: false
})
]
});
I want that error log show different type of console than debug console.But by default its only taking debug one. how can I manually add console config for both files.When I try to add another transport.console it shows error.
Transport already attached: console, assign a different name
var winston = require('winston');
let consoleLogger = function(options){
return new (winston.Logger)({
transports: [
new (winston.transports.Console)(options),
]
});
}
// the CustomTransport is to prevent error log show multiple times
class CustomTransport extends winston.Transport {
constructor(options) {
super(options);
this.options = options;
this.levels = options && options.levels || [this.level];
}
log(level, msg, meta, callback) {
if(this.levels.indexOf(level) > -1){
consoleLogger(this.options)[level](msg, meta);
}
callback(null, true);
}
}
winston.transports.CustomTransport = CustomTransport;
const tsFormat = () => (new Date()).toLocaleTimeString();
var logger = new (winston.Logger)({
transports: [
// add name attribute to prevent Transport already attached error
// no error log for this transport
new (winston.transports.CustomTransport)(
{
name: 'info-console',
level: 'debug',
levels : ['debug', 'verbose', 'info', 'warn'],
timestamp: tsFormat,
handleExceptions: true,
colorize: true,
json: false
}
),
// only error log for this transport, modify the configuration as you need
new (winston.transports.CustomTransport)(
{
name: 'error-console',
level: 'error',
timestamp: tsFormat,
handleExceptions: true,
colorize: true,
json: false
}
),
new (winston.transports.File)(
{
name: 'info-file',
filename: 'info.log',
timestamp: true,
maxsize: 1024000,
level: 'info'
}),
new (winston.transports.File)(
{
name: 'error-file',
filename: 'error.txt',
timestamp: true,
maxsize: 1024000,
level: 'error'
})
]});
usage:
logger.debug('debug');
logger.error('error');

Winston logger to log to two files

I have two different winston loggers which look like this (there are two separate ones as they write different logs depending on my functions) - :
var security = new(winston.Logger)({
transports: [
new(require('winston-daily-rotate-file'))({
filename: logDir + '/-security.log',
datePattern: 'dd-MM-yyyy',
prepend: true,
json: false,
timestamp: function() {
return moment().format('D/MM/YYYY HH:mm:ss:SSS');
}
})
]
});
And then I have one more for :
var system = new(winston.Logger)({
transports: [
new(require('winston-daily-rotate-file'))({
filename: logDir + '/-security.log',
datePattern: 'dd-MM-yyyy',
prepend: true,
json: false,
timestamp: function() {
return moment().format('D/MM/YYYY HH:mm:ss:SSS');
}
})
]
});
However, I also have a verbose.log file, where I want all of the logs from the security and system log file to be written to the verbose too. Whats the best way of doing that?
I tried adding the filename (i.e. verbose.log) to each of the transports, but this does not work as I get the error
Error: Transport already attached: dailyRotateFile, assign a different name
It is a common problem with winston, but a simple one to solve.
Due to how winston is coded, it cannot differenciate your two transport himself, and need you to explicitly declare them as separate by giving them different names:
var security = new(winston.Logger)({
transports: [
new(require('winston-daily-rotate-file'))({
//=>
name: 'foo',
//<=
filename: logDir + '/-security.log',
datePattern: 'dd-MM-yyyy',
prepend: true,
json: false,
timestamp: function() {
return moment().format('D/MM/YYYY HH:mm:ss:SSS');
}
})
]
});
var system = new(winston.Logger)({
transports: [
new(require('winston-daily-rotate-file'))({
//=>
name: 'bar',
//<=
filename: logDir + '/-security.log',
datePattern: 'dd-MM-yyyy',
prepend: true,
json: false,
timestamp: function() {
return moment().format('D/MM/YYYY HH:mm:ss:SSS');
}
})
]
});
The name itself doesn't matter as long as it is different, a easy way to do that is to use the filename along with some other identifier for the transport.
Source: issue #101 of the official Github
EDIT:
var verbose = new(require('winston-daily-rotate-file'))({
name: 'baz',
filename: logDir + '/-verbose.log',
datePattern: 'dd-MM-yyyy',
prepend: true,
json: false,
timestamp: function() {
return moment().format('D/MM/YYYY HH:mm:ss:SSS');
}
})
var security = new(winston.Logger)({
transports: [
new(require('winston-daily-rotate-file'))({
//=>
name: 'foo',
//<=
filename: logDir + '/-security.log',
datePattern: 'dd-MM-yyyy',
prepend: true,
json: false,
timestamp: function() {
return moment().format('D/MM/YYYY HH:mm:ss:SSS');
}
}),
//=>
verbose
//<=
]
});
var system = new(winston.Logger)({
transports: [
new(require('winston-daily-rotate-file'))({
//=>
name: 'bar',
//<=
filename: logDir + '/-security.log',
datePattern: 'dd-MM-yyyy',
prepend: true,
json: false,
timestamp: function() {
return moment().format('D/MM/YYYY HH:mm:ss:SSS');
}
}),
//=>
verbose
//<=
]
});
You can filter out logs by creating two or more loggers as per your requirement and adding name property to them. Adding a common transport function in both logger worked for me.
const winston = require('winston');
const combinedLogger = new winston.transports.File({
name: "data",
filename: "./logs/combined.log",
level: "info",
})
const infoLogger = winston.createLogger({
defaultMeta: { service: "log-service" },
transports: [
new winston.transports.File({
name: "info",
filename: "./logs/info.log",
level: "info",
}),
combinedLogger
],
});
const errorLogger = winston.createLogger({
defaultMeta: { service: "log-service" },
transports: [
new winston.transports.File({
name: "error",
filename: "./logs/error.log",
level: "error",
}),
combinedLogger
],
});
const logger = {
info: (params) => {
return infoLogger.info(params);
},
error: (params) => {
return errorLogger.error(params);
},
};
logger.info("info log"); // Will goto info.log & combined.log
logger.error("error log"); // Will goto error.log & combined.log

Resources