您的位置:首页 >Go语言实现自定义io.Writer装饰器统计日志流量
发布于2026-03-17 阅读(0)
扫一扫,手机访问
io.Writer装饰器不能仅靠嵌入struct实现,因日志库常探测io.Closer、WriteString等接口;必须显式实现Write、Close、WriteString等方法,并用atomic保证并发安全,同时正确处理空写、部分写及panic场景。

io.Writer 装饰器不能直接套一层 struct 就完事因为 io.Writer 接口只定义了 Write([]byte) (int, error),但实际日志库(比如 log.SetOutput 或 zapcore.AddSync)常会检查底层是否实现了 io.Closer、io.StringWriter 甚至 WriteString 方法。如果只嵌入 io.Writer 字段却不透传这些方法,调用方一调用 Close() 就 panic,或者写字符串时退化成 []byte 转换,性能掉一截。
Write、Close(如果底层支持)、WriteStringWrite 和 WriteString 里,否则漏计流量(比如 zap 默认优先走 WriteString)核心是把原始 io.Writer 包一层,每次写都先累加长度,再原样转发。但要注意:返回值里的 int 是实际写出字节数,不是你统计的值;出错时也得原样返回错误,不能吞掉。
type CountingWriter struct {
w io.Writer
bytes uint64
}
func (c *CountingWriter) Write(p []byte) (int, error) {
n, err := c.w.Write(p)
atomic.AddUint64(&c.bytes, uint64(n))
return n, err
}
func (c *CountingWriter) WriteString(s string) (int, error) {
n, err := c.w.WriteString(s)
atomic.AddUint64(&c.bytes, uint64(n))
return n, err
}
func (c *CountingWriter) Close() error {
if closer, ok := c.w.(io.Closer); ok {
return closer.Close()
}
return nil
}
func (c *CountingWriter) Bytes() uint64 {
return atomic.LoadUint64(&c.bytes)
}
atomic 是因为日志往往是多 goroutine 并发写的,uint64 非原子读写在 32 位系统上会出错Write 里做格式化或缓冲——那属于业务逻辑,装饰器只负责“路过计数”w 不是 io.Closer,Close() 返回 nil 比 panic 更安全不同日志库对 io.Writer 的使用方式不同:标准库 log 只认 Write;zap 会尝试转成 io.Writer 再检查 io.Closer 和 WriteString;slog(Go 1.21+)默认用 slog.NewTextHandler 或 slog.NewJSONHandler,它们内部包装了 io.Writer,但不会主动调用 Close,除非你传的是 *os.File 这类可关对象。
log.SetOutput:直接传 &CountingWriter{w: os.Stderr} 即可zapcore.AddSync:传 zapcore.AddSync(&CountingWriter{w: writer}),确保 writer 本身支持 WriteString(比如 os.Stderr 支持,bytes.Buffer 不支持)slog.NewTextHandler:构造 handler 后,它内部会调用 Write,所以装饰器有效;但别指望它调 Close,除非你自己 wrap 的是 *os.File真实日志流里常有 Write([]byte{})(空切片)、网络 writer 返回 n < len(p)(部分写)、甚至底层 writer 在写过程中 panic(比如 pipe 关闭)。装饰器如果没处理好,统计就错,甚至导致整个日志挂死。
Write([]byte{}) 必须返回 (0, nil),且不增加计数——否则空日志也会涨字节数n,不是 len(p);否则高估流量Write panic,装饰器不能 recover——日志库需要知道失败,强行 recover 会导致错误静默Bytes() 方法里加锁,用 atomic.LoadUint64 就够;但如果你要重置计数,就得用 atomic.StoreUint64,别用赋值
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
9