话不多说旋一个
(仅供参考,没考虑性能、冗余问题)
服务端代码:
安装依赖:
`npm i socket.io tail-file express -S`
const app = require('express')();
const fs = require("fs");
const path = require("path");
const server = require('http').createServer(app);
const Tail = require('tail-file');
const io = require('socket.io')(server);
const logWs = io.of('/log'); // namespace
const getFilePath = (appId) => const getFilePath = (appId) => path.resolve(__dirname, `logs/${appId}/test.log`); // 测试
server.listen(3000);
class TailModel {
static data = new Map();
static async init (filePath) {
try {
fs.accessSync(filePath);
} catch (error) {
await onFileCreate(filePath);
}
TailModel.add(filePath);
}
static add (filePath) {
const mytail = new Tail(filePath);
this.data.set(filePath, mytail);
mytail.start();
return mytail;
}
static getTail (filePath) {
let mytail = this.data.get(filePath);
if (!mytail) mytail = this.add(filePath);
return mytail;
}
static del (filePath) {
let mytail = this.data.get(filePath);
if (mytail) mytail.stop();
this.data.delete(filePath);
}
}
class RoomModel {
static data = new Map();
static add (filePath, socketId) {
const room = this.data.get(filePath);
if (!room) this.data.set(filePath, new Map());
this.data.get(filePath).set(socketId);
}
static del (filePath, socketId) {
const room = this.data.get(filePath);
if (!room) return;
room.delete(socketId);
}
static getRoom (filePath) {
const room = this.data.get(filePath);
if (!room) this.data.set(filePath, new Map());
return this.data.get(filePath);
}
}
// 等待文件被创建 promise
// 该方法会阻塞进程等待文件被创建,实际情况考虑等待文件被创建还是直接推送错误日志
function onFileCreate (filePath) {
let watcherpath = path.resolve(filePath);
return new Promise((resolve, reject) => {
let listenerFunc = (curr,prev)=>{
const ctime = Date.parse(curr.mtime);
const ptime = Date.parse(prev.mtime);
if (ctime > 0 && (ptime == 0 || ctime == ptime)) {
// 文件被创建
fs.unwatchFile(watcherpath, listenerFunc);
resolve();
}
}
fs.watchFile(watcherpath, { interval: 2000 }, listenerFunc);
})
}
logWs.on('connection', async (socket) => {
const appId = socket.handshake.query.appId;
console.log(appId, 'has connected');
const filePath = getFilePath(appId);
// 添加到房间
RoomModel.add(filePath, socket.id);
await TailModel.init(filePath);
const mytail = TailModel.getTail(filePath);
mytail.on('line', line => {
socket.emit('message', { code: 0, data: line });
});
mytail.on('error', err => {
console.log('mytail error', err);
});
// 当客户端断开时
socket.on('disconnect', (reason) => {
// 离开房间 判断房间人数,如果为零则关闭该房间对应的文件监听
RoomModel.del(filePath, socket.id);
const room = RoomModel.getRoom(filePath);
if (room && room.size === 0) {
TailModel.del(filePath)
}
});
});
客户端的调用
function initSocket() {
if (this.m_socket) this.m_socket.close();
const url = `ws://localhost:3000`
this.m_socket = io.connect(url);
// 监听 message 会话
this.m_socket.on('message', res => {
if (res && res.code === 0) {
this.addLog(res.data);
}
});
}
附:监听文件变化的函数
function watchFileStatus (filePath) {
let watcherpath = path.resolve(filePath);
let listenerFunc = (curr,prev)=>{
const ctime = Date.parse(curr.mtime);
const ptime = Date.parse(prev.mtime);
if (ctime > 0 && (ptime == 0 || ctime == ptime)) {
// 文件被创建
} else if (ctime == 0 && ptime == 0) {
// 文件不存在
} else if (ctime == 0 && ptime > 0) {
// 文件被删除
} else {
// 文件被编辑
}
}
fs.watchFile(watcherpath, { interval: 2000 }, listenerFunc);
}