不能直接用bufio.scanner处理大日志文件,因其默认64kb缓冲区遇超长行(如几mb json)会panic“token too long”,且不支持跳过行、随机定位;可靠做法是改用bufio.reader.readstring('\n')逐行读取并手动控制边界与内存。

为什么不能直接用 bufio.Scanner 处理大日志文件
因为 bufio.Scanner 默认缓冲区只有 64KB,遇到超长行(比如单行几 MB 的 JSON 日志)会直接 panic:scanner: token too long;而且它不支持跳过指定行数、无法随机定位,做阈值切割时容易内存暴涨或卡死。
真正可靠的做法是绕过 Scanner,用 bufio.Reader 配合手动行边界识别:
- 每次调用
reader.ReadString('\n')或reader.ReadBytes('\n'),显式控制读取行为 - 对每行做长度检查,避免单行耗尽内存
- 用计数器累计行数,达到阈值就关闭当前文件句柄、新建一个
如何安全地按行数切分并避免文件句柄泄漏
关键不是“切”,而是“及时关闭旧文件 + 原子化新建新文件”。常见错误是忘记 fclose 或在 panic 路径里没 defer 关闭。
实操建议:
- 用
os.Open打开源日志,用bufio.NewReader包装 - 切割目标文件用
os.Create,但必须在每次切换前oldFile.Close() - 给每个输出文件加序号命名,例如
log_001.log,避免覆盖 - 在循环外用
defer关闭源文件,在循环内每次生成新文件后立即defer newFile.Close()—— 注意:这个 defer 要写在 for 循环体内,否则只生效最后一次
io.CopyN 不能用于按行切割的真相
io.CopyN 按字节数复制,不是按行。日志按行数切分 ≠ 按字节数切分。哪怕你估算平均每行 200 字节,实际可能有空行、超长 traceID、嵌套 JSON,误差会迅速累积,第 10 个分片就偏移几百行。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
必须逐行读取并计数。伪代码逻辑是:
lineCount := 0
for {
line, err := reader.ReadString('\n')
if err == io.EOF { break }
if err != nil { /* 处理中断或损坏 */ }
if lineCount%threshold == 0 && lineCount > 0 {
currentOut.Close()
currentOut = mustCreateNewFile()
}
currentOut.WriteString(line)
lineCount++
}
Windows 下换行符 \r\n 导致行数误判怎么办
Go 的 ReadString('\n') 在 Windows 上仍能正确识别 \r\n 结尾(底层会吞掉 \r),但如果你用 ReadBytes 或自己解析,就可能把 \r\n 当两行处理。
稳妥做法统一用:
-
reader.ReadString('\n')—— 它内部已适配 CRLF - 避免手动
bytes.Split(buf, []byte{'\n'}),除非你确定输入全是 LF - 若原始日志混用换行符(比如部分行是
\r单独结尾),先做一次预清洗,或改用strings.TrimRight(line, "\r\n")再计数
阈值切割真正的难点不在读,而在边界一致性:同一行不能被拆到两个文件里,也不能因编码/换行差异漏计。这些细节不验证,上线后分片大小就会漂移。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










