如何在进程退出之前强制nodejs winston日志记录到文件

How to force nodejs winston log to file before process exit

我正在使用 winston 3 来记录我的数据。但有时它在进程退出之前没有记录错误。

以下进程将退出,不登录到logfile.log:

const winston = require('winston');

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

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

尝试的解决方案之一是使用回调:

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

但是Winston3.x有callback issue,所以上面的代码不行,进程不会退出。

我正在寻找可行的解决方案?任何帮助将不胜感激。我的OS是Ubuntu16.04,节点10.17.

编辑 1: 我也试过 Prabhjot Singh Kainth 的建议使用 finish 事件来触发进程退出:

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

在上述情况下,进程将退出,但不会创建日志文件。

winston.Logger 的每个实例也是一个 Node.js 流。

当流结束后所有日志都刷新到所有传输时,将引发完成事件。

只需尝试使用此代码即可:

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();

终于,我找到了一个可行的解决方案。而不是监听 logger finish 事件,它应该监听 file._dest finish 事件。但是 file._dest 仅在 file open 事件之后创建。所以在初始化过程中需要等待fileopen事件

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

这个怎么样?

logger.info('First message...')
logger.info('Last message', () => process.exit(1))

很好,但当存在 多个文件传输时 就不行了。我将在下面 post 我的整个日志记录解决方案,它可以正确处理退出进程,包括对开发和生产的支持。

需要注意的是,它目前不适用于异常日志,但它可以与所有其他基于传输的日志一起使用。如果有人知道如何使异常日志也能正常工作,请在评论中 post。

首先,您要在执行日志的文件所在的同一目录中创建一个文件夹,并将其命名为 logger。在此文件夹中,创建三个 .js 文件,一个名为 dev-logger.js,另一个名为 prod-logger.js,以及最后 index.js

在node中,当你需要一个目录时,它会假设你想要加载index.js文件。

TheFileYouWantToLogFrom.js

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

index.js

此文件将查看您的节点进程环境状态并使用正确的记录器。

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;

您稍后可以通过简单地在您的终端中调用您的节点进程(在我的例子中是 cron.js)来测试它,方法是说 NODE_ENV=production node cron.jsNODE_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;

dev 和 prod 记录器文件的工作方式是使用 numOpenTransports 变量来跟踪当前打开的 'files transports',一旦它们全部关闭,然后然后,退出进程