Go日志库-zap

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: Go日志库-zap

Go日志库-zap

简介

  zap是uber开源的日志包,以高性能著称,很多公司的日志包都是基于zap改造而来。zap除了具有日志基本的功能之外,还具有很多强大的特性:

  • 支持常用的日志级别,例如:Debug、Info、Warn、Error、DPanic、Panic、Fatal。
  • 性能非常高,zap具有非常高的性能,适合对性能要求比较高的场景。
  • 像logrus一样,支持结构化的日志记录。
  • 支持预设日志字段。
  • 支持针对特定的日志级别,输出调用堆栈。
  • 支持hook。

快速开始

package main

import (
    "time"

    "go.uber.org/zap"
)

func main() {
   
    logger, _ := zap.NewProduction()
    defer logger.Sync() // flushes buffer, if any
    url := "http://beyondinfo.com.cn"

    logger.Info("failed to fetch URL",
        zap.String("url", url),
        zap.Int("attempt", 3),
        zap.Duration("backoff", time.Second),
    )

    //如果觉得Logger的日志格式比较繁琐,可以使用更加便捷的SugaredLogger,调用logger.Sugar()即可创建SugaredLogger。
    //SugaredLogger的使用比Logger简单,但性能比Logger低 50% 左右,可以用在调用次数不高的函数中,调用方式如下:

    sugar := logger.Sugar()
    sugar.Infow("failed to fetch URL",
        "url", url,
        "attempt", 3,
        "backoff", time.Second,
    )
    sugar.Infof("Failed to fetch URL: %s", url)
}
{"level":"info","ts":1626404163.6161273,"caller":"demo/zap_quick.go:13","msg":"failed to fetch URL","url":"http://beyondinfo.com.cn","attempt":5,"backoff":1}
{"level":"info","ts":1626404163.616174,"caller":"demo/zap_quick.go:20","msg":"failed to fetch URL","url":"http://beyondinfo.com.cn","attempt":3,"backoff":1}
{"level":"info","ts":1626404163.6162095,"caller":"demo/zap_quick.go:25","msg":"Failed to fetch URL: http://beyondinfo.com.cn"}

  默认的日志输出格式为JSON格式,并记录了文件名和行号。

  上述代码通过zap.NewProduction()创建了一个logger,zap还提供了zap.NewExample()、zap.NewDevelopment()来快速创建一个logger。

  不同方法创建的logger具有不同的设置:

  • Example适合用在测试代码中
  • Development在开发环境中使用
  • Production用在生产环境。
  • 如果想自定义logger,可以调用zap.New()方法来创建。

  logger提供了Debug、Info、Warn、Error、Panic、Fatal等方法,用来记录不同级别的日志。在程序退出时,注意要调用defer logger.Sync()将缓存中的日志刷新到磁盘文件中。

  当我们对日志的性能要求比较高时,可以使用Logger而非SugaredLogger,Logger性能更好,内存分配次数更少。为了提高性能,Logger没有使用interface和反射,并且Logger只支持结构化的日志,所以在使用Logger时,需要指定具体的类型和key-value格式的日志字段,例如:

logger.Info("failed to fetch URL",
    zap.String("url", url),
    zap.Int("attempt", 3),
    zap.Duration("backoff", time.Second),
)

定制Logger

  可以使用NexExample()/NewDevelopment()/NewProduction()函数创建默认的Logger,每种方法创建的Logger配置不一样,也可以创建一个定制化的Logger,创建方式如下:

package main

import (
    "encoding/json"

    "go.uber.org/zap"
)

func main() {
   
    rawJSON := []byte(`{
    "level":"debug",
    "encoding":"json",
    "outputPaths": ["stdout", "test.log"],
    "errorOutputPaths": ["stderr"],
    "initialFields":{"name":"dj"},
    "encoderConfig": {
      "messageKey": "message",
      "levelKey": "level",
      "levelEncoder": "lowercase"
    }
  }`)

    var cfg zap.Config
    if err := json.Unmarshal(rawJSON, &cfg); err != nil {
   
        panic(err)
    }
    logger, err := cfg.Build()
    if err != nil {
   
        panic(err)
    }
    defer logger.Sync()

    logger.Info("server start work successfully!")
}

  运行结果:

{"level":"info","message":"server start work successfully!","name":"dj"}

  上面示例调用zap.Config的Build方法构造一个logger,zap.Config定义如下:

