go zap日志库使用说明 及 封装

  • 1 zap日志的基本使用
    • 1.0 zap简介
    • 1.1 日志介绍
    • 1.2 为什么选择zap日志
    • 1.3 zap的安装
    • 1.4 创建实例-两种类型
      • 1.4.1 Logger
      • 1.4.2 SugaredLogger
    • 1.5 自定义logger - zap.New输出到文件
    • 1.6 日志同时输出到控制台和文件
    • 1.7 将JSON Encoder更改为普通的Log Encoder
    • 1.8 编码配置优化-时间-级别大写优化
    • 1.8(1) 上方完整代码记录
    • 1.9 引入第三方库-Lumberjack进行日志切割归档
    • 1.10 Lumberjack的安装
    • 1.11 zap logger中加入Lumberjack
    • 1.12 按级别(归档)写入文件
    • 1.13 控制台按级别显示颜色
    • 1.13(1) 完整代码
    • 1.14 完整代码:移动自定义日志到自定义包中,并封装

1 zap日志的基本使用

1.0 zap简介

参考:
zap日志的基本使用(go必会知识*)

一张图

1.1 日志介绍

项目在开发阶段,如果出现问题,一般会去查看日志,来定位问题,这是非常有效的,上线后更加需要日志

那么我们需要怎么样的日志呢?

其实跟go语言相关的日志库有很多,但是一个好的日志,往往具备以下几个条件:

  • 能打印最基本的信息,例如调用的文件,函数名称,行号,日志时间等

  • 支持不同的日志级别,例如: info、debug、error 等

  • 能够将记录的日志保存在文件里面,并且可以根据时间或者文件大小来切割日志文件,而zap就完全满足了,他非常的高效,并且是结构化的,分级的go日志库。

1.2 为什么选择zap日志

两个点:

  1. 提供结构化日志记录,并且是printf风格的日志记录
  2. 记录一个静态字符串,没有任何上下文或printf风格的模板:

根据Uber-go Zap的文档,它的性能比类似的结构化日志包更好——也比标准库更快。 以下是Zap发布的基准测试信息记录一条消息和10个字段所消耗的时间对比:

1.3 zap的安装

go get即可

go get -u go.uber.org/zap

1.4 创建实例-两种类型

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

在性能很好但不是很关键的上下文中,使用 SugaredLogger 。它比其他结构化日志记录包快4-10倍,并且支持结构化和printf风格的日志记录

在每一微秒和每一次内存分配都很重要的上下文中,使用 Logger 。它甚至比 SugaredLogger 更快,内存分配次数也更少,但它只支持强类型的结构化日志记录

注意默认情况下日志都会打印到应用程序的console界面。

1.4.1 Logger

可以通过调用zap.NewProduction() zap.NewDevelopment() 或者 zap.Example() 来创建一个Logger。

这三个方法的区别在于它将记录的信息不同,参数只能是string类型

//代码
var log *zap.Logger
log = zap.NewExample()
log, _ := zap.NewDevelopment()
log, _ := zap.NewProduction()
log.Debug("This is a DEBUG message")
log.Info("This is an INFO message")//Example 输出
{"level":"debug","msg":"This is a DEBUG message"}
{"level":"info","msg":"This is an INFO message"}//Development 输出
2018-10-30T17:14:22.459+0800    DEBUG    development/main.go:7    This is a DEBUG message
2018-10-30T17:14:22.459+0800    INFO    development/main.go:8    This is an INFO message//Production 输出
{"level":"info","ts":1540891173.3190675,"caller":"production/main.go:8","msg":"This is an INFO message"}
{"level":"info","ts":1540891173.3191047,"caller":"production/main.go:9","msg":"This is an INFO message with fields","region":["us-west"],"id":2}

三种创建方式对比:

ExampleProduction 使用的是json格式输出,Development使用行的形式输出, 其中值得关注的就Production,Development创建的Logger

NewDevelopment 是以 空格分开 的形式展示
NewProduction 使用的是 json格式键值对的形式 展示出来

Development

  1. 从警告级别向上打印到堆栈中来跟踪

  2. 始终打印包/文件/行(方法)

  3. 在行尾添加任何额外字段作为json字符串

  4. 以大写形式打印级别名称

  5. 以毫秒为单位打印ISO8601格式的时间戳

Production

  1. 调试级别消息不记录

  2. Error,Dpanic级别的记录,会在堆栈中跟踪文件,Warn不会

  3. 始终将调用者添加到文件中

  4. 以时间戳格式打印日期

  5. 以小写形式打印级别名称

