go中使用三方库zap管理日志,总结全面、通俗易懂!

如果你想学到更多实用知识。

可以关注我的公众号:【前端驿站Lite】,一个不止分享前端的地方 ᕦ( •̀∀•́)ᕤ

介绍

zap是一款go开源的日志库。

特点

  • 支持七种日志级别:DebugInfoWarnErrorDPanicPanicFatal,其中 DPanic 是指在开发环境下(development)记录日志后会进行 panic
  • 能打印最基本的信息,例如调用的文件、函数名称、错误代码行号、记录时间等
  • 能够持久化存储日志,例如将日志记录在文件中,并且能够根据需要来切割日志

安装

shell 复制代码
go get -u go.uber.org/zap

创建实例

使用zap前,需要做一些配置,最终就是为了得到日志记录器,然后调用该记录器的方法来记录日志。

zap提供了两种类型的日志记录器SugaredLoggerLogger

LoggerSugaredLogger性能更高,因为Logger要直接指定好数据类型没有反射,所以性能更高。但不如SugaredLogger灵活 ,SugaredLogger可以直接传入任意类型的数据。

除了上面两种开箱即用的日志记录器,zap还支持使用zap.New(...)自定义日志记录器。

注: 默认情况下日志都会打印到应用程序的终端界面

Logger

可以通过zap.NewProduction()zap.NewDevelopment() 或者 zap.Example()(不常用,不做过多介绍) 来创建一个Logger

这三个方法的区别在于他将记录的信息不同,输出格式不同。

注意:

  • 输出日志传入的参数只能是string类型
  • zap.NewProduction()它只能记录 InfoLevel 及以上的日志级别
go 复制代码
var log *zap.Logger    //定义一个全局的Logger

log = zap.NewExample()
log, _ = zap.NewDevelopment()
log, _ = zap.NewProduction()

log.Debug("This is a DEBUG message")
log.Info("This is an INFO message")
log.Fatal("This ia a fatal message")

//zap.NewExample() 输出
{"level":"debug","msg":"This is a DEBUG message"}
{"level":"info","msg":"This is an INFO message"}

//zap.NewDevelopment() 输出
2022-11-19T10:07:36.345+0800    DEBUG   li/main.go:12   This is a DEBUG message
2022-11-19T10:07:36.394+0800    INFO    li/main.go:13   This is an INFO message

//zap.NewProduction() 输出
{"level":"info","ts":1668823728.0574305,"caller":"li/main.go:13","msg":"This is an INFO message"}
{"level":"fatal","ts":1668823905.9411304,"caller":"li/main.go:14","msg":"This ia a fatal message","stacktrace":"main.main\n\tD:/goproject/src/hub-gin server/li/main.go:14\nruntime.main\n\tD:/go/src/runtime/proc.go:250"}

日志追加其他字段

记录日志的 logger.Xxx 方法,比如logger.Info签名如下:

go 复制代码
func (log *Logger) Info(msg string, fields ...Field)

其中 fieldszapcore.Field 类型,用来存储 key-value,并记录 value 类型,不管是 zap.String 还是 zap.Int 底层都是 zapcore.Field 类型来记录的。zap 为每一种 Go 的内置类型都定义了对应的 zap.Xxx 方法,甚至还实现 zap.Any() 来支持 interface{}

所以如果想在日志中追加其他信息,往后追加参数就可以。

go 复制代码
package main

import (
	"time"

	"go.uber.org/zap"
)

func main() {
	// 生产环境
	{
		logger, _ := zap.NewProduction()
		defer logger.Sync() // 刷新 buffer,保证日志最终会被输出

		url := "https://jianghushinian.cn/"
		logger.Info("production failed to fetch URL",
			zap.String("url", url), // 因为没有使用 interface{} 和反射机制,所以需要指定具体类型
			zap.Int("attempt", 3),
			zap.Duration("backoff", time.Second),
		)
	}

	// 开发环境
	{
		logger, _ := zap.NewDevelopment()
		defer logger.Sync()

		url := "https://jianghushinian.cn/"
		logger.Debug("development failed to fetch URL",
			zap.String("url", url),
			zap.Int("attempt", 3),
			zap.Duration("backoff", time.Second),
		)
	}
}
shell 复制代码
# zap.NewProduction() 输出信息
{"level":"info","ts":1679212318.10218,"caller":"zap/main.go:16","msg":"production failed to fetch URL","url":"https://jianghushinian.cn/","attempt":3,"backoff":1}

