Winston : understanding logging levels - node.js

Reading and fiddling with Winston, I'm puzzled as to why the logging levels are ordered as they are and why the transports behave in the way they do (well, at least the Console one). I'd appreciate if someone could, perhaps even thoroughly, with real use case examples, explain why logging with Winston works this way?
For example, I setup my logger like this :
var logger = new (winston.Logger)({
levels: winston.config.syslog.levels,
colors: winston.config.syslog.colors,
level: "debug", // I'm not sure what this option even does here???
transports: [
new (winston.transports.Console)({
colorize: true,
handleExceptions: true,
json: false,
level: "debug"
})
]
});
So, if I do logger.debug("Test");, then it will log debug: Test, fine. But if I do logger.info("Test");, then nothing happens.
The problem I have is that, If I want to log to the console eveverything but debug messages, what do I do? ... or even debug and info messages, but log everything else?
Coming from a Java world, using the standard loggers, I am used to having debug being more "fine grained" than warn and the loggers worked backwards; setting the logging level to info, for example, did log everything but debug (or something).
Also, what if I'd like a logger to log only error, warning and info messages, how would I do that with Winston?
* EDIT *
Apparently, this order of level is unique to winston.config.syslog.levels. So the only question remaining is : "Is it possible to, somehow, restrict a transport to a very specific logging level only?"

As per the documentation, you can set your own Logging levels, 0 being lowest, and associate colours with it. Now, if you don't want to log the lowest level, just set the level property to the corresponding level. By default, the console logger has it's level set to info
So, here is an example:
logger = new (winston.Logger)({
levels: {
'info': 0,
'ok': 1,
'error': 2
}
transports: [
new (winston.transports.ConsoleTransport)(silent: options.silent, level: 'ok')
]
});

var logger = new (winston.Logger)({
levels: {
'info': 0,
'ok': 1,
'error': 2
},
colors: {
'info': 'red',
'ok': 'green',
'error': 'yellow'
},
transports: [
new (winston.transports.Console)({level:'info',colorize: true})
]
});
logger.log('info',"This is info level");
logger.info("This is info level");

Related

How do I include exceptions in node winston daily rotating log?

I am trying to create a daily rotating log with log entries from throughout the application as well as uncaught exceptions:
const { createLogger, format, transports } = require('winston')
import 'winston-daily-rotate-file'
const httpContext = require('express-http-context')
const requestIdFormat = format((info, opts) => {
const requestId = httpContext.get('requestId')
if (requestId){
info.requestId = requestId
}else{
info.requestId = ''
}
return info
})
const allTransport = new transports.DailyRotateFile({
filename: 'logs/%DATE%-application.log',
datePattern: 'YYYY-MM-DD',
maxSize: '20m',
maxFiles: '14d',
level: 'info',
handleExceptions: true
})
const errorTransport = new transports.DailyRotateFile({
filename: 'logs/%DATE%-error.log',
datePattern: 'YYYY-MM-DD',
maxSize: '20m',
maxFiles: '14d',
level: 'error',
handleExceptions: true
})
export const logger = createLogger({
format: format.combine(requestIdFormat(), format.timestamp(), format.printf(i => `${i.timestamp} | ${i.requestId} | ${i.level}: ${i.message}`), format.errors({stack: true})),
transports: [
allTransport,
errorTransport,
],
exitOnError: false
})
But the exceptions e.g. throw Error('hello?') are not logged to the log files.
I've tried other variations c.f. https://github.com/winstonjs/winston#handling-uncaught-exceptions-with-winston e.g. setting exceptionHandlers in createLogger, but that does not work either.
How should I alter the code to include uncaught exceptions in the log?
UPDATE: I now see that an exception thrown on e.g. invalid import IS in fact logged, so maybe the issue is that the exception I test with is thrown in an express service - maybe it is caught in the express framework and that is why it is not logged?
Thanks,
-Louise
I ended up just "manually" logging exceptions, i.e. logger.error(err), as I already have a middleware hook for showing a catch-all error page.
I am still not sure why some exceptions were automatically logged and why I have to manually log exceptions I explicitly throw via "throw new Error('some message')"?
I've faced this kind of situation before, and for some reason, sometimes the node process get killed/exited before all log handling finishes its execution. There are some libs that may cause conflict in handling because they may call process.exit() while other libs are trying to handle the same thing. So, we need to investigate and debug to try to find what is terminating our process before our logs are flushed/written to disk. I strongly recommend not doing any async processing when caughting exceptions. Do everything in a sync way and faster.
In a project I've worked, I spended hours until I found process.exit in a module , probably copied from internet. Process.exit been called everywhere in an app is the root cause for errors and misbehavior of apps.

Log all errors and warnings

I am using a third party nodejs application which is quite large and it is using many different things for logging. For instance console.log() and console.error(). I would like to be able to trap all output and log to a specific file. I was thinking about using winston and do something like this:
const winston = require('winston')
const logger = winston.createLogger({
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' })
]
})
const ce = console.error
console.error = function(...args) {
logger.log.apply('error', args)
ce.apply(args)
}
Is there a better way to handle this kind of situation?
I might also add that some parts of the application is already using winston for logging purposes.
My aim is the create a single file with all errors and warnings generated from within the application.

How to get Winston daily file rotate to log to corresponding level file