type Config struct {
   
    //最小的日志级别,可以通过SetLevel改变。
    Level AtomicLevel `json:"level" yaml:"level"`

    //设置Logger的模式为development模式。更加容易接受堆栈追踪
    Development bool `json:"development" yaml:"development"`

    // 禁用调用信息. 该字段值为 true 时, 日志中将不再显示该日志所在的函数调用信息。
    DisableCaller bool `json:"disableCaller" yaml:"disableCaller"`

    // 禁用自动堆栈跟踪捕获。
    DisableStacktrace bool `json:"disableStacktrace" yaml:"disableStacktrace"`

    // 流控配置, 也叫采样. 单位是每秒钟, 作用是限制日志在每秒钟内的输出数量, 以防止CPU和IO被过度占用。
    Sampling *SamplingConfig `json:"sampling" yaml:"sampling"`

    //指定日志编码器, 目前仅支持两种编码器:console和json,默认为json。
    Encoding string `json:"encoding" yaml:"encoding"`

    // 编码配置。
    EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"`

    // 配置日志标准输出,可以配置多个日志输出路径, 一般情况可以仅配置标准输出或输出到文件, 如有需求的话, 也可以两者同时配置。
    OutputPaths []string `json:"outputPaths" yaml:"outputPaths"`

    // 错误输出路径,可以是多个。
    ErrorOutputPaths []string `json:"errorOutputPaths" yaml:"errorOutputPaths"`

    // 初始化字段配置, 该配置的字段会以结构化的形式打印在每条日志输出中。
    InitialFields map[string]interface{
   } `json:"initialFields" yaml:"initialFields"`
}

  其中EncoderConfig为编码配置:

type EncoderConfig struct {
   
    // 日志中信息的键名,默认为msg。
    MessageKey    string `json:"messageKey" yaml:"messageKey"`
    // 日志中级别的键名,默认为level。
    LevelKey      string `json:"levelKey" yaml:"levelKey"`
    TimeKey       string `json:"timeKey" yaml:"timeKey"`
    NameKey       string `json:"nameKey" yaml:"nameKey"`
    CallerKey     string `json:"callerKey" yaml:"callerKey"`
    FunctionKey   string `json:"functionKey" yaml:"functionKey"`
    StacktraceKey string `json:"stacktraceKey" yaml:"stacktraceKey"`
    LineEnding    string `json:"lineEnding" yaml:"lineEnding"`
    // 日志中级别的格式,默认为小写,如debug/info。
    EncodeLevel    LevelEncoder    `json:"levelEncoder" yaml:"levelEncoder"`
    EncodeTime     TimeEncoder     `json:"timeEncoder" yaml:"timeEncoder"`
    EncodeDuration DurationEncoder `json:"durationEncoder" yaml:"durationEncoder"`
    EncodeCaller   CallerEncoder   `json:"callerEncoder" yaml:"callerEncoder"`
    EncodeName NameEncoder `json:"nameEncoder" yaml:"nameEncoder"`
    ConsoleSeparator string `json:"consoleSeparator" yaml:"consoleSeparator"`
}

选项

  zap支持多种选项,选项的使用方式如下:

package main

import "go.uber.org/zap"

func main() {
   
    logger, _ := zap.NewProduction(zap.AddCaller())
    defer logger.Sync()

    logger.Info("hello world")
}

  运行结果:

{"level":"info","ts":1607010625.6718638,"caller":"zap/example3.go:9","msg":"hello world"}

  上述日志输出了日志的调用信息(文件名:行号)"caller":"zap/example3.go:9"。zap提供了多个选项可供选择:

  • AddStacktrace(lvl zapcore.LevelEnabler):用来在指定级别及以上级别输出调用堆栈。
  • zap.WithCaller(enabled bool):指定是否在日志输出内容中增加文件名和行号。
  • zap.AddCaller():与zap.WithCaller(true)等价,指定在日志输出内容中增加行号和文件名。
  • zap. AddCallerSkip(skip int):指定在调用栈中跳过的调用深度,否则通过调用栈获得的行号可能总是日志组件中的行号。
  • zap. IncreaseLevel(lvl zapcore.LevelEnabler):提高日志级别,如果传入的lvl比当前logger的级别低,则不会改变日志级别。
  • ErrorOutput(w zapcore.WriteSyncer):指定日志组件中出现异常时的输出位置。
  • Fields(fs ...Field):添加公共字段。
  • Hooks(hooks ...func(zapcore.Entry) error):注册钩子函数,用来在日志打印时同时调用hook方法。
  • WrapCore(f func(zapcore.Core) zapcore.Core):替换Logger的zapcore.Core。 -Development():将Logger修改为Development模式。

  预设字段:

package main

import "go.uber.org/zap"

func main() {
   
    logger := zap.NewExample(zap.Fields(
        zap.Int("userID", 10),
        zap.String("requestID", "fbf54504"),
    ))

    logger.Debug("This is a debug message")
    logger.Info("This is a info message")
}

全局Logger

  zap提供了2个全局Logger,可以方便我们调用:

  • zap.Logger:可通过zap.L()获得,提供了Debug()、Info()、Warn()、Error()、Panic()、DPanic()、Fatal()方法来记录日志。
  • zap.SugaredLogger:可通过zap.S()获得,提供了Debugf()、Debugw()、Infof()、Infow()、Warnf()、Warnw()、Errorf()、Errorw()、Panicf()、Panicw()、DPanicf()、DPanicw()、Fatalf()、Fatalw()方法来记录日志。

  默认的全局Logger不会记录任何日志,它是一个无用的Logger,例如zap.L()返回了名为_globalL的Logger,_globalL定义为:

_globalL  = NewNop()
func NewNop() *Logger {
    return &Logger{
        core:        zapcore.NewNopCore(),
        errorOutput: zapcore.AddSync(ioutil.Discard),
        addStack:    zapcore.FatalLevel + 1,
    }
}

  zapcore.NewNopCore()函数定义为:

type nopCore struct{
   }

// NewNopCore returns a no-op Core.
func NewNopCore() Core                                        {
    return nopCore{
   } }
func (nopCore) Enabled(Level) bool                            {
    return false }
func (n nopCore) With([]Field) Core                           {
    return n }
func (nopCore) Check(_ Entry, ce *CheckedEntry) *CheckedEntry {
    return ce }
func (nopCore) Write(Entry, []Field) error                    {
    return nil }
func (nopCore) Sync() error                                   {
    return nil }

// NewCore creates a Core that writes logs to a WriteSyncer.
func NewCore(enc Encoder, ws WriteSyncer, enab LevelEnabler) Core {
   
    return &ioCore{
   
        LevelEnabler: enab,
        enc:          enc,
        out:          ws,
    }
}

一、简介

  zap 提供快速、结构化、分级的日志记录。

  Zap包括一个无反射、零分配的 JSON 编码器,并且基础 Logger 力求尽可能避免序列化开销和分配。

  通过在此基础上构建高级 SugaredLogger,zap 允许用户选择何时需要计算每次分配以及何时更喜欢更熟悉的松散类型的 API。

二、架构

  Zap将内部模块划分成4部分:

  API 接口部分,主要模块是Logger,Config,Field等模块,Config提供用户灵活配置接口,Logger实例化等接口。Logger模块主要提供用户一组写日志接口,Field提供一组kv值。
日志编码部分,主要的模块有Encoder、jsonEncoder/consoleEncoder、Buffer。Encoder提供编码管理,jsonEncoder/consoleEncoder提供具体的编解码功能,Buffer提供编码的数据载体。
写日志部分,这部分的主要模块有lockedWriteSyncer、Sink、os。lockedWriteSyncer是用来防止并发写,Sink是对os的一层包装。如果设置了hook会在这里将数据转入hook中
最后一部分是日志的管理,主要的模块有ioCore、CheckedEntry、Entry。其实就是管理日志编码和写日志文件。

三、配置

  构建zap可以使用预设的配置NewExample、NewProduction 和 NewDevelopment

logger, err := zap.NewProduction()
if err != nil {
  log.Fatalf("can't initialize zap logger: %v", err)
}
defer logger.Sync()

  但是对于大部分来说都需要自定义配置

  基本配置;

package main

import (
    "encoding/json"

    "go.uber.org/zap"
)

func main() {

    rawJSON := []byte(`{
      "level": "debug",
      "encoding": "json",
      "outputPaths": ["stdout", "/tmp/logs"],
      "errorOutputPaths": ["stderr"],
      "initialFields": {"foo": "bar"},
      "encoderConfig": {
        "messageKey": "message",
        "levelKey": "level",
        "levelEncoder": "lowercase"8
      }
    }`)

    var cfg zap.Config
    if err := json.Unmarshal(rawJSON, &cfg); err != nil {
        panic(err)
    }
    logger, err := cfg.Build()
    if err != nil {
        panic(err)
    }
    defer logger.Sync()

    logger.Info("logger construction succeeded")
}

  高级配置:

