玩转高性能日志库ZAP (6)-采样
版权声明 本站原创文章 由 萌叔 发表
转载请注明 萌叔 | https://vearne.cc
前言
uber开源的高性能日志库zap, 除了性能远超logrus之外,还有很多诱人的功能,比如支持日志采样、支持通过HTTP服务动态调整日志级别。本文简单聊一下日志采样。
使用说明
Sampling:Sampling实现了日志的流控功能,或者叫采样配置,主要有两个配置参数,Initial和Thereafter,实现的效果是在1s的时间单位内,如果某个日志级别下同样内容的日志输出数量超过了Initial的数量,那么超过之后,每隔Thereafter的数量,才会再输出一次。是一个对日志输出的保护功能。
注意 这里画个重点
- 仅对"同样内容" 的日志做采样
- 默认1s的时间单位内
示例
package main
import (
"go.uber.org/zap"
)
func main() {
config := zap.NewProductionConfig()
// 默认值:Initial:100 Thereafter:100
config.Sampling = &zap.SamplingConfig{
Initial: 5, // 从第6条数据开始
Thereafter: 3, // 每3条打印一条
}
// 可以置为nil 来关闭采样
//config.Sampling = nil
config.Encoding = "console"
logger, _ := config.Build()
defer logger.Sync()
// 打印的消息要**重复**才会被执行采样动作
for i := 0; i < 100; i++ {
logger.Info("hello")
}
}
输出
仅输出36条日志,而不是100条
核心代码
代码版本 v1.10.0
// Info logs a message at InfoLevel. The message includes any fields passed
// at the log site, as well as any fields accumulated on the logger.
func (log *Logger) Info(msg string, fields ...Field) {
if ce := log.check(InfoLevel, msg); ce != nil {
ce.Write(fields...)
}
}
func (s *sampler) Check(ent Entry, ce *CheckedEntry) *CheckedEntry {
if !s.Enabled(ent.Level) {
return ce
}
counter := s.counts.get(ent.Level, ent.Message)
// 内部的counter每个 s.tick 会被重置,默认为1秒
n := counter.IncCheckReset(ent.Time, s.tick)
// first等同于Initial
// thereafter等同于Thereafter
if n > s.first && (n-s.first)%s.thereafter != 0 {
return ce
}
return s.Core.Check(ent, ce)
}
** 注意:** 这里有个比较有意思的的地方是,zap
使用消息的Hash值来判断内容是否重复。
显然hash函数有一定的几率碰撞的,但是在较小的时间区间(默认1秒)的情况下,萌叔认为还是可以接受的。
func (cs *counters) get(lvl Level, key string) *counter {
i := lvl - _minLevel
j := fnv32a(key) % _countersPerLevel
return &cs[i][j]
}
// fnv32a, adapted from "hash/fnv", but without a []byte(string) alloc
func fnv32a(s string) uint32 {
const (
offset32 = 2166136261
prime32 = 16777619
)
hash := uint32(offset32)
for i := 0; i < len(s); i++ {
hash ^= uint32(s[i])
hash *= prime32
}
return hash
}
有兴趣的还可以看萌叔的另一个代码示例
test_zap2.go
总结
通过日志采样,降低了高并发下打印日志的开销,能够有效提升提服务的吞吐能力。