I have defined custom levels for my application .They are as follows.
protected levels: Level = {
"error": 0,
"warn": 1,
"info": 2,
"debug": 3,
"trace": 4
};
I am using daily file rotate transport to get daily log in separate files.
const options: Object = {
name: this.level,
filename: logFilePath,
dirname: WinstonLogAgent.DIR_LOG,
datePattern: "yyyyMMdd.",
prepend: true,
level: this.level,
levels: this.levels,
maxsize: this.maxFileSize,
maxFiles: this.maxFileCount,
handleExceptions: true,
humanReadableUnhandledException: true
};
this.transportInstance.push(new (winston.transports.DailyRotateFile)(options));
If i define log level to be 'info', it will create one log file named info.log and will log for levels 'info' , 'warn' and 'error' (trace and debug will be ignored).
But behaviour i wanted was different. If i am specifying level to be 'info' and i am logging levels 'info' , 'warn' and 'error' , then there should be separate files created for each type of log . i.e 'info' level should be logged to info.log and 'warn' level to be logged to warn.log.
I have tried specifying five different daily file rotate transport ,each with unique level. Then the problem i find is that there is duplicate log entries.
For example , if am logging 'error' level , it would log to info.log , warn.log & error.log when logging level is set to info.
How can i achieve my objective?
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

NodeJs WinstonJS very weird start

I just started using winstonJS for NodeJS app logging.
My code goes like this :
var winston = require('winston');
var logger = new winston.Logger({
levels: { error: 0, warn: 1, info: 2, debug: 3, trace: 4 },
transports: [
new (winston.transports.Console)()
]
});
logger.log('error', 'log0');
logger.log('warn', 'log1');
logger.log('info', 'log2');
logger.log('debug', 'log3');
logger.log('trace', 'log4');
and the ONLY logs that I get in my console are :
info: log2
trace: log4
debug: log3
Note the wrong order as well.
Am I missing something obvious ?
Thanks
You have to consider 2 things:
Console transport is created always with default level "info" unless you specify a different level
when you specify a level for a transport, the logger will print all the message with level >= than the specified level
Also consider that the default logging levels for winston are: silly=0, debug=1, verbose=2, info=3, warn=4, error=5
Analyzing your code I can see you defined a new "info" level, with value of 2: this means the console log will print only messages with levels >= 2, and in your example these levels are exactly info, debug and trace (I guess you define your levels in the wrong order).
If you change your code like this, you will see all the message printed:
transports: [
new (winston.transports.Console)({level: 'error'})
]
About the wrong output order I can't reproduce it, my output is always
info: log2
debug: log3
trace: log4
PS: if you define levels like this error: 0, warn: 1, infoz: 2, debug: 3, trace: 4 with anything different replacing info, the logger will print nothing at all because the default console transport level will remain info. You can discover your transport level with a simple
console.log(logger.transports.console.level);

How to set log level in Winston/Node.js

I am using Winston logging with my Node.js app and have defined a file transport. Throughout my code, I log using either logger.error, logger.warn, or logger.info.
My question is, how do I specify the log level? Is there a config file and value that I can set so that only the appropriate log messages are logged? For example, I'd like the log level to be "info" in my development environment but "error" in production.
If you are using the default logger, you can adjust the log levels like this:
const winston = require('winston');
// ...
winston.level = 'debug';
will set the log level to 'debug'. (Tested with winston 0.7.3, default logger is still around in 3.2.1).
However, the documentation recommends creating a new logger with the appropriate log levels and then using that logger:
const myLogger = winston.createLogger({
level: 'debug'
});
myLogger.debug('hello world');
If you are already using the default logger in your code base this may require you to replace all usages with this new logger that you are using:
const winston = require('winston');
// default logger
winston.log('debug', 'default logger being used');
// custom logger
myLogger.log('debug', 'custom logger being used');
Looks like there is a level option in the options passed covered here
From that doc:
var logger = new (winston.Logger)({
transports: [
new (winston.transports.Console)({ level: 'error' }),
new (winston.transports.File)({ filename: 'somefile.log' })
]
});
Now, those examples show passing level in the option object to the console transport. When you use a file transport, I believe you would pass an options object that not only contains the filepath but also the level.
That should lead to something like:
var logger = new (winston.Logger)({
transports: [
new (winston.transports.File)({ filename: 'somefile.log', level: 'error' })
]
});
Per that doc, note also that as of 2.0, it exposes a setLevel method to change at runtime. Look in the Using Log Levels section of that doc.
There are 6 default levels in winston: silly=0(lowest), debug=1, verbose=2, info=3, warn=4, error=5(highest)
While creating the logger transports, you can specify the log level like:
new (winston.transports.File)({ filename: 'somefile.log', level: 'warn' })
Above code will set log level to warn, which means silly, verbose and info will not be output to somefile.log, while warn, debug and error will.
You can also define your own levels:
var myCustomLevels = {
levels: {
foo: 0,
bar: 1,
baz: 2,
foobar: 3
}
};
var customLevelLogger = new (winston.Logger)({ levels: myCustomLevels.levels });
customLevelLogger.foobar('some foobar level-ed message');
Note that it's better to always include the 6 predefined levels in your own custom levels, in case somewhere used the predefined levels.
You can change the logging level in runtime by modifying the level property of the appropriate transport:
var log = new (winston.Logger)({
transports: [
new (winston.transports.Console)({ level : 'silly' })
]
});
...
// Only messages with level 'info' or higher will be logged after this.
log.transports.Console.level = 'info';
I guess, it works similarly for file but I haven't tried that.
If you want to change the log level on the fly. Like for when you need to trace production issue for short amount of time; then revert to error log level. You can use a dynamic logger provided you can expose a service on the web https://github.com/yannvr/Winston-dynamic-loglevel
apart from this you can cleanly achieve this by imlplementing runtime-node-refresh follow this link for more.

Resources