Reputation: 462
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?
Upvotes: 7
Views: 9569
Reputation: 173
I have something similar to the @philippe-canavarro answer, custom filter. This example will use ES6 module syntax for typescript (for javascript just remove the types):
// This code could be a logger.ts file or logger.js file
import winston, { Logger, LogEntry } from 'winston';
const infoFilter = winston.format((input: LogEntry): LogEntry | boolean => {
return input.level === 'info' ? input : false;
});
const warnFilter = winston.format((input: LogEntry): LogEntry | boolean => {
return input.level === 'warn' ? input : false;
});
export const logger: Logger = winston.createLogger({
transports: [
new winston.transports.File({
filename: path.join('./logs/info.log'),
level: 'info',
format: winston.format.combine(infoFilter(), winston.format.json())
}),
new winston.transports.File({
filename: path.join('/logs/warn.log'),
level: 'warn',
format: winston.format.combine(warnFilter(), winston.format.json())
}),
new winston.transports.File({
filename: path.join('/logs/error.log'),
level: 'error'
}),
]
});
As level 'error' is the critical level, does not need a filter. Also you can import format
and transport
to not use winston.format.xxx
and winston.transport.xxx
. { Logger, LogEntry }
are types, you can safely remove them from the whole code.
Upvotes: 1
Reputation: 261
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),
}),
...
Upvotes: 5
Reputation: 2597
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.
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');
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:
Upvotes: 5
Reputation: 126
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 () {}
}
}
Upvotes: 0