# zap.NewDevelopment() 输出信息
2023-03-19T15:51:58.102+0800    DEBUG   zap/main.go:29        development failed to fetch URL {"url": "https://jianghushinian.cn/", "attempt": 3, "backoff": "1s"}

日志后面追加的 key-value 会被转换成 JSON

SugaredLogger

想让代码写起来更爽一些,zap 为我们提供了SugaredLogger

通过 logger.Sugar() 方法可以将一个 Logger 对象转换成一个 SugaredLogger 对象。

go 复制代码
package main

import (
	"time"

	"go.uber.org/zap"
)

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

	url := "https://jianghushinian.cn/"
	sugar := logger.Sugar()
	sugar.Infow("production failed to fetch URL",
		"url", url,
		"attempt", 3,
		"backoff", time.Second,
	)
	sugar.Info("Info")
	sugar.Infof("Infof: %s", url)
	sugar.Infoln("Infoln")
}
shell 复制代码
{"level":"info","ts":1679217743.5967638,"caller":"zap/sugar.go:15","msg":"production failed to fetch URL","url":"https://jianghushinian.cn/","attempt":3,"backoff":1}
{"level":"info","ts":1679217743.5969589,"caller":"zap/sugar.go:20","msg":"Info"}
{"level":"info","ts":1679217743.5969741,"caller":"zap/sugar.go:21","msg":"Infof: https://jianghushinian.cn/"}
{"level":"info","ts":1679217743.5969841,"caller":"zap/sugar.go:22","msg":"Infoln"}

SugaredLogger 提供了更人性化的接口,日志中追加 key-value 时不在需要 zap.String("url", url) 这种显式指明类型的写法,只需要保证 keystring 类型,value 则可以为任意类型,能够减少我们编写的代码量。

此外,为了满足不同需求,SugaredLogger 提供了四种方式输出日志:sugar.Xxxsugar.Xxxwsugar.Xxxfsugar.Xxxln

zap.Config

NewExampleNewProduction 使用的是json格式输出,NewDevelopment使用Text+空格行的形式输出。

导致以上这些差异的原因是配置不同,我们来看下 zap.NewProductionzap.NewDevelopment 的代码实现:

NewProduction

go 复制代码
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"},
	}
}

NewDevelopment

go 复制代码
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"},
    }
}

可以看到,两者在实现思路上是一样的,都是先创建一个配置对象 zap.Config,然后再调用配置对象的 Build 方法来构建 Logger

zap.Config它是一个结构体,定义如下:

go 复制代码
type Config struct {
    // 日志级别。会输出该级别及以上级别的日志。 动态改变日志级别,在运行时你可以安全改变日志级别
    Level AtomicLevel `json:"level" yaml:"level"`
    // 将日志记录器设置为开发模式,在 WarnLevel 及以上级别日志会包含堆栈跟踪信息
    Development bool `json:"development" yaml:"development"`
    // 禁用调用信息,值为 true 时,日志中将不再显示记录日志时所在的函数调用文件名和行号。
    DisableCaller bool `json:"disableCaller" yaml:"disableCaller"`
    // 是否完全禁止自动堆栈跟踪。默认情况下,在 development 中,warnlevel及以上日志级别会自动捕获堆栈跟踪信息
    // 在 production 中,ErrorLevel 及以上也会自动捕获堆栈信息
    DisableStacktrace bool `json:"disableStacktrace" yaml:"disableStacktrace"`
    // 采样策略配置,单位为每秒,作用是限制日志在每秒内的输出数量,以此来防止全局的 CPU 和 I/O 负载过高。
    Sampling *SamplingConfig `json:"sampling" yaml:"sampling"`
    // 设置日志编码。可以设置为 console 和 json。也可以通过 RegisterEncoder 设置第三方编码格式
    Encoding string `json:"encoding" yaml:"encoding"`
    // 编码配置,决定了日志字段格式。
    EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"`
    // 配置日志输出位置,URLs 或文件路径,可配置多个。
    OutputPaths []string `json:"outputPaths" yaml:"outputPaths"`
    // zap 包内部出现错误的日志输出位置,URLs 或文件路径,可配置多个,默认 os.Stderr。
    ErrorOutputPaths []string `json:"errorOutputPaths" yaml:"errorOutputPaths"`
    // 初始化字段配置,该配置的字段会以结构化的形式打印在每条日志输出中。
    InitialFields map[string]interface{} `json:"initialFields" yaml:"initialFields"`
}