1.4.2 SugaredLogger

它们惟一的区别是,我们可以通过调用主logger的. Sugar()方法来获取一个SugaredLogger,然后使用SugaredLoggerprintf格式记录语句,例如

var sugarLogger *zap.SugaredLoggerfunc InitLogger() {logger, _ := zap.NewProduction()sugarLogger = logger.Sugar()
}func main() {InitLogger()defer sugarLogger.Sync()sugarLogger.Errorf("Error fetching URL %s : Error = %s", url, err)
}

1.5 自定义logger - zap.New输出到文件

如果不想将日志信息打印在终端,那么可以自定义配置,使用 zap.New(…) 方法来手动传递所有配置。zap.New源码

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

可以看到New方法需要一个zapcore.Core的参数,并且这里Core是一个interface类型的接口,看Core类型的参数怎么获取,通过点击Core,查看到源码中,如下:NewCore可以返回一个ioCore,然后再源码中ioCore实现了Core提供的几个接口(这里自行去查看,接口实现不列出)。

// 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 {LevelEnablerenc Encoderout WriteSyncer
}

从源码中可以看到,只需要3个参数,就能得到一个Logger了,那么这三个参数分别表示什么呢?

Encoder编码器(如何写入日志)。我们将使用开箱即用的NewJSONEncoder(),并使用预先设置的ProductionEncoderConfig()。

// core 三个参数之  Encoder 编码
func getEncoder() zapcore.Encoder {return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
}

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

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

LevelEnabler: 设置打印的日志等级,通过它来动态的保存日志,比如上线后我们error以下的日志就不打印了!

我们通过 zapcore.***Level 来设置,里面都是封装好的日志等级,可以看下zapcore的源码哦

const (// DebugLevel logs are typically voluminous, and are usually disabled in// production.DebugLevel Level = iota - 1// InfoLevel is the default logging priority.InfoLevel// WarnLevel logs are more important than Info, but don't need individual// human review.WarnLevel// ErrorLevel logs are high-priority. If an application is running smoothly,// it shouldn't generate any error-level logs.ErrorLevel// DPanicLevel logs are particularly important errors. In development the// logger panics after writing the message.DPanicLevel// PanicLevel logs a message, then panics.PanicLevel// FatalLevel logs a message, then calls os.Exit(1).FatalLevel_minLevel = DebugLevel_maxLevel = FatalLevel// InvalidLevel is an invalid value for Level.//// Core implementations may panic if they see messages of this level.InvalidLevel = _maxLevel + 1
)

将上面提到的都结合起来,如下,我们就可以创建一个logger了(想提供给外部使用,命名改成大写)

package mainimport ("go.uber.org/zap""go.uber.org/zap/zapcore""os"
)var logger *zap.Logger
var SugaredLogger *zap.SugaredLoggerfunc InitLogger() {encoder := getEncode()writerSyncer := getWriterSyncer()core := zapcore.NewCore(encoder, writerSyncer, zap.DebugLevel)logger = zap.New(core)SugaredLogger = logger.Sugar()
}// core 三个参数之  Encoder 编码
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)
}

我们来跑一个例子:

func main() {InitLogger()defer logger.Sync()logger.Info("Starting zaplog_testing...",zap.String("key_test", "test key_value"))
}

至此,日志信息已经持久化到文件中了,以后就可以在文件中查看了,但是只有文件记录,在当前调试的时候,每次都要去找到日志文件,然后打开日志文件再查看日志信息是不是很麻烦?有没有即能输出到文件的同时,又能打印到控制台的方式呢? 显然是有的,见下方

1.6 日志同时输出到控制台和文件

如果需要同时输出控制台和文件,只需要改造一下zapcore.NewCore即可

本质其实就是修改一下 WriteSyncer ,使用zapcore.NewMultiWriteSyncer来设置多个输出对象,如下示例,依然使用上面1.5的代码.

