golang实现实时监听文件并自动切换目录

 更新时间:2023年12月22日 10:05:25   作者:sky我的世界  
这篇文章主要给大家介绍了golang实现实时监听文件,并自动切换目录,文中通过代码示例给大家介绍的非常详细,对大家的学习或工作有一定的参考价值,需要的朋友可以参考下

应用程序使用golang开发,日志采用zap进行记录,每天会根据日期自动创建文件夹存放当天日志记录(log.log、error.log)如下图所示,如何实时记录日志内容,进行持久化入库,并且自动根据日期切换文件夹监听

解决方案

采用fsnotify来实现,fsnotify 是 Go 语言中的一个库,用于监控文件系统事件,例如文件或目录的创建、删除、修改等。它提供了一个跨平台的文件系统通知接口,允许你监听文件系统的变化并采取相应的措施。

需要注意的是需要设计数据库或者缓存来存储解析日志的offset,不然会出现如果程序重新启动,会重复解析日志文件的问题。

核心代码

package watch
 
import (
	"bufio"
	"fmt"
	"github.com/fsnotify/fsnotify"
	"go.uber.org/zap"
	"io"
	"os"
	"path/filepath"
	"time"
)
 
// 存储已处理的位置
func saveProcessedOffset(fullFileName, logLevel string, offset int64) {
	logRunRecord := system.SysRunLogWatchRecord{}
	// 尝试从数据库中找到匹配的记录
	global.DB.Where(system.SysRunLogWatchRecord{FullFileName: fullFileName, LogLevel: logLevel}).First(&logRunRecord)
	// 如果找到了匹配的记录,则更新 offset 值
	if logRunRecord.ID > 0 {
		logRunRecord.ProcessedOffset = offset
		global.DB.Save(&logRunRecord)
	} else {
		// 没有找到匹配的记录,插入新记录
		newLogRecord := system.SysRunLogWatchRecord{
			FullFileName:    fullFileName,
			LogLevel:        logLevel,
			ProcessedOffset: offset,
		}
		global.DB.Create(&newLogRecord)
	}
}
 
// 从存储中读取已处理的位置
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 检测日志
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("启动一个定时器,每分钟检查一次时间并切换目录")
	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
	// 在循环外打开文件
	logFilePath := fmt.Sprintf("%s/%s.log", directory, logLevel)
	// 当前正在解析的日志文件
	currentReadFile, err = os.Open(logFilePath)
	if err != nil {
		global.LOG.Error("无法打开文件:", 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("无法监控新目录:", zap.Error(err))
					}
					global.LOG.Info("开始监控新文件:" + newDirectory)
					//监控新文件的时候,关闭旧文件
					currentReadFile.Close()
					logFilePath := fmt.Sprintf("%s/%s.log", directory, logLevel)
					currentReadFile, err = os.Open(logFilePath)
					processedOffset = 0
					global.LOG.Info("文件已经发生变化,新文件为:" + logFilePath)
				} else {
					global.LOG.Info("新文件不存在,继续监控旧文件")
					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("无法打开文件:", 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("存储运行日志错误", zap.Error(err))
					}
				}
				if err := scanner.Err(); err != nil {
					global.LOG.Error("读取文件时发生错误", zap.Error(err))
				}
				// 更新已处理的位置
				processedOffset, err = currentReadFile.Seek(0, io.SeekEnd)
				if err != nil {
					global.LOG.Error("无法获取文件偏移量:", zap.Error(err))
				}
				// 将已处理的位置存储到文件中
				saveProcessedOffset(fmt.Sprintf("%s/%s.log", directory, logLevel), logLevel, processedOffset)
			}
		case err, ok := <-watcher.Errors:
			if !ok {
				return
			}
			global.LOG.Error("错误事件", zap.Error(err))
		}
	}
}
 
// 获取当前日期并构建日志目录路径
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) {
		// 文件不存在,创建文件夹和文件
		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 文件已经存在,开始监控:" + errorLogPath)
	} else if err == nil {
		global.LOG.Info("error.log 文件已经存在,开始监控:" + errorLogPath)
	} else {
		global.LOG.Error("os.IsNotExist error:", zap.Error(err))
		return
	}
}

其中WatchSysRuntimeLogIncrement方法传入日志路径和需要解析的日志文件名称(例如info)后缀默认.log,执行此方法即可实现逻辑

以上就是golang实现实时监听文件并自动切换目录的详细内容,更多关于golang实时监听文件的资料请关注脚本之家其它相关文章!

相关文章

  • Go语言实现一个Http Server框架(一) http库的使用

    Go语言实现一个Http Server框架(一) http库的使用

    本文主要介绍用Go语言实现一个Http Server框架中对http库的基本使用说明,文中有详细的代码示例,感兴趣的同学可以借鉴一下
    2023-04-04
  • 浅析Go 字符串指纹

    浅析Go 字符串指纹

    这篇文章主要介绍了Go 字符串指纹的相关资料,帮助大家更好的理解和学习go语言,感兴趣的朋友可以了解下
    2020-09-09
  • Go语言框架快速集成限流中间件详解

    Go语言框架快速集成限流中间件详解

    这篇文章主要为大家介绍了Go语言框架快速集成限流中间件详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-09-09
  • golang 实现两个结构体复制字段

    golang 实现两个结构体复制字段

    这篇文章主要介绍了golang 实现两个结构体复制字段,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-04-04
  • Go语言调用其它程序并获得程序输出的方法

    Go语言调用其它程序并获得程序输出的方法

    这篇文章主要介绍了Go语言调用其它程序并获得程序输出的方法,实例分析了Go调用cmd程序的技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-02-02
  • Golang Http 验证码示例实现

    Golang Http 验证码示例实现

    这篇文章主要介绍了Golang Http 验证码示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • 在Go语言中实现DDD领域驱动设计实例探究

    在Go语言中实现DDD领域驱动设计实例探究

    本文将详细探讨在Go项目中实现DDD的核心概念、实践方法和实例代码,包括定义领域模型、创建仓库、实现服务层和应用层,旨在提供一份全面的Go DDD实施指南
    2024-01-01
  • Golang实现数据结构Stack(堆栈)的示例详解

    Golang实现数据结构Stack(堆栈)的示例详解

    在计算机科学中,stack(栈)是一种基本的数据结构,它是一种线性结构,具有后进先出(Last In First Out)的特点。本文将通过Golang实现堆栈,需要的可以参考一下
    2023-04-04
  • Golang并发编程之调度器初始化详解

    Golang并发编程之调度器初始化详解

    这篇文章主要为大家详细介绍了Golang并发编程中关于调度器初始化的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一下
    2023-03-03
  • go goroutine实现素数统计的示例

    go goroutine实现素数统计的示例

    这篇文章主要介绍了go goroutine实现素数统计,本文通过示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-07-07

最新评论