12

I am using winston 3 to log my data. But sometimes it didn't log the error before process exit.

The following process will exit, without log to the logfile.log:

const winston = require('winston');

winston.add(new winston.transports.File({
    filename: 'logfile.log'
}));

winston.info('please log me in');
process.exit(1);

One of attempted solution is used callback:

const winston = require('winston');

const logger = new winston.createLogger({
    new winston.transports.File({
    filename: 'logfile.log'
}));

winston.info('please log me in', () => {
    process.exit(1);
});

setTimeout(() => {}, 100000000000); // pause the process until process.exit is call

But Winston 3.x have callback issue, so the above code won't work, the process will not exit.

I am looking for a workable solution? Any help will be greatly appreciated. My OS is Ubuntu 16.04, Node 10.17.

Edit 1: I also have try Prabhjot Singh Kainth's suggestion use finish event to trigger process exit:

const winston = require('winston');

const logger = winston.createLogger({
    transports: [
        new winston.transports.File({
            filename: 'logfile.log'
        })
    ]
});

logger.info('please log me in');

logger.on('finish', () => {
    process.exit();
});
logger.end();

setTimeout(() => {}, 100000000000); // pause the process until process.exit is call

In the above case, the process will exit, but no log file will be created.

Wan Chap
  • 841
  • 7
  • 13

5 Answers5

3

How about this one?

logger.info('First message...')
logger.info('Last message', () => process.exit(1))
fmagno
  • 1,446
  • 12
  • 27
2

Finally, I find a workable solution. Instead of listening to logger finish event, it should listen to file._dest finish event. But file._dest is only created after file open event. So it need to wait for file open event in initialization process.

const winston = require('winston');

const file = new winston.transports.File({
  filename: 'logfile.log'
});

const logger = winston.createLogger({
    transports: [file]
});

file.on('open', () => {  // wait until file._dest is ready
  logger.info('please log me in');
  logger.error('logging error message');
  logger.warn('logging warning message');

  file._dest.on('finish', () => {
    process.exit();
  });
  logger.end();
});

setTimeout(() => {}, 100000000000); // pause the process until process.exit is call
Wan Chap
  • 841
  • 7
  • 13
1

Each instance of winston.Logger is also a Node.js stream.

A finish event will be raised when all logs have flushed to all transports after the stream has been ended.

Just try using this code :

const transport = new winston.transports.Console();
const logger = winston.createLogger({
  transports: [transport]
});
logger.on('finish', function (info) {
  // All `info` log messages has now been logged
});

logger.info('CHILL WINSTON!', { seriously: true });
logger.end();
Prabhjot Singh Kainth
  • 1,831
  • 2
  • 18
  • 26
  • 2
    It doesn't work. I am trying to write to a file not console. Console transports is always works fine. But if you use `winston.transports.File({filename:'log'})`, the log file will not create when process exit. – Wan Chap Nov 20 '19 at 05:01
1

The answer from @WanChap was good, but not when there is multiple file transports. I will post my entire logging solution below, which handles exiting the process correctly, including support for Development and Production.

The only caveat is that it does not work with the exceptions log at the moment, but it will work with every other transport based log. If anyone figures out how to make this work with the exception log as well, please post in the comments.

First, you want to create a folder in the same directory that your file that is doing the logs is in, and call it logger. In this folder, create three .js files, one called dev-logger.js, the other prod-logger.js, and finally index.js

In node, when you require a directory, it will assume you want to load the index.js file.

TheFileYouWantToLogFrom.js

const logger = require('./logger');
logger.info("it works!");

index.js

This file will look at your node process environment state and use the correct logger.

const buildDevLogger = require('./dev-logger');
const buildProdLogger = require('./prod-logger');

let logger = null;
if(process.env.NODE_ENV === 'development'){
    logger = buildDevLogger();
}else{
    logger = buildProdLogger();
}

module.exports = logger;

You can test this later, by simply calling your node process (in my case cron.js) in your terminal, by saying something like NODE_ENV=production node cron.js or NODE_ENV=development node cron.js

dev-logger.js

const { format, createLogger, transports} = require('winston');
const { timestamp, combine, printf, errors } = format;

var numOpenTransports = 0;

function buildDevLogger(){
    const logFormat = printf(({ level, message, timestamp, stack, durationMs }) => {
        return `${timestamp} ${level}: ${stack || message}${durationMs ? " | "+durationMs+'ms' : ''}`;
    });
    
    //For info on the options go here.
    //https://github.com/winstonjs/winston/blob/master/docs/transports.md
    var options = {
        console: {
            handleExceptions: true,
            level: 'debug',
            format: combine(
                format.colorize(),
                timestamp({format: 'YYYY-MM-DD HH:mm:ss'}),
                errors({stack: true}),
                logFormat
            ),
        },
        combined: {
            filename: 'logs/dev/combined.log',
            maxsize: 10000000,
            maxFiles: 10,
            tailable:true,
            format: combine(
                timestamp({format: 'YYYY-MM-DD HH:mm:ss'}),
                errors({stack: true}),
                logFormat
            ),
        },
        error: { 
            filename: 'logs/dev/error.log',
            level: 'error',
            maxsize: 10000000,
            maxFiles: 10,
            tailable:true ,
            format: combine(
                timestamp({format: 'YYYY-MM-DD HH:mm:ss'}),
                errors({stack: true}),
                logFormat
            ),
        },
        exception : {
            filename: 'logs/dev/exceptions.log',
            maxsize: 10000000,
            maxFiles: 10,
            tailable:true,
            format: combine(
                timestamp({format: 'YYYY-MM-DD HH:mm:ss'}),
                errors({stack: true}),
                logFormat
            ),
        }
    };

    function fileFinished(){
        numOpenTransports--;
        if(numOpenTransports==0){
            process.exit(0);
        }
    }

    var combinedFile = new transports.File(options.combined);
    var errorFile = new transports.File(options.error);
    var exceptionFile = new transports.File(options.exception);

    combinedFile.on('open', () => {  // wait until file._dest is ready
        numOpenTransports++;
        combinedFile._dest.on('finish', () => {
            fileFinished();
        });
    });
    errorFile.on('open', () => {  // wait until file._dest is ready
        numOpenTransports++;
        errorFile._dest.on('finish', () => {
            fileFinished();
        });
    });

    return createLogger({
        defaultMeta: {service:'cron-dev'},
        transports: [
            new transports.Console(options.console),
            combinedFile, 
            errorFile,
        ],
        exceptionHandlers: [
            exceptionFile
        ]
    });
}

module.exports = buildDevLogger;

prod-logger.js

const { format, createLogger, transports} = require('winston');
const { timestamp, combine, errors, json } = format;

var numOpenTransports = 0;

function buildProdLogger(){
    var options = {
        console: {
            handleExceptions: true,
            level: 'debug',
            format: combine(
                timestamp({format: 'YYYY-MM-DD HH:mm:ss'}),
                errors({stack: true}),
                json()
            ),
        },
        combined: {
            filename: 'logs/prod/combined.log',
            maxsize: 10000000,
            maxFiles: 10,
            tailable:true,
            format: combine(
                timestamp({format: 'YYYY-MM-DD HH:mm:ss'}),
                errors({stack: true}),
                json()
            ),
        },
        error: { 
            filename: 'logs/prod/error.log',
            level: 'error',
            maxsize: 10000000,
            maxFiles: 10,
            tailable:true ,
            format: combine(
                timestamp({format: 'YYYY-MM-DD HH:mm:ss'}),
                errors({stack: true}),
                json()
            ),
        },
        exception : {
            filename: 'logs/prod/exceptions.log',
            maxsize: 10000000,
            maxFiles: 10,
            tailable:true,
            format: combine(
                timestamp({format: 'YYYY-MM-DD HH:mm:ss'}),
                errors({stack: true}),
                json()
            ),
        }  
    };

    function fileFinished(){
        numOpenTransports--;
        if(numOpenTransports==0){
            process.exit(0);
        }
    }

    var combinedFile = new transports.File(options.combined);
    var errorFile = new transports.File(options.error);
    var exceptionFile = new transports.File(options.exception);

    combinedFile.on('open', () => {  // wait until file._dest is ready
        numOpenTransports++;
        combinedFile._dest.on('finish', () => {
            fileFinished();
        });
    });
    errorFile.on('open', () => {  // wait until file._dest is ready
        numOpenTransports++;
        errorFile._dest.on('finish', () => {
            fileFinished();
        });
    });

    return createLogger({
        defaultMeta: {service:'cron-prod'},
        transports: [
            new transports.Console(options.console),
            combinedFile, 
            errorFile,
        ],
        exceptionHandlers: [
            exceptionFile
        ]
    });
}

module.exports = buildProdLogger;

The way the dev and prod logger files work is by using the numOpenTransports variable to keep track of which 'files transports' are currently open, and once they are all closed, then, and only then, exit the process.

Jeremy Caney
  • 7,102
  • 69
  • 48
  • 77
Joseph Astrahan
  • 8,659
  • 12
  • 83
  • 154
0

I tried the following code, but I always got "TypeError: file.on is not a function". I'm using winston version 3.9.0.

const winston = require('winston');

const file = new winston.transports.File({
  filename: 'logfile.log'
});

const logger = winston.createLogger({
    transports: [file]
});

file.on('open', () => {  // wait until file._dest is ready
  logger.info('please log me in');
  logger.error('logging error message');
  logger.warn('logging warning message');

  file._dest.on('finish', () => {
    process.exit();
  });
  logger.end();
});

setTimeout(() => {}, 100000000000); // pause the process until process.exit is call
Jean
  • 1
  • 1
  • This does not provide an answer to the question. Once you have sufficient [reputation](https://stackoverflow.com/help/whats-reputation) you will be able to [comment on any post](https://stackoverflow.com/help/privileges/comment); instead, [provide answers that don't require clarification from the asker](https://meta.stackexchange.com/questions/214173/why-do-i-need-50-reputation-to-comment-what-can-i-do-instead). - [From Review](/review/late-answers/34570088) – varad_s Jun 26 '23 at 13:42