golang實(shí)現(xiàn)實(shí)時(shí)監(jiān)聽(tīng)文件并自動(dòng)切換目錄
應(yīng)用程序使用golang開(kāi)發(fā),日志采用zap進(jìn)行記錄,每天會(huì)根據(jù)日期自動(dòng)創(chuàng)建文件夾存放當(dāng)天日志記錄(log.log、error.log)如下圖所示,如何實(shí)時(shí)記錄日志內(nèi)容,進(jìn)行持久化入庫(kù),并且自動(dòng)根據(jù)日期切換文件夾監(jiān)聽(tīng)。
解決方案
采用fsnotify來(lái)實(shí)現(xiàn),fsnotify 是 Go 語(yǔ)言中的一個(gè)庫(kù),用于監(jiān)控文件系統(tǒng)事件,例如文件或目錄的創(chuàng)建、刪除、修改等。它提供了一個(gè)跨平臺(tái)的文件系統(tǒng)通知接口,允許你監(jiān)聽(tīng)文件系統(tǒng)的變化并采取相應(yīng)的措施。
需要注意的是需要設(shè)計(jì)數(shù)據(jù)庫(kù)或者緩存來(lái)存儲(chǔ)解析日志的offset,不然會(huì)出現(xiàn)如果程序重新啟動(dòng),會(huì)重復(fù)解析日志文件的問(wèn)題。
核心代碼
package watch import ( "bufio" "fmt" "github.com/fsnotify/fsnotify" "go.uber.org/zap" "io" "os" "path/filepath" "time" ) // 存儲(chǔ)已處理的位置 func saveProcessedOffset(fullFileName, logLevel string, offset int64) { logRunRecord := system.SysRunLogWatchRecord{} // 嘗試從數(shù)據(jù)庫(kù)中找到匹配的記錄 global.DB.Where(system.SysRunLogWatchRecord{FullFileName: fullFileName, LogLevel: logLevel}).First(&logRunRecord) // 如果找到了匹配的記錄,則更新 offset 值 if logRunRecord.ID > 0 { logRunRecord.ProcessedOffset = offset global.DB.Save(&logRunRecord) } else { // 沒(méi)有找到匹配的記錄,插入新記錄 newLogRecord := system.SysRunLogWatchRecord{ FullFileName: fullFileName, LogLevel: logLevel, ProcessedOffset: offset, } global.DB.Create(&newLogRecord) } } // 從存儲(chǔ)中讀取已處理的位置 func readProcessedOffset(fullFileName, logLevel string) int64 { var logRunRecord system.SysRunLogWatchRecord global.DB.Where("full_file_name = ? and log_level = ?", fullFileName, logLevel).First(&logRunRecord) return logRunRecord.ProcessedOffset } // WatchSysRuntimeLogIncrement 檢測(cè)日志 func WatchSysRuntimeLogIncrement(logPath, logLevel string) { watcher, err := fsnotify.NewWatcher() if err != nil { global.LOG.Error("fsnotify watch error:", zap.Error(err)) } defer watcher.Close() ticker := time.NewTicker(1 * time.Minute) global.LOG.Info("啟動(dòng)一個(gè)定時(shí)器,每分鐘檢查一次時(shí)間并切換目錄") defer ticker.Stop() directory := getCurrentLogDirectory(logPath) initCurrentErrorLog(fmt.Sprintf("%s/%s.log", directory, logLevel)) err = watcher.Add(directory) if err != nil { global.LOG.Error("watcher Add error", zap.Error(err)) } var currentReadFile *os.File // 在循環(huán)外打開(kāi)文件 logFilePath := fmt.Sprintf("%s/%s.log", directory, logLevel) // 當(dāng)前正在解析的日志文件 currentReadFile, err = os.Open(logFilePath) if err != nil { global.LOG.Error("無(wú)法打開(kāi)文件:", zap.Error(err)) return } var processedOffset int64 for { select { case <-ticker.C: currentDate := time.Now().Format("2006-01-02") newDirectory := fmt.Sprintf("%s%s", logPath, currentDate) if newDirectory != directory { if _, err := os.Stat(fmt.Sprintf("%s/%s.log", newDirectory, logLevel)); err == nil { watcher.Remove(directory) directory = newDirectory err := watcher.Add(newDirectory) if err != nil { global.LOG.Error("無(wú)法監(jiān)控新目錄:", zap.Error(err)) } global.LOG.Info("開(kāi)始監(jiān)控新文件:" + newDirectory) //監(jiān)控新文件的時(shí)候,關(guān)閉舊文件 currentReadFile.Close() logFilePath := fmt.Sprintf("%s/%s.log", directory, logLevel) currentReadFile, err = os.Open(logFilePath) processedOffset = 0 global.LOG.Info("文件已經(jīng)發(fā)生變化,新文件為:" + logFilePath) } else { global.LOG.Info("新文件不存在,繼續(xù)監(jiān)控舊文件") processedOffset = readProcessedOffset(fmt.Sprintf("%s/%s.log", directory, logLevel), logLevel) } } else { processedOffset = readProcessedOffset(fmt.Sprintf("%s/%s.log", directory, logLevel), logLevel) } case event, ok := <-watcher.Events: if !ok { return } if event.Op&fsnotify.Write == fsnotify.Write { global.LOG.Info("解析文件:" + logFilePath) if err != nil { global.LOG.Error("無(wú)法打開(kāi)文件:", zap.Error(err)) continue } currentReadFile.Seek(processedOffset, io.SeekStart) scanner := bufio.NewScanner(currentReadFile) for scanner.Scan() { line := scanner.Text() runLog := system.SystemRunningLog{ Description: logLevel, ModuleName: logPath, Operation: line, } err = global.DB.Create(&runLog).Error if err != nil { global.LOG.Error("存儲(chǔ)運(yùn)行日志錯(cuò)誤", zap.Error(err)) } } if err := scanner.Err(); err != nil { global.LOG.Error("讀取文件時(shí)發(fā)生錯(cuò)誤", zap.Error(err)) } // 更新已處理的位置 processedOffset, err = currentReadFile.Seek(0, io.SeekEnd) if err != nil { global.LOG.Error("無(wú)法獲取文件偏移量:", zap.Error(err)) } // 將已處理的位置存儲(chǔ)到文件中 saveProcessedOffset(fmt.Sprintf("%s/%s.log", directory, logLevel), logLevel, processedOffset) } case err, ok := <-watcher.Errors: if !ok { return } global.LOG.Error("錯(cuò)誤事件", zap.Error(err)) } } } // 獲取當(dāng)前日期并構(gòu)建日志目錄路徑 func getCurrentLogDirectory(logPath string) string { currentDate := time.Now().Format("2006-01-02") return fmt.Sprintf("%s%s", logPath, currentDate) } // 初始文件 func initCurrentErrorLog(errorLogPath string) { // 判斷文件是否存在 _, err := os.Stat(errorLogPath) if os.IsNotExist(err) { // 文件不存在,創(chuàng)建文件夾和文件 err := os.MkdirAll(filepath.Dir(errorLogPath), os.ModePerm) if err != nil { global.LOG.Error("os.MkdirAll error:", zap.Error(err)) return } file, err := os.Create(errorLogPath) if err != nil { global.LOG.Error("os.Create error:", zap.Error(err)) return } defer file.Close() global.LOG.Info("error.log 文件已經(jīng)存在,開(kāi)始監(jiān)控:" + errorLogPath) } else if err == nil { global.LOG.Info("error.log 文件已經(jīng)存在,開(kāi)始監(jiān)控:" + errorLogPath) } else { global.LOG.Error("os.IsNotExist error:", zap.Error(err)) return } }
其中WatchSysRuntimeLogIncrement方法傳入日志路徑和需要解析的日志文件名稱(例如info)后綴默認(rèn).log,執(zhí)行此方法即可實(shí)現(xiàn)邏輯
以上就是golang實(shí)現(xiàn)實(shí)時(shí)監(jiān)聽(tīng)文件并自動(dòng)切換目錄的詳細(xì)內(nèi)容,更多關(guān)于golang實(shí)時(shí)監(jiān)聽(tīng)文件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Go語(yǔ)言實(shí)現(xiàn)一個(gè)Http?Server框架(一)?http庫(kù)的使用
本文主要介紹用Go語(yǔ)言實(shí)現(xiàn)一個(gè)Http?Server框架中對(duì)http庫(kù)的基本使用說(shuō)明,文中有詳細(xì)的代碼示例,感興趣的同學(xué)可以借鑒一下2023-04-04golang 實(shí)現(xiàn)兩個(gè)結(jié)構(gòu)體復(fù)制字段
這篇文章主要介紹了golang 實(shí)現(xiàn)兩個(gè)結(jié)構(gòu)體復(fù)制字段,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04Go語(yǔ)言調(diào)用其它程序并獲得程序輸出的方法
這篇文章主要介紹了Go語(yǔ)言調(diào)用其它程序并獲得程序輸出的方法,實(shí)例分析了Go調(diào)用cmd程序的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-02-02Golang Http 驗(yàn)證碼示例實(shí)現(xiàn)
這篇文章主要介紹了Golang Http 驗(yàn)證碼示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08在Go語(yǔ)言中實(shí)現(xiàn)DDD領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)實(shí)例探究
本文將詳細(xì)探討在Go項(xiàng)目中實(shí)現(xiàn)DDD的核心概念、實(shí)踐方法和實(shí)例代碼,包括定義領(lǐng)域模型、創(chuàng)建倉(cāng)庫(kù)、實(shí)現(xiàn)服務(wù)層和應(yīng)用層,旨在提供一份全面的Go DDD實(shí)施指南2024-01-01Golang實(shí)現(xiàn)數(shù)據(jù)結(jié)構(gòu)Stack(堆棧)的示例詳解
在計(jì)算機(jī)科學(xué)中,stack(棧)是一種基本的數(shù)據(jù)結(jié)構(gòu),它是一種線性結(jié)構(gòu),具有后進(jìn)先出(Last In First Out)的特點(diǎn)。本文將通過(guò)Golang實(shí)現(xiàn)堆棧,需要的可以參考一下2023-04-04go?goroutine實(shí)現(xiàn)素?cái)?shù)統(tǒng)計(jì)的示例
這篇文章主要介紹了go?goroutine實(shí)現(xiàn)素?cái)?shù)統(tǒng)計(jì),本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-07-07