package main

import (
    "io/ioutil"
    "os"

    "go.uber.org/zap"
    "go.uber.org/zap/zapcore"
)

func main() {

    // First, define our level-handling logic.
    highPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
        return lvl >= zapcore.ErrorLevel
    })
    lowPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
        return lvl < zapcore.ErrorLevel
    })

    // Assume that we have clients for two Kafka topics. The clients implement
    // zapcore.WriteSyncer and are safe for concurrent use. (If they only
    // implement io.Writer, we can use zapcore.AddSync to add a no-op Sync
    // method. If they're not safe for concurrent use, we can add a protecting
    // mutex with zapcore.Lock.)
    topicDebugging := zapcore.AddSync(ioutil.Discard)
    topicErrors := zapcore.AddSync(ioutil.Discard)

    // High-priority output should also go to standard error, and low-priority
    // output should also go to standard out.
    consoleDebugging := zapcore.Lock(os.Stdout)
    consoleErrors := zapcore.Lock(os.Stderr)

    // Optimize the Kafka output for machine consumption and the console output
    // for human operators.
    kafkaEncoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
    consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())

    // Join the outputs, encoders, and level-handling functions into
    // zapcore.Cores, then tee the four cores together.
    core := zapcore.NewTee(
        zapcore.NewCore(kafkaEncoder, topicErrors, highPriority),
        zapcore.NewCore(consoleEncoder, consoleErrors, highPriority),
        zapcore.NewCore(kafkaEncoder, topicDebugging, lowPriority),
        zapcore.NewCore(consoleEncoder, consoleDebugging, lowPriority),
    )

    // From a zapcore.Core, it's easy to construct a Logger.
    logger := zap.New(core)
    defer logger.Sync()
    logger.Info("constructed a logger")
}

四、兼容标准库的日志

//将标准库的log重定向到std即全局的zap.
func RedirectStdLog(l *Logger) func()
//将标准库日志重定向到looger
func RedirectStdLogAt(l * Logger , level zapcore . Level ) (func(), error )
package main

import (
    "log"

    "go.uber.org/zap"
)
func main() {
    logger := zap.NewExample()
    defer logger.Sync()

    undo := zap.RedirectStdLog(logger)
    defer undo()

    log.Print("redirected standard library")
}
//output
//{"level":"info","msg":"redirected standard library"}

五、全局配置

//替换全局的Logger 和 SugaredLogger,并返回一个函数来恢复原始值。并发使用是安全的。
func ReplaceGlobals(logger * Logger ) func()
package main

import (
    "go.uber.org/zap"
)

func main() {
    logger := zap.NewExample()
    defer logger.Sync()

    undo := zap.ReplaceGlobals(logger)
    defer undo()

    zap.L().Info("replaced zap's global loggers")
}

六、函数

//将多个WriteSyncers组合成一个单一的,锁定的WriteSyncer。如果没有提供任何输入,它将返回一个无操作的WriteSyncer。它的提供纯粹是为了方便;结果与单独使用 zapcore.NewMultiWriteSyncer 和 zapcore.Lock 没有什么不同。
func CombineWriteSyncers(writers ...zapcore.WriteSyncer) zapcore.WriteSyncer

//LevelFlag 使用标准库的 flag.Var 来声明一个具有指定名称、默认值和使用指南的全局标志。返回值是一个指向标志值的指针。
func LevelFlag(name string, defaultLevel zapcore.Level, usage string) *zapcore.Level

//为开发环境返回一个默认的EncoderConfig
func NewDevelopmentEncoderConfig() zapcore.EncoderConfig

//为生产环境返回一个默认的EncoderConfig
func NewProductionEncoderConfig() zapcore.EncoderConfig

//返回一个 *log.Logger,它在 InfoLevel 写入提供的 zap Logger。
func NewStdLog(l *Logger) *log.Logger

//返回 *log.Logger,它以所需级别写入提供的 zap 记录器。
func NewStdLogAt(l *Logger, level zapcore.Level) (*log.Logger, error)

//打开或创建每个指定的资源,并将它们组合到一个锁定的WriteSyncer中。它还返回遇到的任何错误和关闭任何打开文件的函数。不传递 URL 会返回一个无操作的 WriteSyncer。
func Open(paths ...string) (zapcore.WriteSyncer, func(), error)

//将标准库的log重定向到std即全局的zap.
func RedirectStdLog(l *Logger) func()

//将标准库日志重定向到looger
func RedirectStdLogAt(l * Logger , level zapcore . Level ) (func(), error )

