5

golang常用库包:log日志记录-uber的Go日志库zap使用详解 - 九卷

 1 year ago
source link: https://www.cnblogs.com/jiujuan/p/17304844.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Go 日志记录库:uber-go 的日志操作库 zap 使用

一、简介#

zapuber 开源的一个高性能,结构化,分级记录的日志记录包。

go1.20.2

zap v1.24.0

zap的特性#

  • 高性能:zap 对日志输出进行了多项优化以提高它的性能

  • 日志分级:有 Debug,Info,Warn,Error,DPanic,Panic,Fatal 等

  • 日志记录结构化:日志内容记录是结构化的,比如 json 格式输出

  • 自定义格式:用户可以自定义输出的日志格式

  • 自定义公共字段:用户可以自定义公共字段,大家输出的日志内容就共同拥有了这些字段

  • 调试:可以打印文件名、函数名、行号、日志时间等,便于调试程序

  • 自定义调用栈级别:可以根据日志级别输出它的调用栈信息

  • Namespace:日志命名空间。定义命名空间后,所有日志内容就在这个命名空间下。命名空间相当于一个文件夹

  • 支持 hook 操作

高性能介绍#

与其它日志库对比#

github官网的对比图,下面的对比图来自:https://github.com/uber-go/zap#performance

Log a message and 10 fields:

Package Time Time % to zap Objects Allocated
⚡ zap 2900 ns/op +0% 5 allocs/op
⚡ zap (sugared) 3475 ns/op +20% 10 allocs/op
zerolog 10639 ns/op +267% 32 allocs/op
go-kit 14434 ns/op +398% 59 allocs/op
logrus 17104 ns/op +490% 81 allocs/op
apex/log 32424 ns/op +1018% 66 allocs/op
log15 33579 ns/op +1058% 76 allocs/op

Log a message with a logger that already has 10 fields of context:

Package Time Time % to zap Objects Allocated
⚡ zap 373 ns/op +0% 0 allocs/op
⚡ zap (sugared) 452 ns/op +21% 1 allocs/op
zerolog 288 ns/op -23% 0 allocs/op
go-kit 11785 ns/op +3060% 58 allocs/op
logrus 19629 ns/op +5162% 70 allocs/op
log15 21866 ns/op +5762% 72 allocs/op
apex/log 30890 ns/op +8182% 55 allocs/op

Log a static string, without any context or printf-style templating:

Package Time Time % to zap Objects Allocated
⚡ zap 381 ns/op +0% 0 allocs/op
⚡ zap (sugared) 410 ns/op +8% 1 allocs/op
zerolog 369 ns/op -3% 0 allocs/op
standard library 385 ns/op +1% 2 allocs/op
go-kit 606 ns/op +59% 11 allocs/op
logrus 1730 ns/op +354% 25 allocs/op
apex/log 1998 ns/op +424% 7 allocs/op
log15 4546 ns/op +1093% 22 allocs/op

做了哪些优化#

基于反射的序列化和字符串格式化,它们都是 CPU 密集型计算且分配很多小的内存。具体到 Go 语言中,使用 encoding/json 和 fmt.Fprintf 格式化 interface{} 会使程序性能降低。

Zap 咋解决呢?Zap 使用一个无反射、零分配的 JOSN 编码器,基础 Logger 尽可能避免序列化开销和内存分配开销。在此基础上,zap 还构建了更高级的 SuggaredLogger。

二、quickstart快速开始#

zap 安装:

go get -u go.uber.org/zap

zap 提供了 2 种日志记录器:SugaredLoggerLogger

在需要性能但不是很重要的情况下,使用 SugaredLogger 较合适。它比其它结构化日志包快 4-10 倍,包括 结构化日志和 printf 风格的 API。看下面使用 SugaredLogger 例子:

logger, _ := zap.NewProduction()
defer logger.Sync() // zap底层有缓冲。在任何情况下执行 defer logger.Sync() 是一个很好的习惯
sugar := logger.Sugar()
sugar.Infow("failed to fetch URL",
 // 字段是松散类型,不是强类型
  "url", url,
  "attempt", 3,
  "backoff", time.Second,
)
sugar.Infof("Failed to fetch URL: %s", url)

