Go需自行构建日志收集管道:用fsnotify监听文件轮转并动态添加新文件,bufio.Scanner安全读取行(设10MB缓冲),原始字节封装为JSON或gob结构化日志,控制发送背压防OOM。
Go 本身没有内置的集中式日志收集能力,log 包只负责本地输出;要实现真正的日志收集工具(比如从多个服务抓日志、转发到 Kafka / ES / 文件归档),必须自己组装管道:监听文件变化 + 解析行 + 过滤/增强 + 序列化 + 发送。
用 fsnotify 监听日志文件追加写入
Linux 下日志轮转(如 logrotate)会重命名或删除旧文件,tail -f 能跟住是因为它检测 inotify 的 IN_MOVED_FROM 和 IN_CREATE 事件。Go 没有原生支持,得靠 fsnotify 手动处理:
- 对每个目标日志路径调用
watcher.Add(),但注意:不能只 Add 一次就完事——轮转后新文件名(如app.log.1或app.log.2024-06-01)不会自动被监听,需结合filepath.Glob定期扫描 + 对新增文件调用Add() -
fsnotify.Event.Op中真正代表“新内容追加”的是fsnotify.Write,但某些文件系统(如 NFS)可能不触发该事件,此时得 fallback 到定期stat检查Size变化 - 避免重复读:记录每个文件的
os.FileInfo.Sys().(*syscall.Stat_t).Ino和dev,防止硬链接或同名覆盖导致 offset 错乱
用 bufio.Scanner 安全读取增量日志行
别直接用 ReadLine 或 ReadString('\n')——日志行可能超长、可能含二进制数据、可能因 crash 导致半截写入。bufio.Scanner 更稳妥,但默认 MaxScanTokenSize 是 64KB:
- 启动前务必调用
scanner.Buffer(make([]byte, 4096), 10*1024*1024),把 max 设为 10MB(根据业务日志单行最大长度预估) - 遇到
scanner.Err() == bufio.ErrTooLong时,说明某行爆了 buffer,此时应丢弃该行并记录告警(log.Printf("line too long in %s, skipped", path)),而不是 panic 或阻塞 - 每次
scanner.Scan()后立即用scanner.Bytes()拿原始字节,别转string——避免 UTF-8 解码失败,后续做 JSON 封装或 Base64 编码更安全
用 gob 或 json.RawMessage 封装结构化日志再发送
原始日志行基本是纯文本,但收集端(如 Loki、Logstash)需要字段:时间戳、服务名、level、trace_id。硬解析正则太脆弱,推荐两种轻量方案: