Nodejs監聽日誌文件的變化

需求

最近有在作日誌文件的分析,其中有一個需求:A服務器項目須要用Nodejs監聽日誌文件的變化,當項目產生了新的日誌信息,將新的部分經過socket傳輸到B服務器項目。socket暫時不作分析。html

這個需求很簡單,經過分析咱們開始擼碼吧。 在擼碼的過程當中還能鞏固所學Nodejs的API,何樂而不爲呢?vue

所用的API

fs.watchFile()

  1. 語法
fs.watchFile(filename[, options], listener)

複製代碼

參數解析node

filename <string> | <Buffer> | <URL> ——文件名
options <Object>

  persistent <boolean> 默認值: true。——是否應該繼續運行
  interval <integer> 默認值: 5007。——輪詢目標的頻率
listener <Function>

  current <fs.Stats>  ——當前值
  previous <fs.Stats> ——以前值
複製代碼

監視 filename 的更改。 每當訪問文件時都會調用 listener 回調。git

listener 有兩個參數,當前的 stat 對象和以前的 stat 對象github

這些 stat 對象是 fs.Stat 的實例。面試

要在修改文件(而不只僅是訪問)時收到通知,則須要比較 curr.mtime 和 prev.mtime。編程

當 fs.watchFile 操做致使 ENOENT 錯誤時,它將調用一次監聽器,並將全部字段置零(或將日期設爲 Unix 紀元)。 若是文件是在那以後建立的,則監聽器會被再次調用,且帶上最新的 stat 對象。 這是 v0.10 以後的功能變化。segmentfault

使用 fs.watch() 比 fs.watchFile 和 fs.unwatchFile 更高效。 應儘量使用 fs.watch 代替 fs.watchFile 和 fs.unwatchFile。api

當 fs.watchFile() 正在監視的文件消失並從新出現時,第二次回調事件(文件從新出現)返回的 previousStat 會與第一次回調事件(文件消失)返回的 previousStat 相同。緩存

這種狀況發生在:

  • 文件被刪除,而後又恢復。
  • 文件被重命名兩次,且第二次重命名回其原來的名稱。
  1. 例子
fs.watchFile('message.text', (curr, prev) => {
  console.log(`當前的最近修改時間是: ${curr.mtime}`);
  console.log(`以前的最近修改時間是: ${prev.mtime}`);
});
複製代碼

fs.open()

語法

fs.open(path[, flags[, mode]], callback)
複製代碼

參數解析

path <string> | <Buffer> | <URL> ——文件路徑
flags <string> | <number> 默認值: 'r'。——文件系統標誌
mode <integer> 默認值: 0o666(可讀寫)。——設置文件模式(權限和粘滯位),但僅限於建立文件的狀況
callback <Function>

  err <Error> ——錯誤
  fd <integer>——文件系統流
複製代碼

fs.read()

語法

fs.read(fd, buffer, offset, length, position, callback)
複製代碼

參數解析

fd <integer> ——文件系統流
buffer <Buffer> | <TypedArray> | <DataView>——數據將寫入的緩衝區
offset <integer>—— buffer 中開始寫入的偏移量
length <integer>——要讀取的字節數
position <integer>——從文件中開始讀取的位置
callback <Function>

  err <Error>
  bytesRead <integer>
  buffer <Buffer>
複製代碼

fs.createReadStream()

  1. 語法
fs.createReadStream(path[, options])
複製代碼

參數解析

path <string> | <Buffer> | <URL>——文件路徑
options <string> | <Object>

  flags <string> 默認值: 'r'。——文件系統標誌
  encoding <string> 默認值: null。——字符編碼
  fd <integer> 默認值: null。——文件系統流
  mode <integer> 默認值: 0o666。——設置文件模式(權限和粘滯位),但僅限於建立文件的狀況
  autoClose <boolean> 默認值: true。——是否自動關閉文件描述符
  start <integer>——文件讀取的開始位置
  end <integer> 默認值: Infinity。——文件讀取的結束位置
  highWaterMark <integer> 默認值: 64 * 1024。
返回: <fs.ReadStream> 參閱可讀流。
複製代碼

若是 autoClose 爲 false,則即便出現錯誤,也不會關閉文件描述符。 應用程序負責關閉它並確保沒有文件描述符泄漏。 若是 autoClose 設爲 true(默認行爲),則在 'error' 或 'end' 事件時將自動關閉文件描述符。

mode 用於設置文件模式(權限和粘滯位),但僅限於建立文件的狀況。

  1. 例子

讀取sample.txt文件的10個字符

fs.createReadStream('sample.txt', { start: 90, end: 99 });
複製代碼

readLine.createInterface

  1. 語法
readline.createInterface(options)
複製代碼

參數解析

options <Object>

  input <stream.Readable> 要監聽的可讀流。此選項是必需的。
  output <stream.Writable> 將逐行讀取數據寫入的可寫流。
  completer <Function> 用於 Tab 自動補全的可選函數。
  terminal <boolean> 若是 input 和 output 應該被視爲 TTY,而且寫入 ANSI/VT100 轉義碼,則爲 true。 默認值: 實例化時在 output 流上檢查 isTTY。
  historySize <number> 保留的最大歷史記錄行數。 要禁用歷史記錄,請將此值設置爲 0。 僅當用戶或內部 output 檢查將 terminal 設置爲 true 時,此選項纔有意義,不然根本不會初始化歷史記錄緩存機制。 默認值: 30。
  prompt - 要使用的提示字符串。默認值: '> '。
  crlfDelay <number> 若是 \r 與 \n 之間的延遲超過 crlfDelay 毫秒,則 \r 和 \n 將被視爲單獨的行尾輸入。  crlfDelay 將被強制轉換爲不小於 100 的數字。 能夠設置爲 Infinity, 這種狀況下, \r 後跟 \n 將始終被視爲單個換行符(對於使用 \r\n 行分隔符的文件讀取多是合理的)。 默認值: 100。
  removeHistoryDuplicates <boolean> 若是爲 true, 則當添加到歷史列表的新輸入行與舊的輸入行重複時,將從列表中刪除舊行。 默認值: false。
  escapeCodeTimeout <number> readline 將會等待一個字符的持續時間(當以毫秒爲單位讀取模糊鍵序列時,可使用輸入讀取到目前爲止造成完整的鍵序列,而且能夠採起額外的輸入來完成更長的鍵序列)。 默認值: 500複製代碼

