Go | zap

Go | zap

1. 简介

那些介绍、性能比较直接看参考中zap链接,这里只介绍该日志库用法,方便快速上手。

package main

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

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

	logger.Info("hello world",
		zap.String("name", "yimt"),
	)

	// 可以将结构化日志转为fmt包形式
	sugar := logger.Sugar()
	sugar.Infof("name %s", "yimt")
}

输出

{"level":"info","ts":1684460836.884597,"caller":"hello-go/main.go:11","msg":"hello world","name":"yimt"}
{"level":"info","ts":1684460836.88468,"caller":"hello-go/main.go:17","msg":"name yimt"}

2. NewDevelopment & NewProduction区别

接下来通过查看NewDevelopmentNewProduction实现,简单了解下zap的配置,为后续学习zapcore.Core打下基础。

2.1. 实现

NewDevelopment

func NewDevelopment(options ...Option) (*Logger, error) {
	return NewDevelopmentConfig().Build(options...)
}
func NewDevelopmentConfig() Config {
	return Config{
		Level:            NewAtomicLevelAt(DebugLevel),
		Development:      true,
		Encoding:         "console",
		EncoderConfig:    NewDevelopmentEncoderConfig(),
		OutputPaths:      []string{"stderr"},
		ErrorOutputPaths: []string{"stderr"},
	}
}
func NewDevelopmentEncoderConfig() zapcore.EncoderConfig {
	return zapcore.EncoderConfig{
		// Keys can be anything except the empty string.
		TimeKey:        "T",
		LevelKey:       "L",
		NameKey:        "N",
		CallerKey:      "C",
		FunctionKey:    zapcore.OmitKey,
		MessageKey:     "M",
		StacktraceKey:  "S",
		LineEnding:     zapcore.DefaultLineEnding,
		EncodeLevel:    zapcore.CapitalLevelEncoder,
		EncodeTime:     zapcore.ISO8601TimeEncoder,
		EncodeDuration: zapcore.StringDurationEncoder,
		EncodeCaller:   zapcore.ShortCallerEncoder,
	}
}

NewProduction

func NewProduction(options ...Option) (*Logger, error) {
	return NewProductionConfig().Build(options...)
}
func NewProductionConfig() Config {
	return Config{
		Level:       NewAtomicLevelAt(InfoLevel),
		Development: false,
		Sampling: &SamplingConfig{
			Initial:    100,
			Thereafter: 100,
		},
		Encoding:         "json",
		EncoderConfig:    NewProductionEncoderConfig(),
		OutputPaths:      []string{"stderr"},
		ErrorOutputPaths: []string{"stderr"},
	}
}
func NewProductionEncoderConfig() zapcore.EncoderConfig {
	return zapcore.EncoderConfig{
		TimeKey:        "ts",
		LevelKey:       "level",
		NameKey:        "logger",
		CallerKey:      "caller",
		FunctionKey:    zapcore.OmitKey,
		MessageKey:     "msg",
		StacktraceKey:  "stacktrace",
		LineEnding:     zapcore.DefaultLineEnding,
		EncodeLevel:    zapcore.LowercaseLevelEncoder,
		EncodeTime:     zapcore.EpochTimeEncoder,
		EncodeDuration: zapcore.SecondsDurationEncoder,
		EncodeCaller:   zapcore.ShortCallerEncoder,
	}
}

2.2. 执行结果

NewDevelopment

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

	logger.Info("hello world",
		zap.String("name", "yimt"),
	)
}

输出

2023-05-17T14:07:09.722+0800    INFO    hello-go/main.go:11     hello world     {"name": "yimt"}

NewProduction

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

	logger.Info("hello world",
		zap.String("name", "yimt"),
	)
}

输出

{"level":"info","ts":1684303547.31098,"caller":"hello-go/main.go:11","msg":"hello world","name":"yimt"}

3. zapcore.Core接口

  • zapcore.NewCore(enc Encoder, ws WriteSyncer, enab LevelEnabler)
  • zapcore.NewNopCore()
  • zapcore.NewIncreaseLevelCore(core Core, level LevelEnabler) (Core, error)
  • zapcore.NewTee()

3.1. zapcore.NewCore(enc Encoder, ws WriteSyncer, enab LevelEnabler)

3.1.1. Encoder

  • zapcore.NewJSONEncoder(cfg EncoderConfig):以JSON形式编码。
  • zapcore.NewConsoleEncoder(cfg EncoderConfig):以console形式编码。

zapcore.EncoderConfig

主要用于配置日志编码,包括时间格式,日志键名,每条日志是否换行等。

  • zap.NewDevelopmentEncoderConfig()
  • zap.NewProductionEncoderConfig()
  • zapcore.EncoderConfig{}:可以自定义配置。

控制日志编码格式,正常控制台显示console形式编码,日志文件记录JSON形式编码。

3.1.2. zapcore.WriteSyncer

  • zapcore.AddSync(f):指定日志输出位置。