//注册一个编码器构造函数,然后 Config 结构可以引用它。默认情况下,注册“json”和“console”编码器。尝试注册名称已被占用的编码器会返回错误。
func RegisterEncoder(name string, constructor func(zapcore.EncoderConfig) (zapcore.Encoder, error)) error

//使用特定方案为所有接收器注册用户提供的工厂。
func RegisterSink(scheme string, factory func(*url.URL) (Sink, error)) error

//替换全局的Logger 和 SugaredLogger,并返回一个函数来恢复原始值。并发使用是安全的。
func ReplaceGlobals(logger * Logger ) func()

七、日志级别

  zap定义的日志级别:

const (
    DebugLevel = zapcore.DebugLevel
    InfoLevel = zapcore.InfoLevel
    WarnLevel = zapcore.WarnLevel
    ErrorLevel = zapcore.ErrorLevel
    DPanicLevel = zapcore.DPanicLevel
    PanicLevel = zapcore.PanicLevel
    FatalLevel = zapcore.FatalLevel
)

  原子日志级别定义:

type AtomicLevel struct {
   
    l *atomic.Int32
}

  相关函数:

// 创建一个启用了 InfoLevel 和以上日志记录的 AtomicLevel。
func NewAtomicLevel() AtomicLevel
// 创建一个 AtomicLevel,然后使用给定的级别调用 SetLevel。
func NewAtomicLevelAt(l zapcore . Level ) AtomicLevel
// 实现了 zapcore.LevelEnabler 接口,它允许使用 AtomicLevel 代替传统的静态级别。
func (lvl AtomicLevel) Enabled(l zapcore.Level) bool
// 返回当前的最小日志级别
func (lvl AtomicLevel) Level() zapcore.Level
//返回当前日志级别的切片,eg"info"
func (lvl AtomicLevel) MarshalText() (text []byte, err error)
// 设置日志级别
func (lvl AtomicLevel) SetLevel(l zapcore.Level)
// 返回当前级别的字符串,eg"info"
func (lvl AtomicLevel) String() string
// 
func (lvl *AtomicLevel) UnmarshalText(text []byte) error

八、配置

type Config struct {
    //最小的日志级别,可以通过SetLevel改变。
    Level AtomicLevel `json:"level" yaml:"level"`
    //设置Logger的模式为development模式。更加容易接受堆栈追踪
    Development bool `json:"development" yaml:"development"`
    // 禁用调用信息. 该字段值为 true 时, 日志中将不再显示该日志所在的函数调用信息。
    DisableCaller bool `json:"disableCaller" yaml:"disableCaller"`
    // 禁用自动堆栈跟踪捕获。
    DisableStacktrace bool `json:"disableStacktrace" yaml:"disableStacktrace"`
    // 流控配置, 也叫采样. 单位是每秒钟, 作用是限制日志在每秒钟内的输出数量, 以防止CPU和IO被过度占用。
    Sampling *SamplingConfig `json:"sampling" yaml:"sampling"`
    //指定日志编码器, 目前仅支持两种编码器:console和json,默认为json。
    Encoding string `json:"encoding" yaml:"encoding"`
    // 编码配置。
    EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"`
    // 配置日志标准输出,可以配置多个日志输出路径, 一般情况可以仅配置标准输出或输出到文件, 如有需求的话, 也可以两者同时配置。
    OutputPaths []string `json:"outputPaths" yaml:"outputPaths"`
    // 错误输出路径,可以是多个。
    ErrorOutputPaths []string `json:"errorOutputPaths" yaml:"errorOutputPaths"`
    // 初始化字段配置, 该配置的字段会以结构化的形式打印在每条日志输出中。
    InitialFields map[string]interface{} `json:"initialFields" yaml:"initialFields"`
}

  相关函数

//一个合理的开发日志配置。在 DebugLevel 及更高级别启用日志记录。它启用开发模式(使 DPanicLevel 日志恐慌),使用控制台编码器,写入标准错误,并禁用采样。堆栈跟踪会自动包含在 WarnLevel 及更高级别的日志中。
func NewDevelopmentConfig() Config
//NewProductionConfig 是合理的生产日志配置。在 InfoLevel 及更高级别启用日志记录。它使用 JSON 编码器,写入标准错误,并启用采样。堆栈跟踪会自动包含在 ErrorLevel 及更高级别的日志中。
func NewProductionConfig() Config
//从 Config 和 Options 构造一个记录器。
func (cfg Config) Build(opts ...Option) (*Logger, error)