package mainimport ("go.uber.org/zap""go.uber.org/zap/zapcore""os"
)var logger *zap.Logger
var SugaredLogger *zap.SugaredLoggerfunc InitLogger() {encoder := getEncoder()writerSyncer := getWriterSyncer()//multiWriteSyncer := zapcore.NewMultiWriteSyncer(writerSyncer, zapcore.AddSync(os.Stdout)) //AddSync将io.Writer转换成WriteSyncer的类型core := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(writerSyncer, zapcore.AddSync(os.Stdout)), zap.DebugLevel)logger = zap.New(core, zap.AddCaller()) //zap.AddCaller() 显示文件名 和 行号SugaredLogger = logger.Sugar()
}// core 三个参数之  Encoder 编码
func getEncoder() zapcore.Encoder {//zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
}// core 三个参数之  日志输出路径
func getWriterSyncer() zapcore.WriteSyncer {file, _ := os.Create("./server/zaplog_test/log.log")//或者将上面的NewMultiWriteSyncer放到这里来,进行返回return zapcore.AddSync(file)
}func main() {InitLogger()defer logger.Sync()logger.Info("Starting zaplog_testing...",zap.String("key_test", "test key_value"))
}

输出结果展示:成功将日志信息,同时输出到控制台 和 文件中了,(扩展:可以根据需要设置一个开关,来控制是同时输出,还是只输出某一个)

1.7 将JSON Encoder更改为普通的Log Encoder

我们采用编码格式的时候,采用的json格式,而有的人习惯看空格,这也是可以设置的

// core 三个参数之  Encoder 编码
func getEncoder() zapcore.Encoder {return zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())//return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
}

但是有时候,想要自定义编码器,怎么办呢?也是可以自行设置的。

1.8 编码配置优化-时间-级别大写优化

那么我们就需要对 encoderConfig 进行一个自定义配置了

// core 三个参数之  Encoder 编码
func getEncoder() zapcore.Encoder {//自定义编码配置encoderConfig := zap.NewProductionEncoderConfig()encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder   //修改时间编码器encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder //在日志文件中使用大写字母记录日志级别//return zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())//return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())return zapcore.NewJSONEncoder(encoderConfig)
}

输出展示如下:时间转换成了能看得懂的格式,INFO日志级别打印变成了大写

怎么来获取 调用的文件,函数名称,行号呢?

在上面的代码中已经加进去了,在调用zap.New的时候,追加一个 zap.AddCaller() 即可,如下

//这里logger是之前声明的全局变量,临时变量记得使用 := 不要犯低级错误
logger = zap.New(core, zap.AddCaller()) //zap.AddCaller() 显示文件名 和 行号

展示输出结果,如下:

你以为没了? 不,还有最重要的一点,文件的切割,但是很可惜,zap没有这玩意,所以我们只能采用第三方库来实现

1.8(1) 上方完整代码记录

package mainimport ("go.uber.org/zap""go.uber.org/zap/zapcore""os"
)var logger *zap.Logger
var SugaredLogger *zap.SugaredLoggerfunc InitLogger() {encoder := getEncoder()writerSyncer := getWriterSyncer()//multiWriteSyncer := zapcore.NewMultiWriteSyncer(writerSyncer, zapcore.AddSync(os.Stdout)) //AddSync将io.Writer转换成WriteSyncer的类型//同时输出到控制台 和 指定的日志文件中core := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(writerSyncer, zapcore.AddSync(os.Stdout)), zap.DebugLevel)logger = zap.New(core, zap.AddCaller()) //zap.AddCaller() 显示文件名 和 行号SugaredLogger = logger.Sugar()
}// core 三个参数之  Encoder 编码
func getEncoder() zapcore.Encoder {//自定义编码配置encoderConfig := zap.NewProductionEncoderConfig()encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder   //修改时间编码器encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder //在日志文件中使用大写字母记录日志级别//return zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())//return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())return zapcore.NewJSONEncoder(encoderConfig)
}// core 三个参数之  日志输出路径
func getWriterSyncer() zapcore.WriteSyncer {file, _ := os.Create("./server/zaplog_test/log.log")//或者将上面的NewMultiWriteSyncer放到这里来,进行返回return zapcore.AddSync(file)
}func main() {InitLogger()defer logger.Sync()logger.Info("Starting zaplog_testing...",zap.String("key_test", "test key_value"))
}

打印结果展示,如下:

