Socket.io + Tail-file 的日志推送服务

话不多说旋一个

(仅供参考,没考虑性能、冗余问题)

服务端代码:

安装依赖: 

 `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);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值