Динамическое отображение логов в броузере на Node.js & WebSocket
Иногда возникает потребность динамически отслеживать появление новых файлов в определенных папках, а также выводить содеримое лог файлов в окно броузера. Данным упрощенным примером я хочу показать как это можно сделать.
Инструменты и модули для решения
Для серверной части был выбран Node.js. Но похожий пример можно аналогично реализовать и с использованием EM на руби. В данном случае подкупила библиотека Socket.IO, а также несколько простых модулей для работы с файлами.
Utilities for watching file trees in node.js — смысл которого отслеживать появление, удаления, а также изменения в файлах внутри указаной папки.
Пример отслеживания появления/удаления файлов внутри common.NODES_PATH и отсылка сообщения об этих событиях.
watch.createMonitor(common.NODES_PATH, function (monitor) {
monitor.on("created", function (f, stat) {
var file_name = common.removeRoot(f);
console.log(file_name + ' created');
var idx = log_files.indexOf(file_name);
if (idx < 0) {
log_files.push(file_name);
io.sockets.emit('log_files_add', [file_name]);
watchFile(file_name);
}
})
monitor.on("removed", function (f, stat) {
var file_name = common.removeRoot(f);
console.log(file_name + ' removed');
var idx = log_files.indexOf(file_name);
if (idx >= 0) {
log_files.splice(idx, 1);
io.sockets.emit('log_files_remove', [file_name]);
unwatchFile(file_name);
}
})
})
A nodejs directory walker — пакет который позволяет пройтись по внутренностям указаной папки и получать события на нахождения того или иного типа файла. Или если упрощенно то просто рекурсивно пройтись по всем вложеным папкам и посомтреть что там лежит. (пример чуть ниже)
Jade — Template engine — чтобы быстро набросать упрощенный UI и проверить задумку.
Пример реализации
- Создаем веб сервер на заданном порту;
- При первом соединении с сервером проходим с помощию walker’a по папкам и посылаем первоначальную порцию информации о существующих файлах (плюс фильтр по расширению) и последние 20 строк.
io.sockets.on('connection', function(socket) { var walk = require('walk'); // receive list of log files var walker = walk.walk(common.NODES_PATH, { followLinks: false }); walker.on('file', function(root, stat, next) { if(/log$/.test(stat.name)) { var file_name = common.removeRoot(root + '/' + stat.name); var idx = log_files.indexOf(file_name); if (idx < 0) { log_files.push(file_name); // send initial data portion var tail = spawn('tail', ['-n', 20].concat(common.NODES_PATH + '/' + file_name)); tail.stdout.on('data', function(data) { console.log('emit starting ' + file_name + ':lines' + ' with ' + data.toString('utf-8')); io.sockets.emit(file_name + ':lines', data.toString('utf-8').split('\n')); }); watchFile(file_name); } } next(); }); walker.on('end', function() { socket.emit('log_files_add', log_files); }); })
- С помощью watch создаем монитор который будет следить за появлением новых файлов или удалением. (При удалении файла блок отображающий заданный лог соответветсвенно удаляется)
function watchFile(file) { console.log('start watching: ' + file); var tail = spawn('tail', ['-f'].concat(common.NODES_PATH + '/' + file)); watching_processes[file] = tail; tail.stdout.on('data', function(data) { console.log('emit ' + file + ':lines' + ' with ' + data.toString('utf-8')); io.sockets.emit(file + ':lines', data.toString('utf-8').split('\n')); }) } function unwatchFile(file) { console.log('end watching: ' + file); var process = watching_processes[file]; process.kill(); watching_processes[file] = null; }
Запуск
В common.js настройка за какой папкой следить
var NODES_PATH = pathResolver.resolve(__dirname + '/../nodes');
При появлении *.log внутри папок и подпапок они будут отображаться в окне броузера
npm install
для установки пакетов[~/Projects/autotest_node] git:master $ mkdir nodes
[~/Projects/autotest_node] git:master $ mkdir nodes/test1
[~/Projects/autotest_node] git:master $ mkdir nodes/test2
[~/Projects/autotest_node] git:master $ echo "test_message1" >> nodes/test1/first.log
[~/Projects/autotest_node] git:master $ echo "test_message1" >> nodes/test2/first.log
[~/Projects/autotest_node] git:master $ echo "test_message2" >> nodes/test2/first.log
[~/Projects/autotest_node] git:master $ echo "test_message2" >> nodes/test1/first.log
npm start
для запуска сервераИсходный код
https://github.com/catz/autotest_node
Заключение
Данный пример может быть полезен, к примеру, если вы хотите расшарить результаты выполнения автоматических тесткейсов и дать возможность следить за результатом их выполнения другим лицам. Хотя для таких задач и есть более продвинутые средства, но если вы хотите сделать простой tail -f в броузере, то подобный пример вполне может подойти.