当性能和类型安全很重要时,请使用 Logger。它比 SugaredLogger 更快,分配的资源更少,但它只支持结构化日志和强类型字段。

logger, _ := zap.NewProduction()
defer logger.Sync()
logger.Info("failed to fetch URL",
  // 字段是强类型,不是松散类型
  zap.String("url", url),
  zap.Int("attempt", 3),
  zap.Duration("backoff", time.Second),
)

三、NewExample/NewDevelopment/NewProduction使用#

zap 为我们提供了三种快速创建 logger 的方法: zap.NewProduction()zap.NewDevelopment()zap.NewExample()

见名思义,Example 一般用在测试代码中,Development 用在开发环境中,Production 用在生成环境中。这三种方法都预先设置好了配置信息。

NewExample()使用#

NewExample 构建一个 logger,专门为在 zap 的测试示例使用。它将 DebugLevel 及以上日志用 JSON 格式标准输出,但它省略了时间戳和调用函数,以保持示例输出的简短和确定性。

为什么说 zap.NewExample() 是 zap 为我们提供快速创建 logger 的方法呢?

因为在这个方法里,zap 已经定义好了日志配置项部分默认值。来看它的代码:

// https://github.com/uber-go/zap/blob/v1.24.0/logger.go#L127
func NewExample(options ...Option) *Logger {
	encoderCfg := zapcore.EncoderConfig{
        MessageKey:     "msg",  // 日志内容key:val, 前面的key设为msg
		LevelKey:       "level", // 日志级别的key设为level
		NameKey:        "logger", // 日志名
		EncodeLevel:    zapcore.LowercaseLevelEncoder, //日志级别,默认小写
		EncodeTime:     zapcore.ISO8601TimeEncoder, // 日志时间
		EncodeDuration: zapcore.StringDurationEncoder,
	}
	core := zapcore.NewCore(zapcore.NewJSONEncoder(encoderCfg), os.Stdout, DebugLevel)
	return New(core).WithOptions(options...)
}

使用例子:

package main

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

func main() {
	logger := zap.NewExample()
	logger.Debug("this is debug message")
	logger.Info("this is info message")
	logger.Info("this is info message with fileds",
		zap.Int("age", 37), 
        zap.String("agender", "man"),
    )
	logger.Warn("this is warn message")
	logger.Error("this is error message")
}
{"level":"debug","msg":"this is debug message"}
{"level":"info","msg":"this is info message"}
{"level":"info","msg":"this is info message with fileds","age":37,"agender":"man"}
{"level":"warn","msg":"this is warn message"}
{"level":"error","msg":"this is error message"}

NewDevelopment()使用#

NewDevelopment() 构建一个开发使用的 Logger,它以人性化的格式将 DebugLevel 及以上日志信息输出。它的底层使用

NewDevelopmentConfig().Build(...Option) 构建。它的日志格式各种设置在函数 NewDevelopmentEncoderConfig() 里,想查看详情设置,请点进去查看。

使用例子:

package main

import (
	"time"

	"go.uber.org/zap"
)

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

	logger.Info("failed to fetch url",
		// 强类型字段
		zap.String("url", "http://example.com"),
		zap.Int("attempt", 3),
		zap.Duration("duration", time.Second),
	)

	logger.With(
		// 强类型字段
		zap.String("url", "http://development.com"),
		zap.Int("attempt", 4),
		zap.Duration("duration", time.Second*5),
	).Info("[With] failed to fetch url")
}
2023-03-22T16:02:45.760+0800    INFO    zapdemos/newdevelopment1.go:13  failed to fetch url     {"url": "http://example.com", "attempt": 3, "duration": "1s"}
2023-03-22T16:02:45.786+0800    INFO    zapdemos/newdevelopment1.go:25  [With] failed to fetch url      {"url": "http://development.com", "attempt": 4, "duration": "5s"}

NewProduction()使用#

NewProduction() 构建了一个合理的 Prouction 日志记录器,它将 info 及以上的日志内容以 JSON 格式记写入标准错误里。

