版权声明 本站原创文章 由 萌叔 发表
转载请注明 萌叔 | http://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

总结

通过日志采样,降低了高并发下打印日志的开销,能够有效提升提服务的吞吐能力。


微信公众号