Sync

zap 会将日志先写入到内存中的缓冲区,然后再定期将缓冲区中的日志刷入到磁盘中,这样做的好处是能够提高日志的写入效率。

但是,如果程序异常退出,那么缓冲区中的日志就会丢失,为了避免这种情况,我们需要在程序退出前调用 日志记录器.Sync() 方法,将缓冲区中的日志刷入到磁盘中。

go 复制代码
var logger *zap.Logger

func main() {
    logger, _ = zap.NewProduction()
    defer logger.Sync()
    logger.Info("Info msg")
}

zap.New()

通过查看 zap.NewProduction()zap.NewDevelopment() 两个构造函数源码,我们知道可以使用 zap.Config 对象的 Build 方法创建 Logger 对象。那么我们很容易能够想到,如果要定制 Logger,只需要创建一个定制的 zap.Config 即可。

实现定制化,需要使用zap.New(...) 方法来手动传递所有配置,函数签名:

go 复制代码
// New constructs a new Logger from the provided zapcore.Core and Options. If
// the passed zapcore.Core is nil, it falls back to using a no-op
// implementation.
//
// This is the most flexible way to construct a Logger, but also the most
// verbose. For typical use cases, the highly-opinionated presets
// (NewProduction, NewDevelopment, and NewExample) or the Config struct are
// more convenient.
//
// For sample code, see the package-level AdvancedConfiguration example.
func New(core zapcore.Core, options ...Option) *Logger {
    if core == nil {
        return NewNop()
    }
    log := &Logger{
        core:        core,
        errorOutput: zapcore.Lock(os.Stderr),
        addStack:    zapcore.FatalLevel + 1,
        clock:       zapcore.DefaultClock,
    }
    return log.WithOptions(options...)
}

// Core is a minimal, fast logger interface. It's designed for library authors
// to wrap in a more user-friendly API.
type Core interface {
    LevelEnabler
    
    // With adds structured context to the Core.
    With([]Field) Core
    // Check determines whether the supplied Entry should be logged (using the
    // embedded LevelEnabler and possibly some extra logic). If the entry
    // should be logged, the Core adds itself to the CheckedEntry and returns
    // the result.
    //
    // Callers must use Check before calling Write.
    Check(Entry, *CheckedEntry) *CheckedEntry
    // Write serializes the Entry and any Fields supplied at the log site and
    // writes them to their destination.
    //
    // If called, Write should always log the Entry and Fields; it should not
    // replicate the logic of Check.
    Write(Entry, []Field) error
    // Sync flushes buffered logs (if any).
    Sync() error
}

可以看出zap.New(...) 方法接收两个参数,第一个参数是 zapcore.Core,第二个参数是 OptionOption 是一个函数类型,用来对 Logger 进行定制。

通过源码我们可以看到New方法需要一个zapcore.Core参数,而Core是接口类型,那我们继续看如何实现Core这个接口。

zapcore.Core

查看到源码中,如下:NewCore可以返回一个ioCore,然后再源码中ioCore实现了Core提供的几个接口

go 复制代码
// 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,
	}
}

type ioCore struct {
	LevelEnabler
	enc Encoder
	out WriteSyncer
}

我们可以看得到zapcore.Core需要三个配置------EncoderWriteSyncerLevelEnabler

1. Encoder

Encoder 是编码器,以什么样的格式写入日志。

