使用 NodeJS 重播日志文件,就好像它是实时发生的一样

Replay a log file with NodeJS as if it were happening in real-time

我有一个日志文件,其中包含从名为 Flarm 的系统捕获的大约 14.000 个飞机位置数据点,它看起来像这样:

{"addr":"A","time":1531919658.578100,"dist":902.98,"alt":385,"vs":-8}
{"addr":"A","time":1531919658.987861,"dist":914.47,"alt":384,"vs":-7}
{"addr":"A","time":1531919660.217471,"dist":925.26,"alt":383,"vs":-7}
{"addr":"A","time":1531919660.623466,"dist":925.26,"alt":383,"vs":-7}

我需要做的是找到一种方法 'play' 这个文件实时返回(就像它现在正在发生一样,即使它是预先录制的),并在任何时候发出一个事件日志条目 'occurs'。该文件未被添加,它是预先录制的,播放将在稍后阶段进行。

之所以这样做是因为我在开发的时候没有接触到接收设备

我能想到的唯一方法是为每个日志条目设置超时,但这似乎不是正确的方法。此外,这个过程必须扩展到更长的录音(这个只有一个小时)。

还有其他方法吗?

假设您想可视化飞行日志,您可以使用 fs watch 如下所示,查看日志文件的变化:

fs.watch('somefile', function (event, filename) {
    console.log('event is: ' + event);
    if (filename) {
        console.log('filename provided: ' + filename);
    } else {
        console.log('filename not provided');
    }
});

代码摘录来自here. For more information on fs.watch() check out here

然后,为了在前端进行无缝更新,您可以在查看日志文件的服务器上设置一个 Websocket,并通过该套接字将新添加的行发送到前端。

在前端获取数据后,您可以在那里将其可视化。虽然我之前没有做过任何飞行可视化项目,但我已经使用 D3js 来可视化其他内容(声音、数值数据、度量分析等)几次,而且每次都能完成。

如果你想 "play them back" 与实际时差,setTimeout 几乎就是你必须做的。

const processEntry = (entry, index) => {
  index++;
  const nextEntry = getEntry(index);
  if (nextEntry == null) return;

  const timeDiff = nextEntry.time - entry.time;
  emitEntryEvent(entry);
  setTimeout(processEntry, timeDiff, nextEntry, index);
};

processEntry(getEntry(0), 0);

这会发出当前条目,然后根据差异设置超时,直到下一个条目。 getEntry 可以从预填充数组中获取行,也可以根据索引单独获取行。在后一种情况下,只有两行数据只会同时存在于内存中。

终于成功了! setTimeout 结果是答案,并结合 Lucas S 的输入。这就是我最终得到的结果:

const EventEmitter = require('events');
const fs = require('fs');

const readable = fs.createReadStream("./data/2018-07-18_1509log.json", {
  encoding: 'utf8',
  fd: null
});

function read_next_line() {
  var chunk;
  var line = '';
  // While this is a thing we can do, assign chunk
  while ((chunk = readable.read(1)) !== null) {
    // If chunk is a newline character, return the line
    if (chunk === '\n'){
      return JSON.parse(line);
    } else {
      line += chunk;
    }
  }
  return false;
}

var lines = [];
var nextline;

const processEntry = () => {
  // If lines is empty, read a line
  if (lines.length === 0) lines.push(read_next_line());

  // Quit here if we've reached the last line
  if ((nextline = read_next_line()) == false) return true;

  // Else push the just read line into our array
  lines.push(nextline);

  // Get the time difference in milliseconds
  var delay = Number(lines[1].time - lines[0].time) * 1000;

  // Remove the first line
  lines.shift();

  module.exports.emit('data', lines[0]);

  // Repeat after the calculated delay
  setTimeout(processEntry, delay);
}

var ready_to_start = false;

// When the stream becomes readable, allow starting
readable.on('readable', function() {
  ready_to_start = true;
});


module.exports = new EventEmitter;
module.exports.start = function() {
  if (ready_to_start) processEntry();
  if (!ready_to_start) return false;
}