go zap日志库的使用,以和封装。
1 zap日志的基本使用
1.0 zap简介
一张图
1.1 日志介绍
项目在开发阶段,如果出现问题,一般会去查看日志,来定位问题,这是非常有效的,上线后更加需要日志
那么我们需要怎么样的日志呢?
其实跟go语言相关的日志库有很多,但是一个好的日志,往往具备以下几个条件:
-
能打印最基本的信息
,例如调用的文件,函数名称,行号,日志时间等 -
支持不同的日志级别
,例如: info、debug、error 等 -
能够将记录的日志保存在文件里面,并且可以根据时间或者文件大小来切割日志文件
,而zap就完全满足了,他非常的高效,并且是结构化的,分级的go日志库。
1.2 为什么选择zap日志
两个点:
- 提供
结构化
日志记录,并且是printf风格的日志记录 - 记录一个静态字符串,没有任何上下文或printf风格的模板:
根据Uber-go Zap的文档,它的性能比类似的结构化日志包更好——也比标准库更快。 以下是Zap发布的基准测试信息记录一条消息和10个字段所消耗的时间对比:
1.3 zap的安装
go get即可
go get -u go.uber.org/zap
1.4 创建实例-两种类型
Zap提供了两种类型的日志记录器 SugaredLogger
和 Logger
。
在性能很好但不是很关键的上下文中,使用 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}
三种创建方式对比:
Example
和 Production
使用的是json格式输出,Development
使用行的形式输出, 其中值得关注的就Production,Development创建的Logger
NewDevelopment 是以 空格分开
的形式展示
NewProduction 使用的是 json格式
,键值对的形式
展示出来
Development
-
从警告级别向上打印到堆栈中来跟踪
-
始终打印包/文件/行(方法)
-
在行尾添加任何额外字段作为json字符串
-
以大写形式打印级别名称
-
以毫秒为单位打印ISO8601格式的时间戳
Production
-
调试级别消息不记录
-
Error,Dpanic级别的记录,会在堆栈中跟踪文件,Warn不会
-
始终将调用者添加到文件中
-
以时间戳格式打印日期
-
以小写形式打印级别名称
1.4.2 SugaredLogger
它们惟一的区别是,我们可以通过调用主logger的. Sugar()
方法来获取一个SugaredLogger
,然后使用SugaredLogger
以printf
格式记录语句,例如
var sugarLogger *zap.SugaredLogger
func 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 {
LevelEnabler
enc Encoder
out 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 main
import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"os"
)
var logger *zap.Logger
var SugaredLogger *zap.SugaredLogger
func 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 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()
//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 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()
//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, // megabytes
MaxBackups: 100,
MaxAge: 28, // days
Compress: false, //Compress确定是否应该使用gzip压缩已旋转的日志文件。默认值是不执行压缩。
}
return zapcore.AddSync(lumberWriteSyncer)
}
lumberjack.Logger采用以下属性作为输入:
属性 | 含义 |
---|---|
Filename | 日志文件的位置,也就是路径 |
MaxSize | 在进行切割之前,日志文件的最大大小(以MB为单位) |
MaxBackups | 保留旧文件的最大个数 |
MaxAges | 保留旧文件的最大天数 |
Compress | 是否压缩/归档旧文件 |
完整代码如下:
package main
import (
"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.SugaredLogger
func 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, // megabytes
MaxBackups: 100,
MaxAge: 28, // days
Compress: false, //Compress确定是否应该使用gzip压缩已旋转的日志文件。默认值是不执行压缩。
}
return zapcore.AddSync(lumberWriteSyncer)
}
func main() {
InitLogger()
defer logger.Sync()
simpleHttpGet("www.百度.com")
simpleHttpGet("http://www.百度.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 按级别(归档)写入文件
为了管理人员的查询方便,一般我们需要将低于error级别的放到info.log,error及以上严重级别日志存放到error.log文件中,我们只需要改造一下zapcore.NewCore
方法的第3个参数,然后将文件WriteSyncer
拆成info
和error
两个即可,示例:
package main
import (
"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.百度.com")
simpleHttpGet("http://www.百度.com")
}
var logger *zap.Logger
var SugaredLogger *zap.SugaredLogger
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()
}
}
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()
}
// 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, // megabytes
MaxBackups: 100,
MaxAge: 28, // days
Compress: false, //Compress确定是否应该使用gzip压缩已旋转的日志文件。默认值是不执行压缩。
}
return zapcore.AddSync(infoLumberIO)
}
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)
}
这样修改之后,info
和debug
级别的日志就存放到info.log
,error
级别的日志单独放到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 main
import (
"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.百度.com")
simpleHttpGet("http://www.百度.com")
}
var logger *zap.Logger
var SugaredLogger *zap.SugaredLogger
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()
}
}
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.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, // megabytes
MaxBackups: 100,
MaxAge: 28, // days
Compress: false, //Compress确定是否应该使用gzip压缩已旋转的日志文件。默认值是不执行压缩。
}
return zapcore.AddSync(infoLumberIO)
}
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)
}
1.14 完整代码:移动自定义日志到自定义包中,并封装
zaplogger/logger.go
package zaplogger
import (
"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.SugaredLogger
func 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文件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()
}
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 string
switch 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, // megabytes
MaxBackups: 100,
MaxAge: 28, // days
Compress: false, //Compress确定是否应该使用gzip压缩已旋转的日志文件。默认值是不执行压缩。
}
return zapcore.AddSync(infoLumberIO)
}
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)
}
// 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 main
import (
"go.uber.org/zap"
"goland_prj_test/server/zaplog/zaplogger"
"net/http"
)
func main() {
zaplogger.InitLogger()
defer zaplogger.Logger.Sync()
simpleHttpGet("www.百度.com")
simpleHttpGet("http://www.百度.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.百度.com","error":"Get \"www.百度.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.百度.com <nil>}, zapcore.Field={error 26 0 Get \"www.百度.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.百度.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.百度.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----)"}
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanhfibhea
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
excel下划线不显示怎么办
PHP中文网 06-23 -
怎样阻止微信小程序自动打开
PHP中文网 06-13 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
photoshop蒙版画笔没反应怎么办
PHP中文网 06-24