fs.watch() 在 Node v13 中损坏还是我做错了什么?

Is fs.watch() broken in Node v13 or am I doing something wrong?

我一直在尝试设置一个文件观察器来检测何时将新数据添加到文件中。一般顺序是 1.) 客户端连接到服务器。 2.) 服务器记录到一个文件。 3.) fs.watch() 检测到文件已更改并运行一个函数,在本例中是一个简单的 console.log('New log entry')

除了 fs.watch() 没有检测到新消息何时添加到日志文件外,一切似乎都正常。但是,如果我单击 VScode 中的日志文件,它似乎会触发它。这是较新版本的 Node 中的错误还是我在这里做错了什么?

我知道我可以使用 fs.watchFile() 但我希望避免轮询的开销...

// src/index.js
const path = require('path');
const express = require('express');
const app = express();
const server = require('http').createServer(app);
const io = require('socket.io')(server);
const logger = require('./logger');
const fs = require('fs');

fs.watch('./logs/combined.log', (event) => {
  if (event === 'change') {
    console.log('New log entry');
  }
});

app.use(express.static(path.join(__dirname, '../public')));
app.get('/', function(req, res, next) {
  res.sendFile(path.join(__dirname, '../public', 'index.html'));
});

io.on('connection', function(socket) {
  logger.info('a user connected');
});

const PORT = process.env.PORT || 8888;

server.listen(PORT, () => {
  logger.info(`Listening at http://localhost:${port}`);
});

-

// src/logger.js
const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [
    new winston.transports.File({ filename: 'logs/combined.log' })
  ]
});

module.exports = logger

-

// public/index.html
<script src="/socket.io/socket.io.js"></script>
<script>
  var socket = io.connect('http://localhost:8888');
</script>

-

// simplified test
const logger = require('./logger');
const fs = require('fs');

fs.watch('./logs/combined.log', event => {
  if (event === 'change') {
    console.log('log file has updated');
  }
});

function intervalFunc() {
  logger.info('new log message');
}
setInterval(intervalFunc, 5000);

fs.watch has an entire Caveats 部分的节点文档在最开始指出:

The fs.watch API is not 100% consistent across platforms, and is unavailable in some situations.

您应该阅读该部分以获取更多信息,但如果您无法让 fs.watch 可靠地为您工作,您可能最终不得不使用 fs.watchfile

[更新]

注意事项部分的 Filename Argument 小节也说明了这一点:

Even on supported platforms, filename is not always guaranteed to be provided. Therefore, don't assume that filename argument is always provided in the callback, and have some fallback logic if it is null.

因为您不需要使用 filename 参数(而且您已经知道文件名),您的回调不应测试真实的 filename 参数。

更新:

所以它looks like Winston comes with built-in listeners。这些侦听器的行为类似于文件观察器,并允许您挂钩某些事件。您可以挂钩的事件之一称为 'data' - 这使您可以监听数据何时写入文件,以及 运行 回调函数。

这提供了与 file.watch 类似的行为。

它们允许您监听以下事件:

  • 关闭
  • 数据
  • 结束
  • 错误
  • 可读

我在 Windows 上测试过它,它运行良好。

这是我正在使用的测试文件:

const winston = require('winston');

const { createLogger, format: { json }, transports: { File } } = winston;

const logger = createLogger({
  level: 'info',
  format: json(),
  transports: [
    new File({ filename: './log.txt' })
  ],  
});

/**
 * Added a built in Winston listener
 */
logger.addListener('data', chunk => {
    console.log('\r\n[winston listener] we have logged some data:\n', chunk)
})

/**
 * Their documentation seems to use the `.on` method.
 * 
 * This appears to provide the same type of behavior that `.addListener` does.
 * 
 * I'm not sure which is best, or recommended, `.addListener` or `.on` - you
 * may want to dig deeper into that.
 */
logger.on('data', chunk => {
    console.log('\r\n[winston on "data"] we have logged some data:\n', chunk);
});

setInterval(() => {
  logger.info('new log message');
}, 5000);

所以我一直在思考这个问题,起初我认为这是因为 Winston 使用写入流来更新文件..

然后我继续尝试重现你的问题,但我无法重现它。

每次更新日志文件时,都会向控制台写入一条消息(意思是,fs.watch 正在工作。

您在使用 Windows 吗?您使用的是哪个版本的 Node?

这是我正在使用的测试文件:

const winston = require('winston');
const fs = require('fs');

const { createLogger, format: { json }, transports: { File } } = winston;

const logger = createLogger({
  level: 'info',
  format: json(),
  transports: [
    new File({ filename: './log.txt' })
  ]
});

fs.watch('./log.txt', event => { 
  if (event === 'change') {
    console.log('log file has updated');
  }
});

setInterval(() => {
  logger.info('new log message');
}, 5000);