目前,zap只支持两种编码器------JSON EncoderConsole Encoder

go 复制代码
// 调用zap中这两个方法来创建编码器
func NewJSONEncoder(cfg EncoderConfig) Encoder
func NewConsoleEncoder(cfg EncoderConfig) Encoder

比如,我们想要以JSON形式返回 ,需要我们传入一个EncoderConfig,这里我们使用zap库中自动配置好的zap.NewProductionEncoderConfig()

go 复制代码
func getEncoder() zapcore.Encoder {
    return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
}

关于Encoder内置的配置有两种NewProductionEncoderConfig()NewDevelopmentEncoderConfig()

go 复制代码
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,
	}
}

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,
	}
}

对比来看,两者有很多不同的配置,比如生产环境下 EncodeTime 值为 zapcore.EpochTimeEncoder,开发环境下 EncodeTime 值为 zapcore.ISO8601TimeEncoder。这就是生产环境日志输出的时间格式为 Unix epoch 而开发环境日志输出的时间格式为 ISO8601 的原因。

zapcore.EncoderConfig 其他几个常用的配置项说明如下:

  • MessageKey: 日志信息的键名,默认 msg。
  • LevelKey: 日志级别的键名,默认 level。
  • TimeKey: 日志时间的键名。
  • EncodeLevel: 日志级别的格式,默认为小写,如 info。

2. WriteSyncer

指定日志输出路径(文件 或 控制台 或者双向输出)。但是打开的类型不一样,文件打开的是io.writer类型,而我们需要的是WriteSyncer,所以我们使用zapcore.AddSync()函数进行转换:

go 复制代码
// core 三个参数之  日志输出路径
func getWriterSyncer() zapcore.WriteSyncer {
file, _ := os.Create("./server/zaplog_test/log.log")
    return zapcore.AddSync(file)
}

3. LevelEnabler

哪种级别的日志将被写入(将写入该级别及以上的日志)。

通过配置上面的3个参数,我们就可以实现将日志输出到我们指定的文件中:

go 复制代码
package main

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

var logger *zap.Logger
var SugaredLogger *zap.SugaredLogger

func InitLogger() {
	encoder := getEncoder()
	writerSyncer := getWriterSyncer()
	core := zapcore.NewCore(encoder, writerSyncer, zap.DebugLevel)
	logger = zap.New(core)
	SugaredLogger = logger.Sugar()
}

func getEncoder() zapcore.Encoder {
	return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
}

func getWriterSyncer() zapcore.WriteSyncer {
	file, _ := os.Create("./server/zaplog_test/log.log")   //指定日志输出文件位置
	return zapcore.AddSync(file)
}

logger.WithOptions

我们还通过 logger.WithOptions()Logger 对象增加了一个选项 zap.AddCallerSkip(100),这个选项的作用是指定在通过调用栈获得行号时跳过的调用深度,因为我们的函数调用栈并不是 100 层,所以会触发 zap 内部错误,zap 会将错误日志输出到 ErrorOutputPaths 配置指定的位置中,即 error.log

go 复制代码
// 方式一:日志编辑器调用WithOptions方法,添加选项
logger = logger.WithOptions(zap.AddCallerSkip(100))

// 方式二:创建日志编辑器时,添加选项
logger = zap.New(core, zap.AddCallerSkip(100))