文件系統標誌

這個不須要司機,記住常見的便可,須要的時候查找。

當 flag 選項採用字符串時,可用如下標誌:

'a' - 打開文件用於追加。若是文件不存在,則建立該文件。

'ax' - 與 'a' 類似,但若是路徑已存在則失敗。

'a+' - 打開文件用於讀取和追加。若是文件不存在,則建立該文件。

'ax+' - 與 'a+' 類似,但若是路徑已存在則失敗。

'as' - 以同步模式打開文件用於追加。若是文件不存在,則建立該文件。

'as+' - 以同步模式打開文件用於讀取和追加。若是文件不存在,則建立該文件。

'r' - 打開文件用於讀取。若是文件不存在,則出現異常。

'r+' - 打開文件用於讀取和寫入。若是文件不存在,則出現異常。

'rs+' - 以同步模式打開文件用於讀取和寫入。指示操做系統繞過本地的文件系統緩存。

這對於在 NFS 掛載上打開文件時很是有用,由於它容許跳過可能過期的本地緩存。 它對 I/O 性能有很是實際的影響,所以除非須要,不然不建議使用此標誌。

這不會將 fs.open() 或 fsPromises.open() 轉換爲同步的阻塞調用。 若是須要同步的操做,則應使用 fs.openSync() 之類的。

'w' - 打開文件用於寫入。若是文件不存在則建立文件,若是文件已存在則截斷文件。

'wx' - 與 'w' 類似,但若是路徑已存在則失敗。

'w+' - 打開文件用於讀取和寫入。若是文件不存在則建立文件,若是文件已存在則截斷文件。

'wx+' - 與 'w+' 類似,但若是路徑已存在則失敗。
複製代碼

fs.Stats 類

fs.Stats 對象提供有關文件的信息。

Stats {
  dev: 2114,
  ino: 48064969,
  mode: 33188,
  nlink: 1,
  uid: 85,
  gid: 100,
  rdev: 0,
  size: 527,
  blksize: 4096,
  blocks: 8,
  atimeMs: 1318289051000.1,
  mtimeMs: 1318289051000.1,
  ctimeMs: 1318289051000.1,
  birthtimeMs: 1318289051000.1,
  atime: Mon, 10 Oct 2011 23:24:11 GMT,
  mtime: Mon, 10 Oct 2011 23:24:11 GMT,
  ctime: Mon, 10 Oct 2011 23:24:11 GMT,
  birthtime: Mon, 10 Oct 2011 23:24:11 GMT }
複製代碼

開始監聽日誌文件

前提,在app.js中調用watchFile方法,將須要監聽的文件路徑傳入該方法中。

function watchFile(filename) {
  console.log('Log monitoring...');
  // Open the file for reading and appending
  fs.open(filename, 'a+', function (err, fd) {
    if (err) {
      throw err;
    }
    var buffer;
    fs.watchFile(filename, {
      persistent: true,
      interval: 1000
    }, (curr, prev) => {
      // Compare the time before and after
      if (curr.mtime > prev.mtime) {
        // console.log(`The current latest revision time is: ${curr.mtime}`);
        // console.log(`The latest modification time is: ${prev.mtime}`);

        // Changes in the contents of documents
        buffer = new Buffer(curr.size - prev.size);
          // (curr.size - prev.size) this is the newly added length of the log file
        readFile(fd, buffer, (curr.size - prev.size), prev.size);
      }
    });
  });

}
複製代碼

讀取新增內容

function readFile(fd, buffer, length, position) {
  // read file
  fs.read(fd, buffer, 0, length, position, function (err, bytesRead, buffer) {
    if (err) {
      log.error(err);
    }
    console.log('Additional Contents', buffer.toString());
  });
}
複製代碼

額外功能:讀取歷史內容

function fetchHistoryLogs(filename) {
  const rl = readLine.createInterface({
    input: fs.createReadStream(filename, {
      enconding: 'utf8'
    }),
    output: null,
    terminal: false
  });

  rl.on('line', (line) => {
    if (line) {
      logsArr.push(line.toString());
    }
  }).on('close', () => {
    for (var i = 0; i < logsArr.length; i++) {
      // Print the data for each row
      console.log(`Original data: \n ${logsArr[i]}`);
    }
  });
}
複製代碼

參考文獻

Nodejs

Nodejs 實時監控文件內容的變化及按行讀取文本文件

Nodejs監控文件內容變化並獲取最新添加的內容

最後,別忘了給這個項目點一個star哦,謝謝支持。

blog

一個學習編程技術的公衆號。天天推送高質量的優秀博文、開源項目、實用工具、面試技巧、編程學習資源等等。目標是作到我的技術與公衆號一塊兒成長。歡迎你們關注,一塊兒進步,走向全棧大佬的修煉之路

相關文章
相關標籤/搜索