需自定义http.Handler和ResponseWriter包装器来结构化日志,记录方法、路径、IP、状态码、耗时等;解析用strings.FieldsFunc+Trim引号;并发统计用分片sync.Map;日志写入需缓冲与flush;滚动与导出需第三方库及兜底重试。
如何用
拦截并结构化 Web 访问日志
Go 标准库不自带访问日志中间件,得自己封装
。核心是包装原 handler,在
中读取
字段(
、
、
)和响应状态码(需用
包装器捕获)。
常见错误:直接在 handler 里写日志但没记录响应耗时或状态码——因为
和
发生在 handler 执行之后,必须用自定义
拦截。
用
记开始时间,
算耗时
响应体大小无法直接获取,可统计
调用的
字节
数累加
避免在日志中打印
全量(含敏感 Cookie),按需提取
或
日志解析阶段该用
还是正则?
Nginx 或 Apache 的
access
log 默认是空格分隔但带引号字段(如
),纯
会切坏。正则虽灵活但性能差,尤其高并发下每秒千条日志时明显拖慢。
推荐组合方案:
切基础字段,再对第 6、7、9 等带引号字段用
去首尾引号。比全量正则快 3–5 倍。
立即学习
“
go语言免费学习笔记(深入)
”;
字段顺序依赖日志格式配置(如 Nginx 的
),务必先确认实际日志字段索引
时间戳字段(如
)需用
配合固定 layout,别用
忽略时区导致错位
状态码和响应大小字段可能为破折号
(如 400 错误未返回 body),解析前要
并检查空值
用
做实时统计会遇到什么并发问题?
多个 goroutine 同时写同一个
会 panic:”fatal error: concurrent map writes”。不能靠加锁包一层就完事——高频写入(如每秒万级请求)下
会成瓶颈。
php版微信js-sdk支付接口类
php版微信js-sdk支付接口类
下载
更实用的做法是分片:用
,key 的 hash 对 8 取模决定写入哪个分片。查总数时遍历 8 个分片的
累加。内存占用略增,但写吞吐提升明显。
的
比普通 map 加锁快,但只适合读多写少;日志统计写密集,分片更稳
路径聚合(如
)需预定义规则,别在运行时用正则匹配所有路径——CPU 爆掉
统计结果导出别用
拼 JSON,用
流式写入文件或网络连接,防内存暴涨
为什么
写日志文件总卡住?
直接开
没问题,但若每条日志都
→
→
,磁盘 I/O 会成为瓶颈,尤其机械盘。更糟的是没设
,断电时最后几秒日志丢失。
正确姿势是长连接式文件句柄 + 缓冲写入:
,每满 64KB 或 1 秒 flush 一次。同时用
确保日志可被外部工具(如
)读取。
别用
直接塞文件,它不缓冲也不支持自动 rotate
滚动日志要用
(第三方),但注意它的
是基于文件修改时间,不是写入时间,时钟回拨会导致误删
如果日志要发 Kafka 或 ES,本地文件只是兜底,务必加失败重试队列,别丢数据
真正难的不是解析一行日志,而是当 QPS 从 100 涨到 5000 时,你的计数器不抖动、文件不卡死、内存不持续增长——这些边界条件往往只在凌晨三点压测时暴露。
net/httphttp.HandlerServeHTTPRequestr.Methodr.URL.Pathr.RemoteAddrResponseWriterWriteHeaderWriteresponseWritertime.Now()deferWriter.HeaderUser-AgentRefererstrings.FieldsFunc"GET /api/v1/users HTTP/1.1"strings.Splitstrings.FieldsFunc(line, func(r rune) bool { return r == ' ' })strings.Trimlog_format[10/Jul/2024:15:22:34 +0800]time.Parsetime.ParseInLocation-strings.TrimSpacemap[string]intmapsync.RWMutex[8]*sync.MapRangesync.MapLoadOrStore/user/:idfmt.Sprintfjson.Encoderos.OpenFileO_WRONLY | O_CREATE | O_APPENDos.OpenFileWriteStringCloseos.File.Syncbufio.NewWriterSize(file, 64*1024)file.Chmod(0644)tail -flog.SetOutputlumberjack.LoggerMaxAge