logger.WithOptions() 支持的选项如下:

  • WrapCore(f func(zapcore.Core) zapcore.Core): 使用一个新的 zapcore.Core 替换掉 Logger 内部原有的的 zapcore.Core 属性。
  • Hooks(hooks ...func(zapcore.Entry) error): 注册钩子函数,用来在日志打印时同时调用注册的钩子函数。
  • Fields(fs ...Field): 添加公共字段。
  • ErrorOutput(w zapcore.WriteSyncer): 指定日志组件内部出现异常时的输出位置。
  • Development(): 将日志记录器设为开发模式,这将使 DPanic 级别日志记录错误后执行 panic()。
  • AddCaller(): 与 WithCaller(true) 等价。
  • WithCaller(enabled bool): 指定是否在日志输出内容中增加调用信息,即文件名和行号。
  • AddCallerSkip(skip int): 指定在通过调用栈获取文件名和行号时跳过的调用深度。
  • AddStacktrace(lvl zapcore.LevelEnabler): 用来指定某个日志级别及以上级别输出调用堆栈。
  • IncreaseLevel(lvl zapcore.LevelEnabler): 提高日志级别,如果传入的 lvl 比现有级别低,则不会改变日志级别。
  • WithFatalHook(hook zapcore.CheckWriteHook): 当出现 Fatal 级别日志时调用的钩子函数。
  • WithClock(clock zapcore.Clock): 指定日志记录器用来确定当前时间的 zapcore.Clock 对象,默认为 time.Now 的系统时钟。

其他使用场景

1.更改时间编码并添加调用者详细信息

鉴于我们对配置所做的更改,有下面两个问题:

  • 时间是以非人类可读的方式展示,例如 1.572161051846623e+09
  • 调用方函数的详细信息没有显示在日志中

我们要做的第一件事是覆盖默认的ProductionConfig(),并进行以下更改:

  • 修改时间编码器
  • 在日志文件中使用大写字母记录日志级别
go 复制代码
func getEncoder() zapcore.Encoder {
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
	return zapcore.NewConsoleEncoder(encoderConfig)
}

接下来,我们将修改 zap logger 代码,添加将调用函数信息记录到日志中的功能。为此,我们将在zap.New(..)函数中添加一个Option

go 复制代码
logger := zap.New(core, zap.AddCaller())

当使用这些修改过的 logger 配置调用上述部分的main()函数时,以下输出将打印在文件------test.log中。

text 复制代码
2019-10-27T15:33:29.855+0800	DEBUG	logic/temp2.go:47	Trying to hit GET request for www.baidu.com
2019-10-27T15:33:29.855+0800	ERROR	logic/temp2.go:50	Error fetching URL www.sogo.com : Error = Get www.baidu.com: unsupported protocol scheme ""
2019-10-27T15:33:29.856+0800	DEBUG	logic/temp2.go:47	Trying to hit GET request for http://www.baidu.com
2019-10-27T15:33:30.125+0800	INFO	logic/temp2.go:52	Success! statusCode = 200 OK for URL http://www.baidu.com

2.将日志同时输出到终端和文件

如果需要同时将日志输出到控制台终端和文件中,只需要改造一下zapcore.NewCore即可,本质其实就是修改一下 WriteSyncer ,使用zapcore.NewMultiWriteSyncer来设置多个输出对象

go 复制代码
func getWriterSyncer() zapcore.WriteSyncer {
    file, _ := os.Create("./server/zaplog_test/log.log")   //指定日志输出文件位置
    return zapcore.AddSync(file)
}

func InitLogger() {
	encoder := getEncoder()
	writerSyncer := getWriterSyncer()
        
	//这里我们使用zapcore.NewMultiWriteSyncer()实现同时输出到多个对象中
	// os.Stdout 为控制台输出
	core := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(writerSyncer, zapcore.AddSync(os.Stdout)), zap.DebugLevel)
        
	logger = zap.New(core, zap.AddCaller()) //zap.AddCaller() 显示文件名 和 行号
	SugaredLogger = logger.Sugar()
}

3.实现日志文件切割

如果将所有的日志记录都存储到同一个文件中,这显然是不可取的,不仅会导致内存过大,也不利于我们翻阅日志排查错误,因此实现日志文件的合理切割是十分必要的。但是很可惜,zap并不支持切割日志文件,我们需要通过第三方库Lumberjack实现切割操作

go 复制代码
import "gopkg.in/natefinch/lumberjack.v2"

要在zap中加入Lumberjack支持,我们需要修改WriteSyncer代码。我们将按照下面的代码修改getLogWriter()函数:

go 复制代码
// core 三个参数之  日志输出路径
func getWriterSyncer() zapcore.WriteSyncer {
	//file, _ := os.Create("./server/zaplog_test/log.log")
	//return zapcore.AddSync(file)

	//引入第三方库 Lumberjack 加入日志切割功能
	lumberWriteSyncer := &lumberjack.Logger{
		Filename:   "./server/zaplog_test/log.log",
		MaxSize:    10, // megabytes
		MaxBackups: 100,
		MaxAge:     28,    // days
		Compress:   false, //Compress确定是否应该使用gzip压缩已旋转的日志文件。默认值是不执行压缩。
	}
	return zapcore.AddSync(lumberWriteSyncer)
}
// core 三个参数之  日志输出路径
func getWriterSyncer() zapcore.WriteSyncer {
	//file, _ := os.Create("./server/zaplog_test/log.log")
	//return zapcore.AddSync(file)

	//引入第三方库 Lumberjack 加入日志切割功能
	lumberWriteSyncer := &lumberjack.Logger{
		Filename:   "./server/zaplog_test/log.log",
		MaxSize:    10, // megabytes
		MaxBackups: 100,
		MaxAge:     28,    // days
		Compress:   false, //Compress确定是否应该使用gzip压缩已旋转的日志文件。默认值是不执行压缩。
	}
	return zapcore.AddSync(lumberWriteSyncer)
}

这里我们设置的是 日志文件每 10MB 会切割并且在当前目录下最多保存 5 个日志文件,并且会将旧文档保存30天

4.按照日志级别归档写入文件

为了管理人员的查询方便,一般我们需要将低于error级别的放到info.logerror及以上严重级别日志存放到error.log文件中,我们只需要改造一下zapcore.NewCore方法的第3个参数,然后将文件WriteSyncer拆成infoerror两个即可,也就是将上文的getWriterSyncer()拆分开:

go 复制代码
/*
// core 三个参数之  日志输出路径
func getWriterSyncer() zapcore.WriteSyncer {
	//file, _ := os.Create("./server/zaplog_test/log.log")
	//return zapcore.AddSync(file)

	//引入第三方库 Lumberjack 加入日志切割功能
	lumberWriteSyncer := &lumberjack.Logger{
		Filename:   "./server/zaplog_test/log.log",
		MaxSize:    10, // megabytes
		MaxBackups: 100,
		MaxAge:     28,    // days
		Compress:   false, //Compress确定是否应该使用gzip压缩已旋转的日志文件。默认值是不执行压缩。
	}
	return zapcore.AddSync(lumberWriteSyncer)
}
*/

// 记录error以下日志级别的文件
func getInfoWriterSyncer() zapcore.WriteSyncer {
	//file, _ := os.Create("./server/zaplog/log.log")
	// 或者将上面的NewMultiWriteSyncer放到这里来,进行返回
	//return zapcore.AddSync(file)

	//引入第三方库 Lumberjack 加入日志切割功能
	infoLumberIO := &lumberjack.Logger{
		Filename:   "./server/zaplog/info.log",
		MaxSize:    10, // megabytes
		MaxBackups: 100,
		MaxAge:     28,    // days
		Compress:   false, //Compress确定是否应该使用gzip压缩已旋转的日志文件。默认值是不执行压缩。
	}
	return zapcore.AddSync(infoLumberIO)
}

//记录error及以上日志级别的文件
func getErrorWriterSyncer() zapcore.WriteSyncer {
	//引入第三方库 Lumberjack 加入日志切割功能
	lumberWriteSyncer := &lumberjack.Logger{
		Filename:   "./server/zaplog/error.log",
		MaxSize:    10, // megabytes
		MaxBackups: 100,
		MaxAge:     28,    // days
		Compress:   false, //Compress确定是否应该使用gzip压缩已旋转的日志文件。默认值是不执行压缩。
	}
	return zapcore.AddSync(lumberWriteSyncer)
}

在使用时只需要对日志级别进行判断,从而将不同级别的文件写入到不同的日志文件中

go 复制代码
var logger *zap.Logger 
var SugaredLogger *zap.SugaredLogger

