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

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.

Related

Structured Logs in Google Cloud Run not being parsed (using Winston for logging)

I'm attempting to format my logs in such a way that Google Cloud will correctly extract the log level. This is running on Cloud Run, with typescript. Cloud Run is grabbing the logs from the container output.
If I do the following, google correctly parses the log line:
console.log(JSON.stringify({
severity: 'ERROR',
message: 'This is testing a structured log error for GCP'
}));
And the log output looks like this:
I've tried a number of different ways to format with winston, ended up with the following:
useFormat = format.combine(
format((info, opts) => {
info['severity'] = info.level;
delete info.level;
return info;
})(),
format.json());
this.winston = winston.createLogger({
level: logLevel,
format: useFormat,
transports: [new winston.transports.Console()]
});
Which looks like it will work (it correctly outputs the json line), I get this in the GCP logs:
Any help appreciated.
Turns out I was close, just needed to .upperCase() the log level (and I'm mapping Verbose -> Debug, I don't really understand why GCP decided to do a totally different log leveling system than everyone else). New code:
useFormat =
format.combine(
format((info, opts) => {
let level = info.level.toUpperCase();
if(level === 'VERBOSE') {
level = 'DEBUG';
}
info['severity'] = level;
delete info.level;
return info;
})(),
format.json());
The last bit of the question is confusing. The problem OP is pointing to is that the json is printed out and the severity is default. The json should not be printed out, only the message, and the severity should be debug. The answer that OP provides does what is wanted.
For others that may be confused in the same way I was.

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.

Can I add a second function export to module.exports without changing the way it's called?

I have a logging module that I use in many of my projects, which generally exports a single Winston logger, so all I did was define a logger and it's transports, then export it:
module.exports = logger;
when importing using const logger = require('mylogger.js') I then use the various levels built in (logger.info logger.debug etc).
I've now decided that I want to create a second logging function that will write logs to a different file, so I need to create and export a new transport. Thing is, if I switch to module.exports = {logger, mynewlogger}, that will change the way I import and call the functions, and I have that in many places.
Besides creating second file and importing both, is there any other way to add a second export without having to change my code everywhere else?
It's either new module that re-exports both:
logger-and-mynewlogger.js
module.exports = {logger, mynewlogger}
Or a separate module:
mynewlogger.js
module.exports = mynewlogger
Or using existing function as module object:
logger.mynewlogger = ...
module.exports = logger;
The first two options are preferable because they result in reasonably designed modules, while the last one is a quick and dirty fix.
Yes, you can define multiple transports for a single exported logger. When creating your Winston log, the 'transports' property is an array which allows you to define multiple outputs.
Here's an example of one I have that has two transports. Firstly, console and the second a daily rotating log.
const winston = require('winston');
const Rotate = require('winston-daily-rotate-file');
const tsFormat = () => (new Date()).toLocaleTimeString();
const logger = new (winston.Logger)({
transports: [
// colorize the output to the console
new (winston.transports.Console)({
timestamp: tsFormat,
colorize: true,
level: 'info',
}),
new (Rotate)({
filename: `${logDir}/${logName}-app.log`,
timestamp: tsFormat,
datePattern: 'YYYY-MM-DD',
prepend: true,
level: env === 'development' ? 'verbose' : 'info',
}),
],
});
module.exports = logger;

NodeJS - WinstonJS - Clear the log file

I'm using WinstonJS for logging to a file, and nodemon to restart when I update my code.
var winston = require('winston');
var logger = new (winston.Logger)({
transports: [
new (winston.transports.File)({
level: 'silly',
filename: __dirname + '/logs/test.log',
json: false
})
]
});
logger.log('info', 'something');
The log file is being appended to, however when I make a code change and save my file, nodemon runs it again and the log file is appended to again. This leads to a log file that just keeps getting longer and longer, and I have to keep manually deleting the contents.
I guess I could do something with the fs module, but it would be far nicer to use Winston to say something like update: replace|append, but I can't see anything like that available.
Is there any way to clear the log file, so I only have log entries for the last time my code was run..?
The
{ flags: 'w' }
property on the options object on the new transport is working for me:
transports: [
new winston.transports.File({ filename: 'myLog.txt', options: { flags: 'w' } })
]
I know this question is a little outdated, but I recently found the answer to this and thought I would share it here for posterity. You can pass fs.createWriteStream options to the File logger:
// Open the file in "write" mode, which truncates first
options: { flags: 'w' },

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