• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

go zap日志库的使用,以和封装。

武飞扬头像
我在窗野望江湖
帮助1

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.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 按级别(归档)写入文件

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

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

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

学新通

这样修改之后,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 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
系列文章
更多 icon
同类精品
更多 icon
继续加载