九、记录器

9.1 Logger定义

type Logger struct {
    core zapcore.Core
    development bool
    addCaller   bool
    onFatal     zapcore.CheckWriteAction // default is WriteThenFatal
    name        string
    errorOutput zapcore.WriteSyncer
    addStack zapcore.LevelEnabler
    callerSkip int
    clock zapcore.Clock
}

9.2 相关函数

// 返回全局的logger
func L() *Logger
// 使用zapcore.Core和Options构造一个新的 Logger。如果传递的zapcore.Core为nil,则它会使用无操作实现。
func New(core zapcore.Core, options ...Option) *Logger
// 构建开发的logger,将 DebugLevel 及以上的日志写入标准错误。
func NewDevelopment(options ...Option) (*Logger, error)
//NewExample 构建了一个 Logger,将 DebugLevel 及以上日志写入标准输出为JSON,但省略了时间戳和调用函数以保持示例输出的简短和确定性。
func NewExample(options ...Option) *Logger
//返回一个无操作记录器。它从不写出日志或内部错误,也从不运行用户定义的钩子。
func NewNop() *Logger
//构建了一个合理的生产记录器,将InfoLevel 和更高级别的日志作为 JSON 写入标准错误。
func NewProduction(options ...Option) (*Logger, error)
//如果启用了在指定级别记录消息,则 Check 返回 CheckedEntry。这是一个完全可选的优化;在高性能应用程序中,Check 可以帮助避免分配一个切片来保存字段。
func (log *Logger) Check(lvl zapcore.Level, msg string) *zapcore.CheckedEntry
//返回底层的zapcore.Core
func (log *Logger) Core() zapcore.Core
//在 DPanicLevel 记录一条消息。该消息包括在日志站点传递的任何字段,以及记录器上累积的任何字段。如果记录器处于开发模式,则它会发生恐慌(DPanic 的意思是“开发恐慌”)。这对于捕获可恢复但不应该发生的错误很有用。
func (log *Logger) DPanic(msg string, fields ...Field)

// 打印日志
func (log *Logger) Debug(msg string, fields ...Field)
func (log *Logger) Error(msg string, fields ...Field)
func (log *Logger) Fatal(msg string, fields ...Field)
func (log *Logger) Info(msg string, fields ...Field)
func (log *Logger) Panic(msg string, fields ...Field)
func (log *Logger) Warn(msg string, fields ...Field)

//将一个新的路径段添加到记录器的名称中。段由句点连接。默认情况下,记录器是未命名的。
func (log *Logger) Named(s string) *Logger

// 包装了Logger以提供更易用但速度稍慢的 API。
func (log *Logger) Sugar() *SugaredLogger
// 刷新日志缓冲
func (log *Logger) Sync() error
// 创建一个子记录器并向其字段。添加到子项的字段不会影响父项,反之亦然。
func (log *Logger) With(fields ...Field) *Logger
// 克隆当前的 Logge并添加opts,并返回结果 Logger。同时使用是安全的。
func (log *Logger) WithOptions(opts ...Option) *Logger

9.3 Check Example

package main

import (
    "go.uber.org/zap"
)

func main() {
    logger := zap.NewExample()
    defer logger.Sync()

    if ce := logger.Check(zap.DebugLevel, "debugging"); ce != nil {
        ce.Write(
            zap.String("foo", "bar"),
            zap.String("baz", "quux"),
        )
    }
}

9.4 Named Example

package main

import (
    "go.uber.org/zap"
)

func main() {
    logger := zap.NewExample()
    defer logger.Sync()

    logger.Info("no name")

    main := logger.Named("main")
    main.Info("main logger")

    main.Named("subpackage").Info("sub-logger")
}
//output
//{"level":"info","msg":"no name"}
//{"level":"info","logger":"main","msg":"main logger"}
//{"level":"info","logger":"main.subpackage","msg":"sub-logger"}

9.5 切换记录器

logger := zap.NewExample()
defer logger.Sync()
sugar := logger.Sugar()
plain := sugar.Desugar()

9.6 logger与sugarLogger的区别

  在性能不错但不重要的情况下,请使用 SugaredLogger。它比其他结构化日志记录包快 4-10 倍,并支持结构化和 printf 风格的日志记录。

  SugaredLogger 的结构化日志 API 是松散类型的,并接受可变数量的键值对。

  默认情况下,logger是无缓冲的。但是,由于 zap 的 API调用允许缓冲,因此进程退出之前调用 Sync 是一个好习惯。

  使用logger。它比 SugaredLogger 更快,分配的资源也少得多,但它只支持强类型、结构化的日志记录。

