Winston: transports.File is not flushing on end.

Created on 15 Oct 2018  路  8Comments  路  Source: winstonjs/winston

Please tell us about your environment:

  • _winston version?_

    • [ ] winston@2

    • [x] winston@3

  • _node -v outputs:_ v10.10.0
  • _Operating System?_ (Windows, macOS, or Linux)
  • _Language?_ (all | TypeScript X.X | ES6/7 | ES5 | Dart)

What is the problem?

File transport is not flushing on exit. Even when flushing per https://github.com/winstonjs/winston#awaiting-logs-to-be-written-in-winston

Steps to reproduce:

'use strict';

let winston = require('winston');
// VERSION: "winston": "^3.1.0",

let file_transport = new winston.transports.File({
    filename: `${__dirname}/myfile.log.json`,
});
let logger = winston.createLogger({
    transports: [file_transport],
});


async function run() {
    for (let i = 0; i < 1000; i++) {
        logger.info('message');
    }

    logger.info('THE LAST MESSAGE');

    await new Promise(resolve => {
        file_transport.on('finish', resolve);
        logger.end();
    });

    process.exit(1);
}

run();

What do you expect to happen instead?

all messages (including THE LAST MESSAGE) should be written to the file.

winston-file

Most helpful comment

I am experiencing the exact same issue, the posted code by @racquetmaster is correct. It makes it almost impossible to use winston with asynchronous processing

I have been following the documentation (below) calling the end() on Logger. I also tried directly on (File) Transport like @racquetmaster without success. IMO this is a very important issue.

Often it is useful to wait for your logs to be written before exiting the process. 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.

```js
'use strict'

const winston = require('winston')

const logFilePath = __dirname + '/test-async.log'

const logger = winston.createLogger({
level: 'debug',
format: winston.format.combine(
winston.format.timestamp({
format: 'YYYY-MM-DD HH:mm:ss'
}),
winston.format.simple(),
winston.format.printf(info => {
return [${info.timestamp}] [${info.level}] ${info.message}
})),
transports: [
new winston.transports.File({
filename: logFilePath
})
]
})

let code = 1
const asyncFunction = async function(){
return new Promise(function(resolve) {
setTimeout(function() {
logger.info('resolved')
resolve(0)
}, 300)
})
}

const start = async function(){

logger.info('START')

code = await asyncFunction()
logger.info(code : ${code})
logger.on('finish', function() {
process.exit(code)
})

//Should display END before exiting the process according to doc
logger.info('END')
logger.end()
}

start()

All 8 comments

I'm experiencing the same issue in some unit tests.

In addition to waiting for the transport to emit the 'finish' event, I've tried just waiting on the logger's finish event and _also_ all the transports.

  it('Should log to file', function(done) {
    const loggingDir = fs.mkdtempSync('logging-dir');

    // Configure with output file
    const loggingFile = path.join(loggingDir, 'log-test.log');
    logger = winston.createLogger({
      transports: [
        new winston.transports.File({filename: loggingFile})
      ]
    });

    // Wait until the logger *and* all transports have emitted the 'finish' event
    Promise.all([logger].concat(logger.transports).map(st =>
      new Promise(function(resolve, reject) {
        st.on('finish', resolve);
      })
    )).then(function() {
      let f = fs.readFileSync(loggingFile, 'utf-8');
      expect(f).is.not.empty;
      expect(f).to.satisfy(str => str.includes('Test Message'));
      expect(f).to.satisfy(str => !str.includes('Silly'));
      done();
    }).catch(function(err) {
      done(err);
    });

There is still a race condition here because the test does pass _some_ of the time, and inevitably if I look in the file manually it _does_ have the logging output within it.

Should be a function flushAllTransports(): Promise<void> which should take care of flushing all buffered data (not just wait next save iteration, but force transport to save) and then resolve promise.

export function flushLogs(): Promise<void> {
  // tslint:disable-next-line: typedef
  return new Promise((resolve, reject) => {
    logger.on('finish', () => resolve());
    logger.end();
  });
}

I am experiencing the same issue, makes it really hard to use winston with asynchronous processing

I am experiencing the exact same issue, the posted code by @racquetmaster is correct. It makes it almost impossible to use winston with asynchronous processing

I have been following the documentation (below) calling the end() on Logger. I also tried directly on (File) Transport like @racquetmaster without success. IMO this is a very important issue.

Often it is useful to wait for your logs to be written before exiting the process. 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.

```js
'use strict'

const winston = require('winston')

const logFilePath = __dirname + '/test-async.log'

const logger = winston.createLogger({
level: 'debug',
format: winston.format.combine(
winston.format.timestamp({
format: 'YYYY-MM-DD HH:mm:ss'
}),
winston.format.simple(),
winston.format.printf(info => {
return [${info.timestamp}] [${info.level}] ${info.message}
})),
transports: [
new winston.transports.File({
filename: logFilePath
})
]
})

let code = 1
const asyncFunction = async function(){
return new Promise(function(resolve) {
setTimeout(function() {
logger.info('resolved')
resolve(0)
}, 300)
})
}

const start = async function(){

logger.info('START')

code = await asyncFunction()
logger.info(code : ${code})
logger.on('finish', function() {
process.exit(code)
})

//Should display END before exiting the process according to doc
logger.info('END')
logger.end()
}

start()

This seems to solve the issue for me

  logger.on('finish', function() {
    setImmediate(() => process.exit(code))
  })

Did not have the time to look at winston code in detail but it seems to mean than the finish event is raised too early, maybe it is added to the event loop before the flush of the underlying transports

I am sure this fix works pretty well: https://github.com/winstonjs/winston/pull/1868
but I wanted to let you know what we have been using to "gracefully" stop the logger:

const fileTransport = new winston.transports.File({
  filename: 'error.log',
  level: 'error',
});

/**
 * Create a basic logger that outputs stderr to error.log
 */
const logger = winston.createLogger({
  level: 'info',
  transports: [
    fileTransport,
  ],
});

logger.endGracefully = () => new Promise((fulfill, reject) => {
  // Add logic for other transports here ...

  fileTransport.on('flush', () => {
    fulfill();
  });

  logger.end();
});

// CODE

logger.error('Here comes a log entry');

setTimeout(async () => {
  logger.error('Here comes another log entry');

  await logger.endGracefully();
  process.exit(0);
}, 1000)

Here comes another log entry will be visible in the log file and the process exits normally.

I am sure this fix works pretty well: #1868
but I wanted to let you know what we have been using to "gracefully" stop the logger:

Thanks for this. Your workaround doesn't work in my tests.

Here's the basic test I have:

unlinkSync('error.log');
// your setup goes here....
logger.error('Hello world');
await logger.endGracefully();
const mesgs = readFileSync('error.log');

This fails because the file doesn't exist at the readFileSync.

I hope that helps.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jlank picture jlank  路  4Comments

greenhat616 picture greenhat616  路  3Comments

sinai-doron picture sinai-doron  路  3Comments

Infinitay picture Infinitay  路  3Comments

xungwoo picture xungwoo  路  3Comments