应将 regexp.mustcompile 放在包变量或 init 函数中预编译一次,避免循环中重复调用;优先使用命名捕获组和 findstringsubmatch 提取结构化字段;日志分析输入应为 io.reader 而非 slog.logger;大文件须流式处理,禁用 readfile 和 strings.split。

匹配日志行时 regexp.Compile 别在循环里调用
Go 的正则编译开销不小,每次 regexp.Compile 都会解析、编译、生成状态机。如果在处理每行日志的循环里反复调用,性能直接掉一个数量级。
- 把
regexp.MustCompile放在包变量或初始化函数里,只编译一次 - 用
MustCompile而不是Compile—— 日志正则通常是硬编码的,出错该 panic 就 panic,别留error分支干扰主逻辑 - 避免用
.*开头的模式(比如^.*ERROR.*$),它会触发回溯爆炸;改用更具体的锚点,比如^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}).*?ERROR.*$
提取字段优先用 FindStringSubmatch 而不是 FindAllString
日志分析核心是结构化:从一行文本里抠出时间、级别、消息体、traceID 等。用 FindAllString 只能拿到字符串切片,还得自己按位置猜含义;而命名捕获组配合 FindStringSubmatch + SubexpNames 才能对齐字段语义。
- 写正则时用
(?P<name>...)</name>命名捕获组,比如^(?P<time>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\s+(?P<level>\w+)\s+(?P<msg>.*)$</msg></level></time> - 调用
re.FindStringSubmatch得到字节切片,再用re.SubexpNames()对应索引取字段名 - 注意:未匹配到的组返回空切片,别直接
string(),先判空,否则可能得到""掩盖缺失问题
log/slog 不适合做日志分析器的输入源
别试图让自己的分析工具去读 slog.Logger 的输出流 —— 它默认不带结构化字段,且输出格式依赖 Handler 实现(比如 JSONHandler 或 TextHandler)。你真正要处理的是原始文本文件、tail -f 流、或容器 stdout 重定向过来的裸日志。
- 分析工具的输入应该是
io.Reader(文件、管道、网络连接),不是某个 Go 日志库的实例 - 如果上游日志用了
slog.With加了source或trace_id,但输出没配成 JSON,那些字段就丢了,别指望靠解析文本还原 - 想提高分析准确率?推动服务端用结构化日志(如 JSON 行格式),而不是在分析端用更复杂的正则去“猜”字段
处理大日志文件时别用 ioutil.ReadFile 或 strings.Split
一个 2GB 的 access.log 用 ReadFile 会直接吃光内存;用 strings.Split(string(data), "\n") 还额外拷贝一遍字符串。日志分析必须流式处理。
立即学习“go语言免费学习笔记(深入)”;
- 用
bufio.Scanner,设好Split函数(比如ScanLines),每次只读一行 - 注意
Scanner默认缓冲区 64KB,超长日志行会被截断;调用bufio.NewReader+ReadString('\n')更可控 - 别在循环里拼接所有匹配结果到一个 slice —— 用 channel 或回调函数把结果流出去,避免内存堆积
正则写得再准,如果一次性加载全量日志进内存,程序早就被 OOM kill 了。真实场景里,慢但稳的流式处理,比快但崩的全量解析更可靠。