它的底层使用 NewProductionConfig().Build(...Option) 构建。它的日志格式设置在函数 NewProductionEncoderConfig 里。

使用例子

package main

import (
	"time"

	"go.uber.org/zap"
)

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

	url := "http://zap.uber.io"
	sugar := logger.Sugar()
	sugar.Infow("failed to fetch URL",
		"url", url,
		"attempt", 3,
		"time", time.Second,
	)

	sugar.Infof("Failed to fetch URL: %s", url)

	// 或更简洁 Sugar() 使用
	// sugar := zap.NewProduction().Sugar()
	// defer sugar.Sync()
}
{"level":"info","ts":1679472893.2944522,"caller":"zapdemos/newproduction1.go:16","msg":"failed to fetch URL","url":"http://zap.uber.io","attempt":3,"time":1}
{"level":"info","ts":1679472893.294975,"caller":"zapdemos/newproduction1.go:22","msg":"Failed to fetch URL: http://zap.uber.io"}

使用配置#

在这 3 个函数中,可以传入一些配置项。为什么能传入配置项?我们来看看 NewExample() 函数定义:

func NewExample(options ...Option) *Logger

它的函数传参有一个 ...Option 选项,是一个 interface 类型,它关联的是 Logger struct。只要返回 Option 就可以传进 NewExample() 里。在 zap/options.go 文件中可以看到很多返回 Option 的函数,也就是说这些函数都可以传入 NewExample 函数里。这里用到了 Go 里面的一个编码技巧,函数选项模式。

zap.Fields() 添加字段到 Logger 中:

package main

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

func main() {
	logger, _ := zap.NewProduction(zap.Fields(
		zap.String("log_name", "testlog"),
		zap.String("log_author", "prometheus"),
	))
	defer logger.Sync()

	logger.Info("test fields output")

	logger.Warn("warn info")
}
{"level":"info","ts":1679477929.842166,"caller":"zapdemos/fields.go:14","msg":"test fields output","log_name":"testlog","log_author":"prometheus"}
{"level":"warn","ts":1679477929.842166,"caller":"zapdemos/fields.go:16","msg":"warn info","log_name":"testlog","log_author":"prometheus"}

zap.Hook() 添加回调函数:

Hook (钩子函数)回调函数为用户提供一种简单方法,在每次日志内容记录后运行这个回调函数,执行用户需要的操作。也就是说记录完日志后你还想做其它事情就可以调用这个函数。

package main