C:\Users\Administrator\AppData\Local\Temp\GoLand\___go_build_goland_prj_test_server_zaplog_test.exe
{"level":"INFO","ts":"2022-08-25T16:21:19.468+0800","caller":"zaplog_test/zaplog.go:44","msg":"Starting zaplog_testing...","ke
y_test":"test key_value"}

log.log 文件中

{"level":"INFO","ts":"2022-08-25T16:21:19.468+0800","caller":"zaplog_test/zaplog.go:44","msg":"Starting zaplog_testing...","key_test":"test key_value"}

1.9 引入第三方库-Lumberjack进行日志切割归档

注意:Zap本身不支持切割归档日志文件

为了实现切割功能呢,我们采用第三方库 Lumberjack

1.10 Lumberjack的安装

下载:下载完之后,使用的过程,goland有可能会出现 xxxx@xx 不允许包名中存在@符号,直接点击提示中的修改包名即可

go get -u github.com/natefinch/lumberjack

1.11 zap logger中加入Lumberjack

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

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

lumberjack.Logger采用以下属性作为输入:

属性 含义
Filename 日志文件的位置,也就是路径
MaxSize 在进行切割之前,日志文件的最大大小(以MB为单位)
MaxBackups 保留旧文件的最大个数
MaxAges 保留旧文件的最大天数
Compress 是否压缩/归档旧文件

完整代码如下:

package mainimport ("go.uber.org/zap""go.uber.org/zap/zapcore""goland_prj_test/go/pkg/mod/gopkg.in/natefinch/lumberjack.v2""net/http""os"
)var logger *zap.Logger
var SugaredLogger *zap.SugaredLoggerfunc InitLogger() {encoder := getEncoder()writerSyncer := getWriterSyncer()//multiWriteSyncer := zapcore.NewMultiWriteSyncer(writerSyncer, zapcore.AddSync(os.Stdout)) //AddSync将io.Writer转换成WriteSyncer的类型//同时输出到控制台 和 指定的日志文件中core := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(writerSyncer, zapcore.AddSync(os.Stdout)), zap.DebugLevel)logger = zap.New(core, zap.AddCaller()) //zap.AddCaller() 显示文件名 和 行号SugaredLogger = logger.Sugar()
}// core 三个参数之  Encoder 编码
func getEncoder() zapcore.Encoder {//自定义编码配置encoderConfig := zap.NewProductionEncoderConfig()encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder   //修改时间编码器encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder //在日志文件中使用大写字母记录日志级别//return zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())//return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())return zapcore.NewJSONEncoder(encoderConfig)
}// core 三个参数之  日志输出路径
func getWriterSyncer() zapcore.WriteSyncer {//file, _ := os.Create("./server/zaplog_test/log.log")或者将上面的NewMultiWriteSyncer放到这里来,进行返回//return zapcore.AddSync(file)//引入第三方库 Lumberjack 加入日志切割功能lumberWriteSyncer := &lumberjack.Logger{Filename:   "./server/zaplog_test/log.log",MaxSize:    10, // megabytesMaxBackups: 100,MaxAge:     28,    // daysCompress:   false, //Compress确定是否应该使用gzip压缩已旋转的日志文件。默认值是不执行压缩。}return zapcore.AddSync(lumberWriteSyncer)
}func main() {InitLogger()defer logger.Sync()simpleHttpGet("www.baidu.com")simpleHttpGet("http://www.baidu.com")
}func simpleHttpGet(url string) {resp, err := http.Get(url)if err != nil {logger.Error("Error fetching url..",zap.String("url", url),zap.Error(err))} else {logger.Info("Success..",zap.String("statusCode", resp.Status),zap.String("url", url))resp.Body.Close()}
}

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

至于测试数据,大家可以跑几个 goroutine来试试,把MaxSize调小一点,即可看到切分的效果

输出结果:此时,日志文件以打开,追加的形式写入文件了,而非之前的os.Create(可读可写,创建,清空)

func Create(name string) (*File, error) {return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}

1.12 按级别(归档)写入文件

引用:golang高性能日志库zap的使用

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

package mainimport ("go.uber.org/zap""go.uber.org/zap/zapcore""goland_prj_test/go/pkg/mod/gopkg.in/natefinch/lumberjack.v2""net/http""os"
)func main() {InitLogger()defer logger.Sync()simpleHttpGet("www.baidu.com")simpleHttpGet("http://www.baidu.com")
}var logger *zap.Logger
var SugaredLogger *zap.SugaredLoggerfunc simpleHttpGet(url string) {resp, err := http.Get(url)if err != nil {logger.Error("Error fetching url..",zap.String("url", url),zap.Error(err))} else {logger.Info("Success..",zap.String("statusCode", resp.Status),zap.String("url", url))resp.Body.Close()}
}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文件WriteSyncerinfoFileWriteSyncer := getInfoWriterSyncer()//error文件WriteSyncererrorFileWriteSyncer := 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.CorecoreArr = append(coreArr, infoFileCore)coreArr = append(coreArr, errorFileCore)//生成loggerlogger = zap.New(zapcore.NewTee(coreArr...), zap.AddCaller()) //zap.AddCaller() 显示文件名 和 行号SugaredLogger = logger.Sugar()
}// core 三个参数之  Encoder 获取编码器
func getEncoder() zapcore.Encoder {//自定义编码配置encoderConfig := zap.NewProductionEncoderConfig()encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder   //指定时间格式encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder //在日志文件中使用大写字母记录日志级别//encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder //按级别显示不同颜色,不需要的话取值zapcore.CapitalLevelEncoder就可以了//encoderConfig.EncodeCaller = zapcore.FullCallerEncoder        //显示完整文件路径//return zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())//return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())return zapcore.NewJSONEncoder(encoderConfig)
}// core 三个参数之  日志输出路径
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, // megabytesMaxBackups: 100,MaxAge:     28,    // daysCompress:   false, //Compress确定是否应该使用gzip压缩已旋转的日志文件。默认值是不执行压缩。}return zapcore.AddSync(infoLumberIO)
}func getErrorWriterSyncer() zapcore.WriteSyncer {//引入第三方库 Lumberjack 加入日志切割功能lumberWriteSyncer := &lumberjack.Logger{Filename:   "./server/zaplog/error.log",MaxSize:    10, // megabytesMaxBackups: 100,MaxAge:     28,    // daysCompress:   false, //Compress确定是否应该使用gzip压缩已旋转的日志文件。默认值是不执行压缩。}return zapcore.AddSync(lumberWriteSyncer)
}

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

1.13 控制台按级别显示颜色

指定编码器的EncodeLevel即可,

// core 三个参数之  Encoder 获取编码器
func getEncoder() zapcore.Encoder {//自定义编码配置encoderConfig := zap.NewProductionEncoderConfig()encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder //指定时间格式//encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder //在日志文件中使用大写字母记录日志级别encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder //按级别显示不同颜色,不需要的话取值zapcore.CapitalLevelEncoder就可以了//encoderConfig.EncodeCaller = zapcore.FullCallerEncoder        //显示完整文件路径//return zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())//return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())return zapcore.NewJSONEncoder(encoderConfig)
}

1.13(1) 完整代码

如果需要抽离logger,到一个自定义的包中,就直接把logger改成大写即可,很简单。

package mainimport ("go.uber.org/zap""go.uber.org/zap/zapcore""goland_prj_test/go/pkg/mod/gopkg.in/natefinch/lumberjack.v2""net/http""os"
)func main() {InitLogger()defer logger.Sync()simpleHttpGet("www.baidu.com")simpleHttpGet("http://www.baidu.com")
}var logger *zap.Logger
var SugaredLogger *zap.SugaredLoggerfunc simpleHttpGet(url string) {resp, err := http.Get(url)if err != nil {logger.Error("Error fetching url..",zap.String("url", url),zap.Error(err))} else {logger.Info("Success..",zap.String("statusCode", resp.Status),zap.String("url", url))resp.Body.Close()}
}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文件WriteSyncerinfoFileWriteSyncer := getInfoWriterSyncer()//error文件WriteSyncererrorFileWriteSyncer := 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.CorecoreArr = append(coreArr, infoFileCore)coreArr = append(coreArr, errorFileCore)//生成loggerlogger = zap.New(zapcore.NewTee(coreArr...), zap.WithCaller(true)) //zap.AddCaller() 显示文件名 和 行号SugaredLogger = logger.Sugar()
}// core 三个参数之  Encoder 获取编码器
func getEncoder() zapcore.Encoder {//自定义编码配置encoderConfig := zap.NewProductionEncoderConfig()encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder   //指定时间格式encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder //在日志文件中使用大写字母记录日志级别//encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder //按级别显示不同颜色,不需要的话取值zapcore.CapitalLevelEncoder就可以了//encoderConfig.EncodeCaller = zapcore.FullCallerEncoder       //显示完整文件路径//return zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())//return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())return zapcore.NewJSONEncoder(encoderConfig)
}// core 三个参数之  日志输出路径
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, // megabytesMaxBackups: 100,MaxAge:     28,    // daysCompress:   false, //Compress确定是否应该使用gzip压缩已旋转的日志文件。默认值是不执行压缩。}return zapcore.AddSync(infoLumberIO)
}func getErrorWriterSyncer() zapcore.WriteSyncer {//引入第三方库 Lumberjack 加入日志切割功能lumberWriteSyncer := &lumberjack.Logger{Filename:   "./server/zaplog/error.log",MaxSize:    10, // megabytesMaxBackups: 100,MaxAge:     28,    // daysCompress:   false, //Compress确定是否应该使用gzip压缩已旋转的日志文件。默认值是不执行压缩。}return zapcore.AddSync(lumberWriteSyncer)
}

1.14 完整代码:移动自定义日志到自定义包中,并封装

zaplogger/logger.go

package zaploggerimport ("fmt""go.uber.org/zap""go.uber.org/zap/zapcore""goland_prj_test/go/pkg/mod/gopkg.in/natefinch/lumberjack.v2""os""time"
)var Logger *zap.Logger
var SugaredLogger *zap.SugaredLoggerfunc init() {InitLogger()
}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文件WriteSyncerinfoFileWriteSyncer := getInfoWriterSyncer()//error文件WriteSyncererrorFileWriteSyncer := 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.CorecoreArr = append(coreArr, infoFileCore)coreArr = append(coreArr, errorFileCore)//生成LoggerLogger = zap.New(zapcore.NewTee(coreArr...), zap.AddCaller()) //zap.AddCaller() 显示文件名 和 行号SugaredLogger = Logger.Sugar()
}func timeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {enc.AppendString(t.Format("2006-01-02 15:04:05.000"))
}func levelEncoder(l zapcore.Level, enc zapcore.PrimitiveArrayEncoder) {var level stringswitch l {case zapcore.DebugLevel:level = "[DEBUG]"case zapcore.InfoLevel:level = "[INFO]"case zapcore.WarnLevel:level = "[WARN]"case zapcore.ErrorLevel:level = "[ERROR]"case zapcore.DPanicLevel:level = "[DPANIC]"case zapcore.PanicLevel:level = "[PANIC]"case zapcore.FatalLevel:level = "[FATAL]"default:level = fmt.Sprintf("[LEVEL(%d)]", l)}enc.AppendString(level)
}func shortCallerEncoder(caller zapcore.EntryCaller, enc zapcore.PrimitiveArrayEncoder) {enc.AppendString(fmt.Sprintf("[%s]", caller.TrimmedPath()))
}func NewEncoderConfig() zapcore.EncoderConfig {return zapcore.EncoderConfig{// Keys can be anything except the empty string.TimeKey:        "T",LevelKey:       "L",NameKey:        "N",CallerKey:      "C",MessageKey:     "M",StacktraceKey:  "S",LineEnding:     zapcore.DefaultLineEnding,EncodeLevel:    levelEncoder, //zapcore.CapitalLevelEncoder,EncodeTime:     timeEncoder,  //指定时间格式EncodeDuration: zapcore.StringDurationEncoder,EncodeCaller:   shortCallerEncoder, //zapcore.ShortCallerEncoder,}
}// core 三个参数之  Encoder 获取编码器
func getEncoder() zapcore.Encoder {//自定义编码配置,下方NewJSONEncoder输出如下的日志格式//{"L":"[INFO]","T":"2022-09-16 14:24:59.552","C":"[prototest/main.go:113]","M":"name = xiaoli, age = 18"}return zapcore.NewJSONEncoder(NewEncoderConfig())//下方NewConsoleEncoder输出如下的日志格式//2022-09-16 14:26:02.933 [INFO]  [prototest/main.go:113] name = xiaoli, age = 18//return zapcore.NewConsoleEncoder(NewEncoderConfig())
}// core 三个参数之  日志输出路径
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, // megabytesMaxBackups: 100,MaxAge:     28,    // daysCompress:   false, //Compress确定是否应该使用gzip压缩已旋转的日志文件。默认值是不执行压缩。}return zapcore.AddSync(infoLumberIO)
}func getErrorWriterSyncer() zapcore.WriteSyncer {//引入第三方库 Lumberjack 加入日志切割功能lumberWriteSyncer := &lumberjack.Logger{Filename:   "./server/zaplog/error.log",MaxSize:    10, // megabytesMaxBackups: 100,MaxAge:     28,    // daysCompress:   false, //Compress确定是否应该使用gzip压缩已旋转的日志文件。默认值是不执行压缩。}return zapcore.AddSync(lumberWriteSyncer)
}// Debugf 不再封装使用 - 在显示调用者文件名的时候,会全部显示调用者为logger/zaplogger.go - 所以如果要显示调用者文件名和行号,这里的封装就不合适了
//直接使用 logger.Logger.Info(xxx)
//或者   logger.SugaredLogger.Infof("xxx%s", name)
// logs.Debug(...) 再封装
func Debugf(format string, v ...interface{}) {Logger.Sugar().Debugf(format, v...)
}func Infof(format string, v ...interface{}) {Logger.Sugar().Infof(format, v...)
}func Warnf(format string, v ...interface{}) {Logger.Sugar().Warnf(format, v...)
}func Errorf(format string, v ...interface{}) {Logger.Sugar().Errorf(format, v...)
}func Panicf(format string, v ...interface{}) {Logger.Sugar().Panicf(format, v...)
}// logs.Debug(...) 再封装
func Debug(format string, fileds ...zapcore.Field) {Logger.Debug(format, fileds...)
}func Info(format string, fileds ...zapcore.Field) {Logger.Info(format, fileds...)
}func Warn(format string, fileds ...zapcore.Field) {Logger.Warn(format, fileds...)
}func Error(format string, fileds ...zapcore.Field) {Logger.Error(format, fileds...)
}func Panic(format string, fileds ...zapcore.Field) {Logger.Panic(format, fileds...)
}

