必须复用同一*os.File并记录偏移量实现增量读取:用file.Seek(0, io.SeekEnd)初始化位置,每次读前Seek到lastOffset,用bufio.NewReader.ReadLine()精确更新偏移,结合inode变化检测与logrotate兼容,避免丢数据或重复消费。
os.OpenFile 保持文件句柄并跟踪读取位置直接每次打开文件重读会丢掉增量状态,必须复用同一个 *os.File 并记录偏移量。关键不是“读完再处理”,而是“边读边记 offset”。os.OpenFile 需带上 os.O_RDONLY 和 os.O_APPEND 以外的标志(比如不加 os.O_TRUNC),否则可能被意外截断。
file.Seek(0, io.SeekEnd) 初始化到末尾,后续只从这个 offset 开始读新内容file.Seek(lastOffset, io.SeekStart),再用 bufio.NewReader(file) 逐行读取lastOffset —— 必须是该行末尾的字节位置(含 n),不能用 file.Seek(0, io.SeekCurrent),因为 bufio.Reader 有内部缓冲,当前位置不可靠lastOffset 持久化到本地文件(如 log.offset)或内存映射,避免进程重启后重复消费bufio.Scanner 在增量场景下容易丢数据bufio.Scanner 默认缓冲区只有 64KB,且无法获取当前已扫描的字节偏移。当日志行超长、或文件被滚动(logrotate)时,它会静默跳过不完整行,甚至因 scanner.Err() == bufio.ErrTooLong 而中断整个流程。
bufio.NewReader.ReadLine() 或 io.ReadBytes('n'),它们返回实际读取的字节数,可精确计算 lastOffset
io.EOF 不代表文件结束,只是当前没新内容 —— 增量分析要循环等待,但别用 time.Sleep 硬等,建议结合 fsnotify 监听文件修改事件app.log → app.log.1),原文件 fd 仍有效,但新日志写入新文件 —— 此时需检测 os.Stat().Ino 是否变化,决定是否切换文件句柄不检测 inode 变化就继续读旧 fd,会导致永远读不到新日志;但过早关闭旧 fd 又可能丢失最后一段未 flush 的内容。
os.Stat(filename),对比当前 fd 的 syscall.Stat_t.Ino(需 syscall.Fstat(int(file.Fd()), &st))file.ReadAt([]byte{}, lastOffset) 尝试读剩余数据(避免截断风险),再关闭旧 fd,os.OpenFile 新文件,从 offset 0 开始读copytruncate,此时 inode 不变但文件被清空 —— 这种情况要靠 file.Seek(0, io.SeekEnd) 返回值是否突降来判断,而非仅依赖 inode单 goroutine 顺序读 + 正则全量匹配 10MB/s 日志很容易打满 CPU,尤其当 pattern 复杂或日志格式不规范时。
立即学习“go语言免费学习笔记(深入)”;
bytes.IndexByte(line, 't') 或 strings.SplitN(line, " ", 5) 替代通用正则,能提速 3–5 倍regexp.Regexp,但避免在循环里用 regexp.Compile
sync.Pool 复用 []byte 缓冲和 map[string]string 结果容器,减少 GC 压力真正难的不是读文件,而是区分“文件还在写”、“已被轮转”、“被手动清空”这三种状态,并在每种状态下给出确定的 offset 推进策略。inode、size、mtime、read 返回值要交叉验证,少依赖任何一个单一信号。
教程上新丨16GB 笔记本跑出接近 26B MoE 性能:Gemma 4 12B 基于创新架构统一处理文本 / 图像 / 声音三种模态
Gemini 3.5 Live Translate - 谷歌打造的实时语音翻译与跨语言沟通模型
《王者荣耀世界》定云除厄记完成方法
LongCat-Video-Avatar 1.5开源:具备全领域泛化能力的音频驱动视频生成模型;AI Student Impact Dataset 5 万量级多
什么是 RAG?为什么仅靠大模型的记忆力远远不够
Agent 开发:你当真需要框架吗?