Реализация «Tail -f» в Node JS


Linux — одна из самых прекрасных разработок на сегодняшний день, и иногда мне интересно, как работает та или иная команда в linux под капотом, например, как именно работает «ls». Поэтому я попытался воспроизвести одну из самых используемых команд linux, «Tail -f» в nodejs.
Для тех, кто не знает, «Tail -f» печатает последние 10 строк из файла, а затем отслеживает обновления в файле и печатает содержимое, когда файл обновляется. Теперь node js имеет встроенный модуль файловой системы, который помогает нам играть с файлами и папками, и есть несколько прямых методов, доступных для чтения файла, мониторинга файла, записи файла. Звучит просто — взять эти команды и использовать их одну за другой, но это не так просто.
Есть вещи, с которыми мы должны справиться:
Мы должны получать данные при каждом обновлении файла
Мы не должны читать весь файл каждый раз, когда он обновляется.
Поэтому я начал просматривать все методы, доступные в пакете fs в nodejs. Я получил readline, который читает файл строка за строкой и на каждой строке выдает ложные данные в событии

// Read file line by line
var readline = require('readline');
var rl = readline.createInterface(process.stdin, process.stdout);
rl.on('line', (log) => {
    console.log(`Log is: ${log}`);
    rl.close();
});
Вход в полноэкранный режим Выйти из полноэкранного режима

Этот метод выглядел очень полезным для моего случая, поэтому я начал с него, но дело в том, что теперь мне нужно решить, когда я должен вызывать этот метод. Мне нужно следить за обновлением файла, поэтому я искал любой другой пакет, который может обнаружить изменения в файле.
Я нашел fs.watchfile, который выдает событие всякий раз, когда в файле происходят какие-либо изменения. Я попробовал этот метод, но заметил, что он выдает события при обновлении метаданных, поэтому мне пришлось добавить некоторый фильтр. Мы получили текущую и предыдущую статистику по каждому событию. Теперь я должен сравнить размер файла, чтобы увидеть, обновлены ли фактические данные в файле или нет, и выполнять действия только при изменении размера файла.

// Keep a watch on file value change
fs.watchFile(filename, (curr, prev) => {
    console.log("Previous Stats", prev);
    console.log("Current Stats", curr);
    if (curr.size.valueOf() === prev.size.valueOf())
        return;
});
Войти в полноэкранный режим Выйти из полноэкранного режима

Чтобы быть вдвойне уверенным, я должен сохранить текущий размер для последующего использования, а затем сравнить его со следующим размером. Теперь я могу следить за обновлением, поэтому при каждом обновлении я вызываю метод readline.
Теперь у меня возникла еще одна большая проблема: при каждом событии я считывал и передавал полный файл. Это было решено просто: я сохраняю указатель и перемещаю его на последнюю строку, а когда я снова читаю файл, я печатаю только ту строку, которая находится после последнего указателя.

// Read file line by line
let pointer = 0;
// Keep a watch on file value change
fs.watchFile(filename, (curr, prev) => {
    if (curr.size.valueOf() === prev.size.valueOf())
        return;

    let currentpointer = 0;
    var readline = require('readline');
    var rl = readline.createInterface(process.stdin, process.stdout);
    rl.on('line', (log) => {
        currentpointer++
        if (currentpointer > pointer)
            console.log(`Log is: ${log}`);
        rl.close();
    });
    pointer = currentpointer;
});
Вход в полноэкранный режим Выход из полноэкранного режима

Это работало так, как нужно, но оставалась проблема: это было неэффективно, поскольку, хотя я не печатал строку журнала каждый раз, когда происходило событие, я просматривал каждую строку, а это отнимало время и память.
Поэтому я начал искать альтернативу readline, которая может читать определенную строку из файла. Я нашел простой метод «read», в котором я могу передать, с какой части я могу читать, но я должен передать начальный байт, а не строку. Я использовал это, теперь вместо чтения строк я читал буфер и изменил указатель со строки на байт буфера.
Теперь я должен открыть файл и прочитать его с последнего байта, который я прочитал в последнем событии. Теперь вместо строковых данных у меня был буфер, поэтому я преобразовал их в обычную строку из буфера, затем разделил строку на «n», что является новой строкой, а затем вывел элементы массива один за другим.

 // Keep a watch on file value change
 fs.watchFile(filename, (curr, prev) => {
    if (filename) {
        //Check if file is actually updated
        if (curr.size.valueOf() === previousFileSize.valueOf())
            return;

        let buffer = new Buffer.alloc(curr.size - lastReadByte + 1);

        previousFileSize = curr.size;
        console.log(`${filename} file Changed`);

        fs.open(filename, fileOpenMode, (err, filedata) => {
            if (err)
                return console.error(err);

            console.log("Reading the file");
            fs.read(filedata, buffer, 0, buffer.length, lastReadByte, (err, bytes) => {
                if (err)
                    return console.error(err);

                if (bytes > 0) {
                    const dataString = buffer.slice(0, bytes).toString();
                    const dataArray = dataString.split("n");
                    dataArray.forEach(logline => {
                        if (logline)
                            console.log(logline)
                    });
                }
                lastReadByte = stats.size
                // Close the opened file.
                fs.close(filedata, (err) => {
                    if (err)
                        return console.error(err);
                    console.log("File closed successfully");
                });
            });
        });
    }
});
Вход в полноэкранный режим Выход из полноэкранного режима

Таким образом, это эффективный способ передачи постоянно обновляющегося файла, реализованный в Node JS. Счастливого кодинга!!! Для получения большего количества материалов, вы можете подписаться на мой канал YT

Оцените статью
devanswers.ru
Добавить комментарий