Fork me on GitHub

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

总结

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


微信公众号

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据