控制日志输出位置,主要有控制台和日志文件。

3.1.3. zapcore.LevelEnabler

  • zap.NewAtomicLevel():可以运行过程中线程安全的调节日志等级。
  • zap.LevelEnablerFunc:以回调形式控制日志等级。

用于控制日志等级。

3.1.4. 演示

package main

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

func main() {
	core := zapcore.NewCore(zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), zapcore.AddSync(os.Stdout), zap.NewAtomicLevel())
	logger := zap.New(core)
	defer logger.Sync()

	logger.Info("hello world")
}

控制台输出

{"level":"info","ts":1684459049.47701,"msg":"hello world"}

3.2. zapcore.NewNopCore()

package main

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

func main() {
	logger := zap.New(zapcore.NewNopCore())
	defer logger.Sync()

	logger.Info("hello world")
}

执行完什么都不会发生,可以直接看NewNopCore()内实现,上面Core所有功能都不处理。

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 }

3.3. zapcore.NewIncreaseLevelCore(core Core, level LevelEnabler) (Core, error)

实现

func NewIncreaseLevelCore(core Core, level LevelEnabler) (Core, error) {
	for l := _maxLevel; l >= _minLevel; l-- {
		if !core.Enabled(l) && level.Enabled(l) {
			return nil, fmt.Errorf("invalid increase level, as level %q is allowed by increased level, but not by existing core", l)
		}
	}

	return &levelFilterCore{core, level}, nil
}

直接在已有一个Core下降1个日志等级,以显示更多日志。

3.4. zapcore.NewTee()

上面示例都有一个问题,日志只能输出到一个Core。在有的时候我们需要日志以console形式输出到控制台,以json形式输出到日志文件时就用到了NewTee,可以将日志一次输入到多个Core处理。

package main

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

func main() {
	f, err := os.Create("log.txt")
	if err != nil {
		panic(err)
	}
	// 日志已json形式输出文件
	outputFileCore := zapcore.NewCore(zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), zapcore.AddSync(f), zap.NewAtomicLevel())
	// 日志已console形式输出到控制台
	outputConsoleCore := zapcore.NewCore(zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()), zapcore.AddSync(os.Stdout), zap.NewAtomicLevel())
	tee := zapcore.NewTee(outputFileCore, outputConsoleCore)
	logger := zap.New(tee)
	defer logger.Sync()

	logger.Info("hello world")
}

log.txt

{"level":"info","ts":1684459649.253505,"msg":"hello world"}

控制台

2023-05-19T09:27:29.253+0800    INFO    hello world

4. 输出日志文件问题

上面示例虽然可以输出日志文件,但是还存在以下问题。

  1. 单个日志文件大小无法限制,单个日志文件过大最直观的麻烦是我们用文件编辑器打开经常未响应。
  2. 日志文件不会自己删除,在生产服务器可能会存在大量日志文件,占用硬盘空间。应该有不少人在MySQL的binlog日志文件上踩过坑吧。
  3. 旧日志压缩存储,节约硬盘空间。

下面就介绍lumberjack库,帮助我们自己管理日志文件,直接看下面示例就可以了。

package main

import (
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"gopkg.in/natefinch/lumberjack.v2"
)

func main() {
	writeSyncer := zapcore.AddSync(&lumberjack.Logger{
		Filename:   "log.txt",
		MaxSize:    500, // megabytes
		MaxBackups: 3,
		MaxAge:     28,   //days
		Compress:   true, // disabled by default
	})
	core := zapcore.NewCore(zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), writeSyncer, zap.NewAtomicLevel())
	logger := zap.New(core)
	defer logger.Sync()

	logger.Info("hello world")
}

5. 最终示例

功能

  1. 同时输出控制台和日志文件
  2. 日志文件达到限制大小自动创建新日志文件。
  3. 自己删除过期日志。
  4. 旧日志文件自动压缩。
package main

import (
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"gopkg.in/natefinch/lumberjack.v2"
	"os"
)

func main() {
	writeSyncer := zapcore.AddSync(&lumberjack.Logger{
		Filename:   "log.txt",
		MaxSize:    500, // megabytes
		MaxBackups: 3,
		MaxAge:     28,   //days
		Compress:   true, // disabled by default
	})
	// 日志已json形式输出文件
	outputFileCore := zapcore.NewCore(zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), writeSyncer, zap.NewAtomicLevel())
	// 日志已console形式输出到控制台
	outputConsoleCore := zapcore.NewCore(zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()), zapcore.AddSync(os.Stdout), zap.NewAtomicLevel())
	tee := zapcore.NewTee(outputFileCore, outputConsoleCore)
	logger := zap.New(tee)
	defer logger.Sync()

	logger.Info("hello world")
}

6. 参考

  • zap
  • lumberjack

你可能感兴趣的:(Go,golang,zap,go.uber.org/zap,日志)