Golang

关注公众号 jb51net

关闭
首页 > 脚本专栏 > Golang > golang整合日志zap

golang整合日志zap的实现示例

作者:code:404-not-found

Go语言中的zap库提供了强大的日志管理功能,支持日志记录到文件、日志切割、多日志级别、结构化日志输出等,它通过三种方法zap.NewProduction()、zap.NewDevelopment()和zap.NewExample(),快速构建适用于不同环境的logger,感兴趣的可以了解一下

在许多Go语言项目中,我们需要一个好的日志记录器能够提供下面这些功能:

安装

go get -u go.uber.org/zap

基本使用

package main

import "go.uber.org/zap"

var logger *zap.Logger

func main() {
	logger, _ = zap.NewProduction() // 使用生产环境
    // logger, _ := zap.NewDevelopment() // 使用开发环境
	defer logger.Sync() // 刷新缓存
    
    
    suger := logger.Sugar() // 开启日志语法糖
    
    suger.Debug("我是Debug级别的日志")
	suger.Debugw("我是Debug级别的日志", "key1", "value1", "key2", "value2")
	suger.Debugf("我是Debug级别的日志%s", "value")

	suger.Warn("我是Warn级别的日志")
	suger.Warnw("我是Warn级别的日志", "key1", "value1", "key2", "value2")
	suger.Warnf("我是Warn级别的日志%s", "value")

	suger.Info("我是Info级别的日志")
	suger.Infow("我是Info级别的日志", "key1", "value1", "key2", "value2")
	suger.Infof("我是Info级别的日志 %s", "info")

	suger.Error("我是Error级别的日志")
	suger.Errorw("我是Error级别的日志", "key1", "value1", "key2", "value2")
	suger.Errorf("我是Error级别的日志 %s", "value")

	suger.Panic("我是Panic级别的日志")
	suger.Panicw("我是Panic级别的日志", "key1", "value1", "key2", "value2")
	suger.Panicf("我是Panic级别的日志 %s", "value")
    
    // 使用不带语法糖的日志
    logger.Debug("我是Debug级别的日志", zap.String("key", "value"), zap.Int("num", 10))
	logger.Warn("我是Warn级别的日志", zap.String("key", "value"), zap.Int("num", 10))
	logger.Info("我是Info级别的日志", zap.String("key", "value"), zap.Int("num", 10))
	logger.Error("我是Error级别的日志", zap.String("key", "value"), zap.Int("num", 10))
	logger.Panic("我是Panic级别的日志", zap.String("key", "value"), zap.Int("num", 10))
}

NewExample/NewDevelopment/NewProduction使用

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

Example 一般用在测试代码中,Development 用在开发环境中,Production 用在生成环境中

NewExample()使用

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

因为在这个方法里,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")
}

NewDevelopment()使用

NewDevelopment() 构建一个开发使用的 Logger

使用例子

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")
}

NewProduction()使用

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

使用例子

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()
}

传入配置项

在这 3 个函数中,可以传入一些配置项。

func NewExample(options …Option) *Logger

package main

import (
	"os"

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

func main() {

	encoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())

	file, _ := os.OpenFile("./log.txt", os.O_CREATE|os.O_APPEND|os.O_RDWR, os.ModePerm)
	syncFile := zapcore.AddSync(file)
	syncConsole := zapcore.AddSync(os.Stderr)
	sync := zapcore.NewMultiWriteSyncer(syncConsole, syncFile)

	core := zapcore.NewCore(encoder, sync, zapcore.InfoLevel)
	logger := zap.New(core)
	logger.Info("info 日志", zap.Int("line", 1))
}

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")
}

自定义配置项

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

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 结构源码,它里面也有很多配置选项

// zapcore@v1.24.0
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
}zap 提供了 2 种日志记录器:`SugaredLogger` 和 `Logger`

SugaredLogger日志

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

使用: suger.[日志级别]x

w 支持键值对方式传入日志

f 支持%s 方式插值

suger := logger.Sugar() // 开启日志语法糖
    
suger.Debug("我是Debug级别的日志")
suger.Debugw("我是Debug级别的日志", "key1", "value1", "key2", "value2")
suger.Debugf("我是Debug级别的日志%s", "value")

suger.Warn("我是Warn级别的日志")
suger.Warnw("我是Warn级别的日志", "key1", "value1", "key2", "value2")
suger.Warnf("我是Warn级别的日志%s", "value")

suger.Info("我是Info级别的日志")
suger.Infow("我是Info级别的日志", "key1", "value1", "key2", "value2")
suger.Infof("我是Info级别的日志 %s", "info")

suger.Error("我是Error级别的日志")
suger.Errorw("我是Error级别的日志", "key1", "value1", "key2", "value2")
suger.Errorf("我是Error级别的日志 %s", "value")

suger.Panic("我是Panic级别的日志")
suger.Panicw("我是Panic级别的日志", "key1", "value1", "key2", "value2")
suger.Panicf("我是Panic级别的日志 %s", "value")

logger日志

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

第一个参数打印日志名,后边支持传入可变参, …zapcore.Field类型

zap.String(“key”, “value”) 表示打印key为字符串 value为字符串的 map

logger.Debug("我是Debug级别的日志", zap.String("key", "value"), zap.Int("num", 10))
logger.Warn("我是Warn级别的日志", zap.String("key", "value"), zap.Int("num", 10))
logger.Info("我是Info级别的日志", zap.String("key", "value"), zap.Int("num", 10))
logger.Error("我是Error级别的日志", zap.String("key", "value"), zap.Int("num", 10))
logger.Panic("我是Panic级别的日志", zap.String("key", "value"), zap.Int("num", 10))

输出日志到文件

cfg := zap.NewProductionConfig()
cfg.OutputPaths = []string{
   "./OUTPUT.log", "stderr", "stdout",
}
logger, _ = cfg.Build()

logger.Debug("打印日志到文件")

日志切割归档

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

go get -u github.com/natefinch/lumberjack@v2
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 ")
	}

}

全局logger

zap提供了 2 种全局 Logger,一个是 zap.Logger,调用 zap.L() 获取;
另外一个是 zap.SugaredLogger ,调用 zap.S() 获取

直接调用 zap.L() 或 zap.S() 记录日志的话,它是不会记录任何日志信息。需要调用 ReplaceGlobals() 函数将它设置为全局 Logger。
ReplaceGlobals 替换全局 Logger 和 SugaredLogger,并返回一个函数来恢复原始值

简单使用例子

package main

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

func main() {
	// 直接调用是不会记录日志信息的,所以下面日志信息不会输出
	zap.L().Info("no log info")
	zap.S().Info("no log info [sugared]")

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

	zap.ReplaceGlobals(logger) // 全局logger,zap.L() 和 zap.S() 需要调用 ReplaceGlobals 函数才会记录日志信息
	zap.L().Info("log info")
	zap.S().Info("log info [sugared]")
}

到此这篇关于golang整合日志zap的实现示例的文章就介绍到这了,更多相关golang整合日志zap内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家! 

您可能感兴趣的文章:
阅读全文