测试

main.go

package mainimport ("go.uber.org/zap""goland_prj_test/server/zaplog/zaplogger""net/http"
)func main() {zaplogger.InitLogger()defer zaplogger.Logger.Sync()simpleHttpGet("www.baidu.com")simpleHttpGet("http://www.baidu.com")
}func simpleHttpGet(url string) {resp, err := http.Get(url)if err != nil {s := "mingcheng------------"zaplogger.Error("Error fetching url..",zap.String("url", url),zap.Error(err))zaplogger.Errorf("Error fetching url..",zap.String("url", url),zap.Error(err))zaplogger.Error("----------test")zaplogger.Errorf("----------test %s", s)} else {s := "name----"zaplogger.Info("Success..",zap.String("statusCode", resp.Status),zap.String("url", url))zaplogger.Infof("Success..%s", s,zap.String("statusCode", resp.Status),zap.String("url", url))zaplogger.Info("-----------test info")zaplogger.Infof("-----------test info ", s, "========test info ", s)resp.Body.Close()}
}

输出结果展示:封装的Debug之类的效果不是很好-待办

{"level":"ERROR","ts":"2022-08-25T17:55:00.012+0800","caller":"zaplogger/logger.go:124","msg":"Error fetching url..","url":"www.baidu.com","error":"Get \"www.baidu.com\": unsupported protocol scheme \"\""}
{"level":"ERROR","ts":"2022-08-25T17:55:00.037+0800","caller":"zaplogger/logger.go:103","msg":"Error fetching url..%!(EXTRA zapcore.Field={url 15 0 www.baidu.com <nil>}, zapcore.Field={error 26 0  Get \"www.baidu.com\": unsupported protocol scheme \"\"})"}
{"level":"ERROR","ts":"2022-08-25T17:55:00.037+0800","caller":"zaplogger/logger.go:124","msg":"----------test"}
{"level":"ERROR","ts":"2022-08-25T17:55:00.038+0800","caller":"zaplogger/logger.go:103","msg":"----------test mingcheng------------"}
{"level":"INFO","ts":"2022-08-25T17:55:00.078+0800","caller":"zaplogger/logger.go:116","msg":"Success..","statusCode":"200 OK","url":"http://www.baidu.com"}
{"level":"INFO","ts":"2022-08-25T17:55:00.080+0800","caller":"zaplogger/logger.go:95","msg":"Success..name----%!(EXTRA zapcore.Field={statusCode 15 0 200 OK <nil>}, zapcore.Field={url 15 0 http://www.baidu.com <nil>})"}
{"level":"INFO","ts":"2022-08-25T17:55:00.080+0800","caller":"zaplogger/logger.go:116","msg":"-----------test info"}
{"level":"INFO","ts":"2022-08-25T17:55:00.081+0800","caller":"zaplogger/logger.go:95","msg":"-----------test info %!(EXTRA string=name----, string=========test info , string=name----)"}

