winston custom logs color - node.js

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
),
.
.
.

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

Winston Logging - separate levels to separate Transports

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 () {}
}
}

Resources