// sugarLogger
sugar := zap.NewExample().Sugar()
defer sugar.Sync()
sugar.Infow("failed to fetch URL",
  "url", "http://example.com",
  "attempt", 3,
  "backoff", time.Second,
)
sugar.Infof("failed to fetch URL: %s", "http://example.com")

//logger
logger := zap.NewExample()
defer logger.Sync()
logger.Info("failed to fetch URL",
  zap.String("url", "http://example.com"),
  zap.Int("attempt", 3),
  zap.Duration("backoff", time.Second),
)

十、 选项

10.1 定义

type Option interface {
    apply(*Logger)
}

10.2 相关函数

// 启用行号、函数名、文件名打印
func AddCaller() Option
// 调用函数的深度
func AddCallerSkip(skip int) Option
// 添加堆栈追踪
func AddStacktrace(lvl zapcore.LevelEnabler) Option
// 将记录器置于开发模式
func Development() Option
// 设置错误级别日志输出
func ErrorOutput(w zapcore.WriteSyncer) Option
// 向记录器增加字段
func Fields(fs ...Field) Option
// 向记录器添加钩子函数
func Hooks(hooks ...func(zapcore.Entry) error) Option
// 增加记录器的级别。如果传入的级别试图降低记录器的级别,则无效。
func IncreaseLevel(lvl zapcore.LevelEnabler) Option
// 设置对致命日志采取的操作。
func OnFatal(action zapcore.CheckWriteAction) Option
// 启用/禁用日志打印文件名、函数名、行号
func WithCaller(enabled bool) Option
// 指定记录器用于确定记录条目的当前时间的时钟。默认为带有 time.Now 的系统时钟。
func WithClock(clock zapcore.Clock) Option
// 包装zapcore.Core
func WrapCore(f func(zapcore.Core) zapcore.Core) Option

10.3 Wrap Example

package main

import (
    "go.uber.org/zap"
    "go.uber.org/zap/zapcore"
)

func main() {
   
    // wrap1()
    //{"level":"info","msg":"working"}
    //{"level":"info","msg":"original logger still works"}
    wrap2()
    //{"level":"info","msg":"single"}
    //{"level":"info","msg":"doubled"}
    //{"level":"info","msg":"doubled"}
}

func wrap1() {
   
    nop := zap.WrapCore(func(zapcore.Core) zapcore.Core {
   
        return zapcore.NewNopCore()
    })

    logger := zap.NewExample()
    defer logger.Sync()

    logger.Info("working")
    logger.WithOptions(nop).Info("no-op")
    logger.Info("original logger still works")
}

func wrap2() {
   
    doubled := zap.WrapCore(func(c zapcore.Core) zapcore.Core {
   
        return zapcore.NewTee(c, c)
    })

    logger := zap.NewExample()
    defer logger.Sync()

    logger.Info("single")
    logger.WithOptions(doubled).Info("doubled")
}

十一、Sink

type Sink interface {
    zapcore.WriteSyncer
    io.Closer
}

十二、SamplingConfig

type SamplingConfig struct {
    Initial    int                                           `json:"initial" yaml:"initial"`
    Thereafter int                                           `json:"thereafter" yaml:"thereafter"`
    Hook       func(zapcore.Entry, zapcore.SamplingDecision) `json:"-" yaml:"-"`
}

参考资料

  Go高性能日志库zap详细分析:https://blog.csdn.net/skh2015java/article/details/81771808

  zap Api:https://pkg.go.dev/go.uber.org/zap#section-sourcefiles

  golang 高性能日志库zap架构分析: https://blog.csdn.net/cyq6239075/article/details/105646717

日志转轮

// lumberjack.Logger is already safe for concurrent use, so we don't need to
// lock it.
w := zapcore.AddSync(&lumberjack.Logger{
  Filename:   "/var/log/myapp/foo.log",
  MaxSize:    500, // megabytes
  MaxBackups: 3,
  MaxAge:     28, // days
})
core := zapcore.NewCore(
  zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
  w,
  zap.InfoLevel,
)
logger := zap.New(core)

实现功能

  1. 日志转轮功能:
  • 可以根据时间生成日志
  • 并且会检测日志大小转轮
  1. 日志输出:
  • 输出日志级别
  • 时间:精确到毫秒:实现timeEncoder、配置到zapcore.EncoderConfig.EncodeTime
func timeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
    enc.AppendString(t.Format("2006-01-02 15:04:05.000"))
}

// 配置zapcore.EncorderConfig
encoderConfig := zapcore.EncoderConfig{
        MessageKey:     "message",
        LevelKey:       "level",
        TimeKey:        "timestamp",
        NameKey:        "logger",
        CallerKey:      "caller",
        StacktraceKey:  "stacktrace",
        LineEnding:     zapcore.DefaultLineEnding,
        EncodeLevel:    encodeLevel,
        EncodeTime:     timeEncoder,
        EncodeDuration: milliSecondsDurationEncoder,
        EncodeCaller:   zapcore.ShortCallerEncoder,
}
  • 输出协程信息
  • 输出模块信息[文件名 行号 函数信息]
  • 消息
INFO 2021-07-22 10:33:22:111 [14] [FtpFilter.go 100 filter] msg

模块实现

types.go

  zap的常量别名:日志级别

  zap类型函数别名

options.go

  一些配置的注入

type Options struct {
    // 输出路径 eg:[]string{"stdout"},{“/kds/log/2021.log”}
    OutputPaths       []string `json:"output-paths"       mapstructure:"output-paths"`    
    // 错误及以上级别的输出路径
    ErrorOutputPaths  []string `json:"error-output-paths" mapstructure:"error-output-paths"`
    // 最小输出的日志级别
    Level             string   `json:"level"              mapstructure:"level"`
    // 日志编码格式 eg."json","console"
    Format            string   `json:"format"             mapstructure:"format"`
    //
    DisableCaller     bool     `json:"disable-caller"     mapstructure:"disable-caller"`
    DisableStacktrace bool     `json:"disable-stacktrace" mapstructure:"disable-stacktrace"`
    EnableColor       bool     `json:"enable-color"       mapstructure:"enable-color"`
    Development       bool     `json:"development"        mapstructure:"development"`
    Name              string   `json:"name"               mapstructure:"name"`
}

日志转轮

  添加自定义的writer

func AddSync(w io.Writer) WriteSyncer {
    switch w := w.(type) {
    case WriteSyncer:
        return w
    default:
        return writerWrapper{w}
    }
}

参考源码

  https://github.com/moul/zapfilter

  https://github.com/tchap/zapext

  https://github.com/fgrosse/zaptest

  https://github.com/moul/zapgorm

  https://github.com/blendle/zapdriver

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
17天前
|
Shell Go API
Go语言grequests库并发请求的实战案例
Go语言grequests库并发请求的实战案例
|
2月前
|
前端开发 C语言 开发者
领导被我的花式console.log吸引了!直接写入公司公共库!
【8月更文挑战第23天】领导被我的花式console.log吸引了!直接写入公司公共库!
37 2
领导被我的花式console.log吸引了!直接写入公司公共库!
|
6天前
|
存储 运维 监控
超级好用的C++实用库之日志类
超级好用的C++实用库之日志类
13 0
|
2月前
|
Linux API
在Linux中,程序产生了库日志虽然删除了,但磁盘空间未更新是什么原因?
在Linux中,程序产生了库日志虽然删除了,但磁盘空间未更新是什么原因?
|
2月前
|
存储 JSON 前端开发
一文搞懂 Go 1.21 的日志标准库 - slog
一文搞懂 Go 1.21 的日志标准库 - slog
58 2
|
2月前
|
监控 Go 开发者
掌握Go语言中的日志管理
【8月更文挑战第31天】
17 0
|
2月前
|
XML Go 数据库
|
2月前
|
Kubernetes Ubuntu Windows
【Azure K8S | AKS】分享从AKS集群的Node中查看日志的方法(/var/log)
【Azure K8S | AKS】分享从AKS集群的Node中查看日志的方法(/var/log)
|
24天前
|
Java
日志框架log4j打印异常堆栈信息携带traceId,方便接口异常排查
日常项目运行日志,异常栈打印是不带traceId,导致排查问题查找异常栈很麻烦。
|
1月前
|
存储 监控 数据可视化
SLS 虽然不是直接使用 OSS 作为底层存储,但它凭借自身独特的存储架构和功能,为用户提供了一种专业、高效的日志服务解决方案。
【9月更文挑战第2天】SLS 虽然不是直接使用 OSS 作为底层存储,但它凭借自身独特的存储架构和功能,为用户提供了一种专业、高效的日志服务解决方案。
66 9
下一篇
无影云桌面