func InitLogger() {
	//获取编码器
	encoder := getEncoder()

	//对日志级别进行判断、分类
	highPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool { //error级别
		return lev >= zap.ErrorLevel
	})
	lowPriority := zap.LevelEnablerFunc(func(lev zapcore.Level) bool { //info和debug级别,debug级别是最低的
		return lev < zap.ErrorLevel && lev >= zap.DebugLevel
	})

	//info文件WriteSyncer
	infoFileWriteSyncer := getInfoWriterSyncer()
	//error文件WriteSyncer
	errorFileWriteSyncer := getErrorWriterSyncer()

	//生成core
	//multiWriteSyncer := zapcore.NewMultiWriteSyncer(writerSyncer, zapcore.AddSync(os.Stdout)) //AddSync将io.Writer转换成WriteSyncer的类型
	//同时输出到控制台 和 指定的日志文件中
	infoFileCore := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(infoFileWriteSyncer, zapcore.AddSync(os.Stdout)), lowPriority)
	errorFileCore := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(errorFileWriteSyncer, zapcore.AddSync(os.Stdout)), highPriority)

	//将infocore 和 errcore 加入core切片
	var coreArr []zapcore.Core
	coreArr = append(coreArr, infoFileCore)
	coreArr = append(coreArr, errorFileCore)

	//生成logger
	logger = zap.New(zapcore.NewTee(coreArr...), zap.AddCaller()) //zap.AddCaller() 显示文件名 和 行号
	SugaredLogger = logger.Sugar()
}

这样修改之后,infodebug级别的日志就存放到info.logerror级别的日志单独放到error.log文件中了

自定义配置

创建自定义的配置对象,除了在代码中指定配置参数,也可以将这些配置项写入到 JSON 文件中,然后通过 json.Unmarshal 的方式将配置绑定到 zap.Config,可以参考官方示例

go 复制代码
package main

import (
	"encoding/json"

	"go.uber.org/zap"
)

func main() {
	// For some users, the presets offered by the NewProduction, NewDevelopment,
	// and NewExample constructors won't be appropriate. For most of those
	// users, the bundled Config struct offers the right balance of flexibility
	// and convenience. (For more complex needs, see the AdvancedConfiguration
	// example.)
	//
	// See the documentation for Config and zapcore.EncoderConfig for all the
	// available options.
	rawJSON := []byte(`{
	  "level": "debug",
	  "encoding": "json",
	  "outputPaths": ["stdout", "/tmp/logs"],
	  "errorOutputPaths": ["stderr"],
	  "initialFields": {"foo": "bar"},
	  "encoderConfig": {
	    "messageKey": "message",
	    "levelKey": "level",
	    "levelEncoder": "lowercase"
	  }
	}`)

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

	logger.Info("logger construction succeeded")
}

// 输出
// {"level":"info","message":"logger construction succeeded","foo":"bar"}

完结撒花🎉🎉🎉,你又进步了一点点。

朋友,如果觉得本文章对你有帮助,可以留个赞👍再走嘛。

赠人玫瑰手留余香!先谢谢啦 ᕦ( •̀∀•́)ᕤ

相关推荐
2401_857610032 小时前
SpringBoot社团管理:安全与维护
spring boot·后端·安全
凌冰_2 小时前
IDEA2023 SpringBoot整合MyBatis(三)
spring boot·后端·mybatis
码农飞飞2 小时前
深入理解Rust的模式匹配
开发语言·后端·rust·模式匹配·解构·结构体和枚举
一个小坑货2 小时前
Rust 的简介
开发语言·后端·rust
monkey_meng3 小时前
【遵守孤儿规则的External trait pattern】
开发语言·后端·rust
Estar.Lee3 小时前
时间操作[计算时间差]免费API接口教程
android·网络·后端·网络协议·tcp/ip
新知图书4 小时前
Rust编程与项目实战-模块std::thread(之一)
开发语言·后端·rust
盛夏绽放4 小时前
Node.js 和 Socket.IO 实现实时通信
前端·后端·websocket·node.js
Ares-Wang4 小时前
Asp.net Core Hosted Service(托管服务) Timer (定时任务)
后端·asp.net
uzong5 小时前
7 年 Java 后端,面试过程踩过的坑,我就不藏着了
java·后端·面试