go zap日志库的使用,以及封装。相关推荐

  1. go语言 gin框架中集成zap日志库

    在go语言gin框架中,日志是默认输出到终端的,但是我们在实际工作中,一般来说是需要记录服务器日志的.而最常用的日志库就是zap日志库,我们需要将gin在终端输出的内容通过zap日志库记录到文件中,首 ...

  2. Go使用Zap日志库

    前言 在项目开发中,经常需要把程序运行过程中各种信息记录下来,有了详细的日志有助于问题排查和功能优化:但如何选择和使用性能好功能强大的日志库,这个就需要我们从多角度考虑. 一.日志库选型需要和比较 1 ...

  3. (1)go web开发之 zap日志库的使用及gin框架配置zap记录日志详细文档讲解分析

    (一)介绍 zap 是go 中比较火的一个日志库,提供不同级别的日志,并且速度快 官方文档: https://pkg.go.dev/go.uber.org/zap#section-readme, 也可 ...

  4. 一文告诉你如何用好uber开源的zap日志库

    1. 引子 日志在后端系统中有着重要的地位,通过日志不仅可以直观看到程序的当前运行状态,更重要的是日志可以在程序发生问题时为开发人员提供线索. 在Go生态中,logrus[2]可能是使用最多的Go日志 ...

  5. Go开发中配置一个Logger日志的功能实现(结合zap日志库)

    为什么需要Logger 一般在开发项目的时候我们都是需要一个存储日志的文件,因为在部署项目以后,我们只能通过去筛查日志进行检索问题,这时候日志是否可以呈现清晰这个对于我们进行排查工作是十分重要的,所以 ...

  6. 【Go进阶】如何让你Go项目中日志清晰有趣-Zap日志库

    本文先介绍了Go语言原生的日志库的使用,然后详细介绍了非常流行的Uber开源的zap日志库,同时介绍了如何搭配Lumberjack实现日志的切割和归档. Zap日志库在Go语言项目中的使用 在许多Go ...

  7. Go 语言中的 logger 和 zap 日志库

    目录 Go 语言中的 logger 和 zap 日志库 Go Logger Zap Logger Logger Sugared Logger 定制 Logger 记录到文件中 Zap logger 中 ...

  8. go.uber.org/zap日志库

    文章目录 go.uber.org/zap日志库 1.GO SDK里Logger优缺点 2.介绍Uber-go zap 3.安装zap依赖 4.zap.NewProductionEncoderConfi ...

  9. 在Go语言项目中使用Zap日志库

    在Go语言项目中使用Zap日志库 Go语言原生的日志库的使用,然后详细介绍了非常流行的Uber开源的zap日志库,同时介绍了如何搭配Lumberjack实现日志的切割和归档. 一.在Go语言项目中使用 ...

最新文章

  1. 【Java Web开发指南】JQuery基础笔记
  2. 执行setenv ethaddr的时候提示Can't over write的原因及解决方法
  3. MAC随机修改批处理
  4. 大样品随机双盲测试_训练和测试样品生成
  5. 宕机了,Redis数据丢了怎么办?
  6. 前端开发知识点解答-CSS-面试
  7. 电脑休眠和睡眠的区别_Windows操作系统中的休眠模式和睡眠模式有什么区别?...
  8. php网页如何做出透明的效果,css+filter实现简单的图片透明效果
  9. 【ES】ES 好文档积累
  10. dex、apk完整性校验
  11. oracle密码锁屏时间,Oracle Linux OEL7 如何关闭屏保和锁屏功能
  12. Memcache的使用和协议分析详解
  13. 景林合伙人张小刚:我们的优势在哪里?
  14. 小米cc9出厂线刷包_小米CC9手机忘记密码一键刷机解锁 | 线刷宝刷机工具刷机包下载...
  15. 经典五笔(五笔输入法)
  16. 前中后序遍历的递归与非递归算法,层序遍历
  17. Hive 优化之 推测执行
  18. Android应用内设置多语言,可随系统语言改变而改变,也可设置app为固定语言不受系统语言影响
  19. 矩阵分析与应用(二)——矩阵微分
  20. Linux服务器下Matlab的安装

热门文章

  1. 回望即将过去的2018年,展望即将到来的2019年
  2. DFS (深度优先搜索) 算法详解 + 模板 + 例题,这一篇就够了
  3. verilog中的generate
  4. Win7运行窗口的打开方法
  5. 推荐系统TopN推荐评测指标
  6. SQL必知必会 笔记 第十一章 使用子查询
  7. JS逆向-RSA算法加密(房天下模拟登录)
  8. mysql useradd_一天一个linux基础命令之添加用户useradd
  9. c语言输出空格问题。
  10. 嘉一机电告诉你胶球清洗装置好不好用(附安装示意图)