import (
	"fmt"

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

func main() {
	logger := zap.NewExample(zap.Hooks(func(entry zapcore.Entry) error {
		fmt.Println("[zap.Hooks]test Hooks")
		return nil
	}))
	defer logger.Sync()

	logger.Info("test output")

	logger.Warn("warn info")
}
{"level":"info","msg":"test output"}
[zap.Hooks]test Hooks
{"level":"warn","msg":"warn info"}
[zap.Hooks]test Hooks

四、logger和sugaredlogger区别#

从上面例子中看出,zap 有 2 种格式化日志方式:logger 和 sugared logger。

  • sugared logger:
  1. 它有很好的性能,比一般日志包快 4-10 倍。
  2. 支持结构化的日志。
  3. 支持 printf 风格的日志。
  4. 日志字段不需要定义类型
  • logger(没有sugar)
  1. 它的性能比 sugared logger 还要快
  2. 它只支持强类型的结构化日志。

它应用在对性能更加敏感日志记录中,它的内存分配次数更少。

比如如果每一次内存分配都很重要的话可以使用这个。对类型安全有严格要求也可以使用这个。

logger 和 sugaredlogger 相互转换:

// 创建 logger
logger := zap.NewExample()
defer logger.Sync()

// 转换 SugaredLogger
sugar := logger.Sugar()

// 转换 logger
plain := sugar.Desugar()

怎么快速构建一个 logger 呢?有下面种几种方法:

  • zap.NewProduction()
  • zap.NewDevelopment()
  • zap.Example()

主要区别:

  • 记录日志信息和结构不同。

    Example 和 Production 是 json 格式输出,Development 是普通一行格式输出,如果后面带有字段输出话用json格式。

  • 默认情况下都会打印日志信息到 console 界面
  • 都是通过 logger 调用 Info、Error 等方法

怎么选择:

  • 需要不错的性能但不是很重要的情况下,可以选择 sugaredlogger。它支持结构化日志和 printf 风格的日志记录。sugaredlogger 的日志记录是松散类型的,不是强类型,能接受可变数量的键值对。如果你要用强类型字段记录,可以使用 SugaredLogger.With 方法。
  • 如果是每次或每微秒记录日志都很重要情况下,可以使用 logger,它比 sugaredlogger 每次分配内存更少,性能更高。但它仅支持强类型的结构化日志记录。

五、自定义配置#

快速构建 logger 日志记录器最简单的方法就是用 zap 预定义了配置的方法:NewExample(), NewProduction()NewDevelopment(),这 3 个方法通过单个函数调用就可以构建一个日志计记录器,也可以简单配置。

但是有的项目需要更多的定制,怎么办?zap 的 Config 结构和 zapcore 的 EncoderConfig 结构可以帮助你,让你能够进行自定义配置。

配置结构说明#

Config 配置项源码:

// zap v1.24.0
type Config struct {
    // 动态改变日志级别,在运行时你可以安全改变日志级别
	Level AtomicLevel `json:"level" yaml:"level"`
    // 将日志记录器设置为开发模式,在 WarnLevel 及以上级别日志会包含堆栈跟踪信息
	Development bool `json:"development" yaml:"development"`
    // 在日志中停止调用函数所在文件名、行数
	DisableCaller bool `json:"disableCaller" yaml:"disableCaller"`
    // 完全禁止自动堆栈跟踪。默认情况下,在 development 中,warnlevel及以上日志级别会自动捕获堆栈跟踪信息
    // 在 production 中,ErrorLevel 及以上也会自动捕获堆栈信息
	DisableStacktrace bool `json:"disableStacktrace" yaml:"disableStacktrace"`
    // 设置采样策略。没有 SamplingConfing 将禁止采样
	Sampling *SamplingConfig `json:"sampling" yaml:"sampling"`
    // 设置日志编码。可以设置为 console 和 json。也可以通过 RegisterEncoder 设置第三方编码格式
	Encoding string `json:"encoding" yaml:"encoding"`
    // 为encoder编码器设置选项。详细设置信息在 zapcore.zapcore.EncoderConfig
	EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"`
    // 日志输出地址可以是一个 URLs 地址或文件路径,可以设置多个
	OutputPaths []string `json:"outputPaths" yaml:"outputPaths"`
    // 错误日志输出地址。默认输出标准错误信息
	ErrorOutputPaths []string `json:"errorOutputPaths" yaml:"errorOutputPaths"`
    // 可以添加自定义的字段信息到 root logger 中。也就是每条日志都会携带这些字段信息,公共字段
	InitialFields map[string]interface{} `json:"initialFields" yaml:"initialFields"`
}

EncoderConfig 结构源码,它里面也有很多配置选项,具体请看 这里:

// [email protected]
type EncoderConfig struct {
    // 为log entry设置key。如果 key 为空,那么在日志中的这部分信息也会省略
	MessageKey     string `json:"messageKey" yaml:"messageKey"`//日志信息的健名,默认为msg
	LevelKey       string `json:"levelKey" yaml:"levelKey"`//日志级别的健名,默认为level
	TimeKey        string `json:"timeKey" yaml:"timeKey"`//记录日志时间的健名,默认为time
	NameKey        string `json:"nameKey" yaml:"nameKey"`
	CallerKey      string `json:"callerKey" yaml:"callerKey"`
	FunctionKey    string `json:"functionKey" yaml:"functionKey"`
	StacktraceKey  string `json:"stacktraceKey" yaml:"stacktraceKey"`
	SkipLineEnding bool   `json:"skipLineEnding" yaml:"skipLineEnding"`
	LineEnding     string `json:"lineEnding" yaml:"lineEnding"`
    // 日志编码的一些设置项
	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"`
    // 配置 interface{} 类型编码器。如果没设置,将用 json.Encoder 进行编码
	NewReflectedEncoder func(io.Writer) ReflectedEncoder `json:"-" yaml:"-"`
    // 配置 console 中字段分隔符。默认使用 tab 
	ConsoleSeparator string `json:"consoleSeparator" yaml:"consoleSeparator"`
}
type Entry struct {
	Level      Level
	Time       time.Time
	LoggerName string
	Message    string
	Caller     EntryCaller
	Stack      string
}

例子1:基本配置#

  1. zap.Config 自定义配置,看官方的一个基本例子:
package main

import (
	"encoding/json"

	"go.uber.org/zap"
)

// https://pkg.go.dev/go.uber.org/[email protected]#hdr-Configuring_Zap
func main() {
	// 表示 zap.Config 的 json 原始编码
	// outputPath: 设置日志输出路径,日志内容输出到标准输出和文件 logs.log
	// errorOutputPaths:设置错误日志输出路径
	rawJSON := []byte(`{
      "level": "debug",
      "encoding": "json",
      "outputPaths": ["stdout", "./logs.log"],
      "errorOutputPaths": ["stderr"],
      "initialFields": {"foo": "bar"},
      "encoderConfig": {
        "messageKey": "message-customer",
        "levelKey": "level",
        "levelEncoder": "lowercase"
      }
    }`)

	// 把 json 格式数据解析到 zap.Config struct
	var cfg zap.Config
	if err := json.Unmarshal(rawJSON, &cfg); err != nil {
		panic(err)
	}
	// cfg.Build() 为配置对象创建一个 Logger
	// zap.Must() 封装了 Logger,Must()函数如果返回值不是 nil,就会报 panic。也就是检查Build是否错误
	logger := zap.Must(cfg.Build())
	defer logger.Sync()

	logger.Info("logger construction succeeded")
}

/*
Must() 函数
//  var logger = zap.Must(zap.NewProduction())
func Must(logger *Logger, err error) *Logger {
    if err != nil {
        panic(err)
    }

    return logger
}
*/

consol 输出如下:

{"level":"info","message-customer":"logger construction succeeded","foo":"bar"}

并且在程序目录下生成了一个文件 logs.log,里面记录的日志内容也是上面consol输出内容。每运行一次就在日志文件末尾append一次内容。

例子2:高级配置#

上面的配置只是基本的自定义配置,如果有一些复杂的需求,比如在多个文件之间分割日志。

或者输出到不是 file 的文件中,比如输出到 kafka 中,那么就需要使用 zapcore 包。

在下面的例子中,我们将把日志输出到 kafka 中,并且也输出到 console 里。并且我们对 kafka 不同主题进行编码设置,对输出到 console 编码进行设置,也希望处理高优先级的日志。

官方例子:

package main

import (
	"io"
	"os"

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

func main() {
	// 首先,定义不同级别日志处理逻辑
	highPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
		return lvl >= zapcore.ErrorLevel
	})
	lowPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
		return lvl < zapcore.ErrorLevel
	})

	// 假设有2个kafka 的 topic,一个 debugging,一个 errors

	// zapcore.AddSync 添加一个文件句柄。
	topicDebugging := zapcore.AddSync(io.Discard)
	topicErrors := zapcore.AddSync(io.Discard)

	// 如果他们对并发使用不安全,我们可以用 zapcore.Lock 添加一个 mutex 互斥锁。
	consoleDebugging := zapcore.Lock(os.Stdout)
	consoleErrors := zapcore.Lock(os.Stderr)

	// 设置 kafka 和 console 输出配置
	kafkaEncoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
	consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())

	// 把上面的设置加入到 zapcore.NewCore() 函数里,然后再把他们加入到 zapcore.NewTee() 函数里
	core := zapcore.NewTee(
		zapcore.NewCore(kafkaEncoder, topicErrors, highPriority),
		zapcore.NewCore(consoleEncoder, consoleErrors, highPriority),
		zapcore.NewCore(kafkaEncoder, topicDebugging, lowPriority),
		zapcore.NewCore(consoleEncoder, consoleDebugging, lowPriority),
	)

	// 最后调用 zap.New() 函数
	logger := zap.New(core)
	defer logger.Sync()
	logger.Info("constructed a logger")
}

例子3:日志写入文件#

与上面例子2相似,但是比它简单

package main

import (
	"os"

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

func main() {
	writetofile()
}

func writetofile() {
	// 设置一些配置参数
	config := zap.NewProductionEncoderConfig()
	config.EncodeTime = zapcore.ISO8601TimeEncoder
	fileEncoder := zapcore.NewJSONEncoder(config)
	defaultLogLevel := zapcore.DebugLevel // 设置 loglevel

	logFile, _ := os.OpenFile("./log-test-zap.json", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 06666)
	// or os.Create()
	writer := zapcore.AddSync(logFile)

	logger := zap.New(
		zapcore.NewCore(fileEncoder, writer, defaultLogLevel),
		zap.AddCaller(),
		zap.AddStacktrace(zapcore.ErrorLevel),
	)
	defer logger.Sync()

	url := "http://www.test.com"
	logger.Info("write log to file",
		zap.String("url", url),
		zap.Int("attemp", 3),
	)
}

例子4:根据日志级别写入不同文件#

这个与上面例子2相似

package main

import (
	"os"

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

func main() {
	writeToFileWithLogLevel()
}

func writeToFileWithLogLevel() {
	// 设置配置
	config := zap.NewProductionEncoderConfig()
	config.EncodeTime = zapcore.ISO8601TimeEncoder
	fileEncoder := zapcore.NewJSONEncoder(config)

	logFile, _ := os.OpenFile("./log-debug-zap.json", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) //日志记录debug信息

	errFile, _ := os.OpenFile("./log-err-zap.json", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) //日志记录error信息

	teecore := zapcore.NewTee(
		zapcore.NewCore(fileEncoder, zapcore.AddSync(logFile), zap.DebugLevel),
		zapcore.NewCore(fileEncoder, zapcore.AddSync(errFile), zap.ErrorLevel),
	)

	logger := zap.New(teecore, zap.AddCaller())
	defer logger.Sync()

	url := "http://www.diff-log-level.com"
	logger.Info("write log to file",
		zap.String("url", url),
		zap.Int("time", 3),
	)

	logger.With(
		zap.String("url", url),
		zap.String("name", "jimmmyr"),
	).Error("test error ")
}

主要是设置日志级别,和把 2 个设置的 NewCore 放入到方法 NewTee 中。

六、Hook和Namespace#

zap.Hook()

Hook (钩子函数)回调函数为用户提供一种简单方法,在每次日志内容记录后运行这个回调函数,执行用户需要的操作。也就是说记录完日志后你还想做其它事情就可以调用这个函数。

package main

import (
	"fmt"

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

func main() {
	logger := zap.NewExample(zap.Hooks(func(entry zapcore.Entry) error {
		fmt.Println("[zap.Hooks]test Hooks")
		return nil
	}))
	defer logger.Sync()

	logger.Info("test output")

	logger.Warn("warn info")
}

zap.Namespace():

创建一个命名空间,后面的字段都在这名字空间中。Namespace 就像一个文件夹,后面文件都放在这个文件夹里。

package main

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

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

	logger.Info("some message",
		zap.Namespace("shop"),
		zap.String("name", "LiLei"),
		zap.String("grade", "No2"),
	)

	logger.Error("some error message",
		zap.Namespace("shop"),
		zap.String("name", "LiLei"),
		zap.String("grade", "No3"),
	)
}
{"level":"info","msg":"some message","shop":{"name":"LiLei","grade":"No2"}}
{"level":"error","msg":"some error message","shop":{"name":"LiLei","grade":"No3"}}

七、日志切割归档#

lumberjack 这个库是按照日志大小切割日志文件。

安装 v2 版本:

go get -u github.com/natefinch/lumberjack@v2

Code:

log.SetOutput(&lumberjack.Logger{
    Filename:   "/var/log/myapp/foo.log", // 文件位置
    MaxSize:    500,  // megabytes,M 为单位,达到这个设置数后就进行日志切割
    MaxBackups: 3,    // 保留旧文件最大份数
    MaxAge:     28,   //days , 旧文件最大保存天数
    Compress:   true, // disabled by default,是否压缩日志归档,默认不压缩
})

参照它的文档和结合上面自定义配置的例子,写一个例子:

package main

import (
	"fmt"

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

func main() {
	lumberjacklogger := &lumberjack.Logger{
		Filename:   "./log-rotate-test.json",
		MaxSize:    1, // megabytes
		MaxBackups: 3,
		MaxAge:     28,   //days
		Compress:   true, // disabled by default
	}
	defer lumberjacklogger.Close()

	config := zap.NewProductionEncoderConfig()

	config.EncodeTime = zapcore.ISO8601TimeEncoder // 设置时间格式
	fileEncoder := zapcore.NewJSONEncoder(config)

	core := zapcore.NewCore(
		fileEncoder,                       //编码设置
		zapcore.AddSync(lumberjacklogger), //输出到文件
		zap.InfoLevel,                     //日志等级
	)

	logger := zap.New(core)
	defer logger.Sync()

    // 测试分割日志
	for i := 0; i < 8000; i++ {
		logger.With(
			zap.String("url", fmt.Sprintf("www.test%d.com", i)),
			zap.String("name", "jimmmyr"),
			zap.Int("age", 23),
			zap.String("agradege", "no111-000222"),
		).Info("test info ")
	}

}

八、zap使用总结#

  • zap 的使用,先创建 logger,再调用各个日志级别方法记录日志信息。比如 logger.Info()。

  • zap 提供了三种快速创建 logger 的方法: zap.Newproduction()zap.NewDevelopment()zap.NewExample()。见名思义,Example 一般用在测试代码中,Development 用在开发环境中,Production 用在生成环境中。这三种方法都预先设置好了配置信息。它们的日志数据类型输出都是强类型。

  • 当然,zap 也提供了给用户自定义的方法 zap.New()。比如用户可以自定义一些配置信息等。

  • 在上面的例子中,几乎都有 defer logger.Sync() 这段代码,为什么?因为 zap 底层 API 允许缓冲日志以提高性能,在默认情况下,日志记录器是没有缓冲的。但是在进程退出之前调用 Sync() 方法是一个好习惯。

  • 如果你在 zap 中使用了 sugaredlogger,把 zap 创建 logger 的三种方法用 logger.Sugar() 包装下,那么 zap 就支持 printf 风格的格式化输出,也支持以 w 结尾的方法。如 Infow,Infof 等。这种就是通用类型日志输出,不是强类型输出,不需要强制指定输出的数据类型。它们的性能区别,通用类型会比强类型下降 50% 左右。

比如 Infow 的输出形式,Infow 不需要 zap.String 这种指定字段的数据类型。如下代码:

sugar := logger.Sugar()
sugar.Infow("failed to fetch URL",
            "url", url,
            "attempt", 3,
            "backoff", time.Second,
)

强类型输出,比如 Info 方法输出字段和值就需要指定数据类型:

logger.Info("failed to fetch url",
		// 强类型字段
		zap.String("url", "http://example.com"),
		zap.Int("attempt", 3),
		zap.Duration("backoff", time.Second),
)
  • 强类型输出和通用类型输出区别

    通用类型输出,经过 interface{} 转换会有性能损失,标准库的 fmt.Printf 为了通用性就用了 interface{} 这种”万能型“的数据类型,另外它还使用了反射,性能进一步降低。

    zap 强类型输出,zap 为了提供日志输出性能,zap 的强类型输出没有使用 interface{} 和反射。zap 默认输出就是强类型。

    上面介绍,zap 中 3 种创建 logger 方式(zap.Newproduction()zap.NewDevelopment()zap.NewExample())就是强类型日志字段,当然,也可以转化为通用类型,用 logger.Sugar() 方法创建 SugaredLogger。

  • zap.Namespace() 创建一个命名空间,后面的字段都在这名字空间中。Namespace 就像一个文件夹,后面文件都放在这个文件夹里。

logger.Info("some message",
    zap.Namespace("shop"),
    zap.String("shopid", "s1234323"),
  )
{"level":"info","msg":"some message","shop":{"shopid":"s1234323"}}

九、Demo源码地址#

zap demos

十、参考#


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK