日志

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

  • 能够将事件记录到文件中,而不是应用程序控制台。
  • 日志切割-能够根据文件大小、时间或间隔等来切割日志文件。
  • 支持不同的日志级别。例如INFO,DEBUG,ERROR等。
  • 能够打印基本信息,如调用文件/函数名和行号,日志时间等。

系统自带的log模块

自带log库的优势和劣势

优势

它最大的优点是使用非常简单。

我们可以设置任何io.Writer作为日志记录输出并向其发送要写入的日志。

劣势

  • 仅限基本的日志级别
    • 只有一个Print选项。不支持INFO/DEBUG等多个级别。
  • 对于错误日志,它有Fatal和Panic
    • Fatal日志通过调用os.Exit(1)来结束程序
    • Panic日志在写入日志消息之后抛出一个panic
    • 但是它缺少一个ERROR日志级别,这个级别可以在不抛出panic或退出程序的情况下记录错误
  • 缺乏日志格式化的能力——例如记录调用者的函数名和行号,格式化日期和时间格式。等等。
  • 不提供日志切割的能力。

默认的log输出到控制台。

log.Printf("%d+%d=%d\n", 3, 4, 3+4)
log.Println("Hello Golang")
log.Fatalln("Bye, the world") //日志输出后会执行os.Exit(1)

指定日志输出到文件

格式

log.New(out io.Writer, prefix string, flag int) *Logger

  • out 是打开的文件,用os.OpenFile()打开的一个可写文件
  • prefix 每条日志的前缀
  • flag 设置日志的格式
    • log.Ldate 日期格式的 2009/01/23
    • log.Lmicroseconds 精确到微秒的时间 01:23:23.123123
    • log.Ltime 精确到秒 01:23:23
    • log.Llongfile 完整文件名和行号 /a/b/c/d.go:23
    • log.Lshortfile 文件名和行号 d.go:23
    • log.LUTC 如果设置了Ldate或Ltime,则使用UTC而不是本地时区
    • log.Lmsgprefix 将“前缀”从行首移到消息之前
    • log.LstdFlags 相当于 Ldate | Ltime
logWriter := log.New(fout, "[BIZ_PREFIX]", log.Ldate|log.Lmicroseconds) //通过flag参数定义日志的格式
logWriter.Println("Hello Golang")

例子

package main

import (
	"fmt"
	"log"
	"os"
	"time"
)

var file, err = os.OpenFile("1.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755)

func logs() {
	logger := log.New(file, "[abc]", log.Ldate|log.Lmicroseconds)
	logger.Println("第一行")
	logger.Println("第二行")
	logger.Println("第三行")
}

//关闭文件
func onexit() {
	if file != nil {
		fmt.Println("退出了")
		file.Close()
	}
}
func main() {
	defer func() {
        //异常退出的时候关闭文件
		if err := recover(); err != nil {
			onexit()
		}
	}()
	n := 0
    //循环打印日志
	for i := 0; i < 10; i++ {
		logs()
		time.Sleep(3 * time.Second)
        //模拟故障
		if i == 5 {
			_ = 1 / n
		}
	}
    //正常退出的时候关闭文件
	onexit()
}

第三方日志库zap

zap是非常快的、结构化的,分日志级别的Go日志库。

为什么选择Uber-go zap

  • 它同时提供了结构化日志记录和printf风格的日志记录
  • 它非常的快

根据Uber-go Zap的文档,它的性能比类似的结构化日志包更好——也比标准库更快。

以下是Zap发布的基准测试信息

记录一条消息和10个字段:

Package Time Time % to zap Objects Allocated
⚡️ zap 862 ns/op +0% 5 allocs/op
⚡️ zap (sugared) 1250 ns/op +45% 11 allocs/op
zerolog 4021 ns/op +366% 76 allocs/op
go-kit 4542 ns/op +427% 105 allocs/op
apex/log 26785 ns/op +3007% 115 allocs/op
logrus 29501 ns/op +3322% 125 allocs/op
log15 29906 ns/op +3369% 122 allocs/op

记录一个静态字符串,没有任何上下文或printf风格的模板:

Package Time Time % to zap Objects Allocated
⚡️ zap 118 ns/op +0% 0 allocs/op
⚡️ zap (sugared) 191 ns/op +62% 2 allocs/op
zerolog 93 ns/op -21% 0 allocs/op
go-kit 280 ns/op +137% 11 allocs/op
standard library 499 ns/op +323% 2 allocs/op
apex/log 1990 ns/op +1586% 10 allocs/op
logrus 3129 ns/op +2552% 24 allocs/op
log15 3887 ns/op +3194% 23 allocs/op

安装

$ go get -u go.uber.org/zap

记录器

Zap提供了两种类型的日志记录器—Sugared Logger和Logger。

  • SugaredLogger

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

  • Logger

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

Logger

  • 通过调用zap.NewProduction()/zap.NewDevelopment()或者zap.Example()创建一个Logger。
  • 上面的每一个函数都将创建一个logger。唯一的区别在于它将记录的信息不同。例如production logger默认记录调用函数信息、日期和时间等。
  • 通过Logger调用Info/Error等。
  • 默认情况下日志都会打印到应用程序的console界面。

例子

package main

import (
	"errors"
	"fmt"

	"go.uber.org/zap"
)

func main() {
	logger, err := zap.NewProduction()//初始化一个生产环境的logger
	if err != nil {
		fmt.Println(err)
		return
	}
	defer logger.Sync()
	logger.Error("error这条", zap.Error(errors.New("错误信息")), zap.String("abc", "这是abc的value"))
	logger.Info("info这条", zap.Error(errors.New("错误信息")), zap.String("abc", "这是abc的value"))
}
/*
{"level":"error","ts":1667451149.3754392,"caller":"week13/1.go:17","msg":"error这条","error":"错误信息","abc":"这是abc的value","stacktrace":"main.main\n\tE:/Codes/GO/week13/1.go:17\nruntime.main\n\tD:/GO/Go/src/runtime/proc.go:250"}
{"level":"info","ts":1667451149.3761096,"caller":"week13/1.go:18","msg":"info这条","error":"错误信息","abc":"这是abc的value"}
*/

可以看到:

生产环境下的logger默认的日志输出格式是json

时间是时间戳


再来个开发环境的

使用zap.NewDevelopment()生成logger

package main

import (
	"errors"
	"fmt"

	"go.uber.org/zap"
)

func main() {
	logger, err := zap.NewDevelopment()//初始化一个开发环境的logger
	if err != nil {
		fmt.Println(err)
		return
	}
	defer logger.Sync()
	logger.Error("error这条", zap.Error(errors.New("错误信息")), zap.String("abc", "这是abc的value"))
	logger.Info("info这条", zap.Error(errors.New("错误信息")), zap.String("abc", "这是abc的value"))
}
/*
2022-11-03T14:06:07.225+0800    ERROR   week13/1.go:43  error这条       {"error": "错误信息", "abc": "这是abc的value"}
main.main
        E:/Codes/GO/week13/1.go:43
runtime.main
        D:/GO/Go/src/runtime/proc.go:250
2022-11-03T14:06:07.233+0800    INFO    week13/1.go:44  info这条        {"error": "错误信息", "abc": "这是abc的value"}
*/

可以看到:

日志的输出格式是以Console格式输出的

时间是方便阅读的格式


通过zap.Error(errors.New("错误信息"))类型,可以给日志信息添加字段,都是以key-value的形式存储

还有其他类型

zap.Int()
zap.Duration()
zap.String()
zap.Error()

SugaredLogger

使用Sugared Logger来实现相同的功能。

  • 大部分的实现基本都相同。
  • 惟一的区别是,我们通过调用主logger的. Sugar()方法来获取一个SugaredLogger
  • 然后使用SugaredLoggerprintf格式记录语句

例子

package main

import (
	"errors"
	"fmt"

	"go.uber.org/zap"
)

func main() {
	logger, err := zap.NewProduction() //初始化一个生产环境的logger
	if err != nil {
		fmt.Println(err)
		return
	}
	sugarLogger := logger.Sugar()
	defer sugarLogger.Sync()
	info1 := "err信息"
	sugarLogger.Errorf("这里是 %s", info1)
	info2 := "info信息"
	sugarLogger.Infof("这里是 %s", info2)
}
/*
{"level":"error","ts":1667456422.2656977,"caller":"week13/1.go:44","msg":"这里是 err信息","stacktrace":"main.main\n\tE:/Codes/GO/week13/1.go:44\nruntime.main\n\tD:/GO/Go/src/runtime/proc.go:250"}
{"level":"info","ts":1667456422.2656977,"caller":"week13/1.go:46","msg":"这里是 info信息"}
*/

定制logger

将日志写入文件而不是终端

我们要做的第一个更改是把日志写入文件,而不是打印到应用程序控制台。

  • 我们将使用zap.New(…)方法来手动传递所有配置,而不是使用像zap.NewProduction()这样的预置方法来创建logger。
func New(core zapcore.Core, options ...Option) *Logger

zapcore.Core需要三个配置——EncoderWriteSyncerLogLevel

Encoder

编码器(如何写入日志)。

有两种开箱即用的编码器

  • Console格式 func NewConsoleEncoder(cfg EncoderConfig) Encoder
  • Json格式 func NewJSONEncoder(cfg EncoderConfig) Encoder

Console格式

类似

2022-11-03T14:04:37.330+0800    INFO    week13/1.go:44  info这条        {"error": "错误信息", "abc": "这是abc的value"}

两种使用方法

1、使用预制的配置ProductionEncoderConfig()或者DevelopmentEncoderConfig()

使用方法 这里以

encoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())
//或者
encoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())
//带入到core
core := zapcore.NewCore(encoder, writer, zap.InfoLevel)

两个的区别

#ProductionEncoderConfig()的日志格式
1.6674517658521087e+09	error	error这条	{"error": "错误信息", "abc": "这是abc的value"}

#DevelopmentEncoderConfig()的日志格式
2022-11-03T13:03:40.884+0800	ERROR	error这条	{"error": "错误信息", "abc": "这是abc的value"}

2、自定义时间格式以及在日志文件中使用大写字母记录日志级别

encoderConfig := zap.NewProductionEncoderConfig()//创建一个编码器配置
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder //设置时间格式  这种2022-11-03T13:47:57.782+0800
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder //设置大写字符的日志级别  这种ERROR
// encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder  //设置带颜色的日志级别 建议控制台打印的时候使用,写入文本不建议使用,很影响读取 
encoderConfig.EncodeCaller = zapcore.FullCallerEncoder   //完整路径的显示日志打印的位置  例如E:/Codes/GO/week13/1.go:33
//encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder  //短路径显示  例如 week13/1.go:33
encoder := zapcore.NewConsoleEncoder(encoderConfig)       //用配置创建一个Console格式的编码器
2022-11-03T13:47:57.458+0800	ERROR	E:/Codes/GO/week13/1.go:33	error这条	{"error": "错误信息", "abc": "这是abc的value"}
2022-11-03T13:47:57.458+0800	INFO	E:/Codes/GO/week13/1.go:34	info这条	{"error": "错误信息", "abc": "这是abc的value"}
2022-11-03T13:47:57.458+0800	DEBUG	E:/Codes/GO/week13/1.go:35	debug这条

Json格式

json格式的与Console格式的使用相同,只是记录的日志格式不同

只需要把zapcore.NewConsoleEncoder()更换成zapcore.NewJSONEncoder()

用预制配置

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

自定义配置

encoderConfig := zap.NewProductionEncoderConfig()//创建一个编码器配置
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder //设置时间格式  这种2022-11-03T13:47:57.782+0800
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder //设置大写字符的日志级别  这种ERROR
// encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder  //设置带颜色的日志级别 建议控制台打印的时候使用,写入文本不建议使用,很影响读取 
encoderConfig.EncodeCaller = zapcore.FullCallerEncoder   //完整路径的显示日志打印的位置  例如E:/Codes/GO/week13/1.go:33
//encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder  //短路径显示  例如 week13/1.go:33
encoder := zapcore.NewJSONEncoder(encoderConfig)       //用配置创建一个Json格式的编码器
{"level":"ERROR","ts":"2022-11-03T14:44:10.133+0800","caller":"E:/Codes/GO/week13/1.go:34","msg":"error这条","error":"错误信息","abc":"这是abc的value"}
{"level":"INFO","ts":"2022-11-03T14:44:10.133+0800","caller":"E:/Codes/GO/week13/1.go:35","msg":"info这条","error":"错误信息","abc":"这是abc的value"}
{"level":"DEBUG","ts":"2022-11-03T14:44:10.133+0800","caller":"E:/Codes/GO/week13/1.go:36","msg":"debug这条"}

WriterSyncer

指定日志将写到哪里去。

单个输出

直接使用zapcore.AddSync()函数并且将打开的文件句柄传进去

//现创建文件
file, _ := os.Create("1.txt")
//打开已有文件
file, _ := os.OpenFile("1.txt", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0755)
//控制台输出
file := os.Stdout

//新建writer
writer := zapcore.AddSync(file)
//带入创建core
core := zapcore.NewCore(encoder, writer, zap.InfoLevel)

多个输出

使用zapcore.NewMultiWriteSyncer()函数将多个writer包装到一起,就可以同时写道多个地方

//写入一个文件
file1, _ := os.Create("1.txt")
//写入另一个文件
file2, _ := os.OpenFile("2.txt", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0755)
//同时控制台输出
file3 := os.Stdout

//新建writer
writer1 := zapcore.AddSync(file1)
writer2 := zapcore.AddSync(file2)
writer3 := zapcore.AddSync(file3)
//将多个writer传入
core := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(writer1, writer2, writer3), zap.InfoLevel)

以上同时在两个文件中输出还在控制台输出

LogLevel

哪种级别的日志将被写入

日志级别的实质就是数字常量

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
)
const (
	// DebugLevel logs are typically voluminous, and are usually disabled in
	// production.
	DebugLevel = zapcore.DebugLevel
	// InfoLevel is the default logging priority.
	InfoLevel = zapcore.InfoLevel
	// WarnLevel logs are more important than Info, but don't need individual
	// human review.
	WarnLevel = zapcore.WarnLevel
	// ErrorLevel logs are high-priority. If an application is running smoothly,
	// it shouldn't generate any error-level logs.
	ErrorLevel = zapcore.ErrorLevel
	// DPanicLevel logs are particularly important errors. In development the
	// logger panics after writing the message.
	DPanicLevel = zapcore.DPanicLevel
	// PanicLevel logs a message, then panics.
	PanicLevel = zapcore.PanicLevel
	// FatalLevel logs a message, then calls os.Exit(1).
	FatalLevel = zapcore.FatalLevel
)

日志的级别

最低的是DebugLevel 是-1

最高的是FatalLevel 是 6

从上到下依次递减

DebugLevel
InfoLevel
WarnLevel
ErrorLevel
DPanicLeve
PanicLevel
FatalLevel`

两种处理方式

1、设置下限

core := zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), zap.WarnLevel)

只有大于等于warn级别的日志会被记录

2、设置区间

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

core := zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), highPriority)
//或者
core := zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), lowPriority)

以上是将不同界别的区别出来

应用场景 将普通信息日志与错误日志分开记录

多个core

当需要将不同日志分开写的时候,就需要多个core配合使用

使用zapcore.NewTee()方法

例子

file, _ := os.OpenFile("2.txt", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0755)
//创建多个core
infoFileCore := zapcore.NewCore(encoder, zapcore.AddSync(file), zap.ErrorLevel)
errorFileCore := zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), zap.DebugLevel)

//将infocore 和 errcore 加入core切片
var core []zapcore.Core
core = append(core, infoFileCore)
core = append(core, errorFileCore)

logger := zap.New(zapcore.NewTee(core...))

添加其他配置

添加记录日志的文件以及行

logger := zap.New(core, zap.AddCaller())

如果没添加zap.AddCaller()

2022-11-03T13:08:33.669+0800	ERROR	error这条	{"error": "错误信息", "abc": "这是abc的value"}

添加了zap.AddCaller()

2022-11-03T13:08:33.669+0800    ERROR   week13/1.go:65  error这条       {"error": "错误信息", "abc": "这是abc的value"}

使用Lumberjack进行日志切割归档

这个日志程序中唯一缺少的就是日志切割归档功能。

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

为了添加日志切割归档功能,我们将使用第三方库Lumberjack来实现。

安装

执行下面的命令安装Lumberjack

$ go get -u github.com/natefinch/lumberjack

zap logger中加入Lumberjack

infoLumberIO := &lumberjack.Logger{
	Filename:   "./server/info.log",
	MaxSize:    10, // megabytes
	MaxBackups: 10,
	MaxAge:     28,    // days
	Compress:   false, //Compress确定是否应该使用gzip压缩已旋转的日志文件。默认值是不执行压缩。
}
writer := zapcore.AddSync(infoLumberIO)

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

  • Filename: 日志文件的位置 如果文件目录不存在,会自动创建
  • MaxSize:最大文件大小,日志文件达到多少的时候切割(以MB为单位)
  • MaxBackups:保留旧文件的最大个数
  • MaxAges:保留旧文件的最大天数
  • Compress:是否压缩/归档旧文件

存档的状态

例子

简单通用的logger

package main

import (
	"errors"
	"os"

	"github.com/natefinch/lumberjack"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

func main() {
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
	encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder
	encoder := zapcore.NewConsoleEncoder(encoderConfig)
	infoLumberIO := &lumberjack.Logger{
		Filename:   "./log/info.log",
		MaxSize:    1, // megabytes
		MaxBackups: 10,
		MaxAge:     28,    // days
		Compress:   false, //Compress确定是否应该使用gzip压缩已旋转的日志文件。默认值是不执行压缩。
	}
	writer := zapcore.AddSync(infoLumberIO)
	core := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(writer, zapcore.AddSync(os.Stdout)), zap.DebugLevel)
	// core := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(writer, zapcore.AddSync(os.Stdout)), lowPriority)
	logger := zap.New(core, zap.AddCaller())
	sugar := logger.Sugar()
	defer sugar.Sync()
	sugar.Errorw("error这条", zap.Error(errors.New("错误信息")), zap.String("abc", "这是abc的value"))
	sugar.Infow("info这条", zap.Error(errors.New("错误信息")), zap.String("abc", "这是abc的value"))
	sugar.Debugw("debug这条")
}

错误日志与普通日志分开的logger

package main

import (
	"errors"
	"os"

	"github.com/natefinch/lumberjack"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

//生成普通信息的Writer
func getInfoWriterSyncer() zapcore.WriteSyncer {
	//引入第三方库 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)
}

//生成故障信息的Writer
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)
}

func main() {
    //创建编码器配置
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
	encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder
    //创建编码器
	encoder := zapcore.NewConsoleEncoder(encoderConfig)
    
    
	//info文件WriteSyncer
	infoFileWriteSyncer := getInfoWriterSyncer()
	//error文件WriteSyncer
	errorFileWriteSyncer := getErrorWriterSyncer()
    
    
	//区分日志级别
	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
	})
    
    //根据不同日志级别创建不用的core
	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 core []zapcore.Core
	core = append(core, infoFileCore)
	core = append(core, errorFileCore)
    
    //用多个core创建logger
	logger := zap.New(zapcore.NewTee(core...), zap.AddCaller()) //zap.AddCaller() 添加将调用函数信息记录到日志中的功能
	sugar := logger.Sugar()
	defer sugar.Sync()
    
    //写入日志
	sugar.Errorw("error这条", zap.Error(errors.New("错误信息")), zap.String("abc", "这是abc的value"))
	sugar.Infow("info这条", zap.Error(errors.New("错误信息")), zap.String("abc", "这是abc的value"))
	sugar.Debugw("debug这条")
}

结果

生成两个文件

error.log中

2022-11-03T15:29:07.419+0800	ERROR	week13/1.go:65	error这条	{"error": "错误信息", "abc": "这是abc的value"}

info.log中

2022-11-03T15:29:07.427+0800	INFO	week13/1.go:66	info这条	{"error": "错误信息", "abc": "这是abc的value"}
2022-11-03T15:29:07.428+0800	DEBUG	week13/1.go:67	debug这条

在控制台中

2022-11-03T15:29:07.419+0800    ERROR   week13/1.go:65  error这条       {"error": "错误信息", "abc": "这是abc的value"}
2022-11-03T15:29:07.427+0800    INFO    week13/1.go:66  info这条        {"error": "错误信息", "abc": "这是abc的value"}
2022-11-03T15:29:07.428+0800    DEBUG   week13/1.go:67  debug这条

日志分类记录文件同时打印有颜色的控制台日志

package common

import (
	"os"

	"github.com/natefinch/lumberjack"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

// 正常日志编码器
func GetFileEnCoder() zapcore.Encoder {
	config := zap.NewProductionEncoderConfig()
	config.EncodeTime = zapcore.ISO8601TimeEncoder
	config.EncodeLevel = zapcore.CapitalLevelEncoder
	config.EncodeDuration = zapcore.NanosDurationEncoder
	config.EncodeCaller = zapcore.ShortCallerEncoder
	config.EncodeName = zapcore.FullNameEncoder
	return zapcore.NewConsoleEncoder(config)
}

// 带颜色的日志编码器
func GetConsoleEnCoder() zapcore.Encoder {
	config := zap.NewProductionEncoderConfig()
	config.EncodeTime = zapcore.ISO8601TimeEncoder
	config.EncodeLevel = zapcore.CapitalColorLevelEncoder
	config.EncodeCaller = zapcore.ShortCallerEncoder
	return zapcore.NewConsoleEncoder(config)
}

// 生成普通信息的Writer
func GetInfoWriterSyncer() zapcore.WriteSyncer {
	//引入第三方库 Lumberjack 加入日志切割功能
	infoLumberIO := &lumberjack.Logger{
		Filename:   "./log/info.log",
		MaxSize:    10, // megabytes
		MaxBackups: 100,
		MaxAge:     28,    // days
		Compress:   false, //Compress确定是否应该使用gzip压缩已旋转的日志文件。默认值是不执行压缩。
	}
	return zapcore.AddSync(infoLumberIO)
}

// 生成故障信息的Writer
func GetErrorWriterSyncer() zapcore.WriteSyncer {
	//引入第三方库 Lumberjack 加入日志切割功能
	lumberWriteSyncer := &lumberjack.Logger{
		Filename:   "./log/error.log",
		MaxSize:    10, // megabytes
		MaxBackups: 100,
		MaxAge:     28,    // days
		Compress:   false, //Compress确定是否应该使用gzip压缩已旋转的日志文件。默认值是不执行压缩。
	}
	return zapcore.AddSync(lumberWriteSyncer)
}

/*
例子:

	logger := Logger()
	defer logger.Sync()
	logger.Debugw("调试信息", zap.String("k1", "v1"), zap.Error(errors.New("这是错误信息")))
	logger.Debugf("调试信息%d", 1111)
	logger.Infow("INFO信息", zap.String("k2", "v2"), zap.Error(errors.New("这是错误信息")))
	logger.Errorw("error信息", zap.String("k3", "v3"), zap.Error(errors.New("这是错误信息")))
*/
func Logger() *zap.SugaredLogger {
	encoderfile := GetFileEnCoder()
	encoderconsole := GetConsoleEnCoder()
	infowriter := GetInfoWriterSyncer()
	errwriter := GetErrorWriterSyncer()

	infolevel := zap.LevelEnablerFunc(func(l zapcore.Level) bool {
		return l < zap.ErrorLevel && l >= zap.DebugLevel
	})
	errlevel := zap.LevelEnablerFunc(func(l zapcore.Level) bool {
		return l >= zap.ErrorLevel
	})
	infocore := zapcore.NewCore(encoderfile, infowriter, infolevel)
	errcore := zapcore.NewCore(encoderfile, errwriter, errlevel)
	consolecore := zapcore.NewCore(encoderconsole, zapcore.AddSync(os.Stdout), zap.DebugLevel)
	core := []zapcore.Core{infocore, errcore, consolecore}
	logger := zap.New(zapcore.NewTee(core...), zap.AddCaller())
	return logger.Sugar()
}

补充

一个更友好的日志

package logger

import (
	"os"

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

// 设置日志编码器
func GetEnCoder() zapcore.Encoder {
	config := zap.NewProductionEncoderConfig()
	//设置日志中时间的格式
	config.EncodeTime = zapcore.ISO8601TimeEncoder
	//设置日志中级别的格式
	config.EncodeLevel = zapcore.CapitalLevelEncoder
	//序列化时间到纳秒
	// config.EncodeDuration = zapcore.NanosDurationEncoder
	//路径显示段路径,也就是相对路径
	config.EncodeCaller = zapcore.ShortCallerEncoder
	//按原样序列化记录器名称
	config.EncodeName = zapcore.FullNameEncoder
	return zapcore.NewConsoleEncoder(config)
}

// 生成普通信息的Writer
func GetInfoWriterSyncer(dir string) zapcore.WriteSyncer {
	//引入第三方库 Lumberjack 加入日志切割功能
	infoLumberIO := &lumberjack.Logger{
		Filename:   dir + "/info.log",
		MaxSize:    10, // megabytes
		MaxBackups: 100,
		MaxAge:     28,    // days
		Compress:   false, //Compress确定是否应该使用gzip压缩已旋转的日志文件。默认值是不执行压缩。
	}
	return zapcore.AddSync(infoLumberIO)
}

// 生成故障信息的Writer
func GetErrorWriterSyncer(dir string) zapcore.WriteSyncer {
	//引入第三方库 Lumberjack 加入日志切割功能
	lumberWriteSyncer := &lumberjack.Logger{
		Filename:   dir + "/error.log",
		MaxSize:    10, // 单位M
		MaxBackups: 100,
		MaxAge:     28,    // 天数
		Compress:   false, //Compress确定是否应该使用gzip压缩已旋转的日志文件。默认值是不执行压缩。
	}
	return zapcore.AddSync(lumberWriteSyncer)
}

type Level int8

const (
	DEBUG Level = iota
	INFO
	WARN
	ERROR
	FATAL
	PANIC
)

var LevelMap = map[Level]string{
	DEBUG: "DEBUG",
	INFO:  "INFO",
	WARN:  "WARN",
	ERROR: "ERROR",
	FATAL: "FATAL",
	PANIC: "PANIC",
}

func (s Level) String() string {
	v, ok := LevelMap[s]
	if ok {
		return `"` + v + `"`
	}
	return ""
}

type Log struct {
	level    Level
	saveFile bool
	dirPath  string
	logger   *zap.SugaredLogger
}

func NewDefaultLog() *Log {
	return &Log{
		level:    DEBUG,
		saveFile: false,
	}
}
func NewSaveFileLog(path string) *Log {
	return &Log{
		level:    DEBUG,
		saveFile: true,
		dirPath:  path,
	}
}

func (g *Log) GetLogger() {
	//如果level写错,则表示为info
	level := map[Level]zapcore.Level{
		DEBUG: zapcore.DebugLevel,
		INFO:  zapcore.InfoLevel,
		WARN:  zapcore.WarnLevel,
		ERROR: zapcore.ErrorLevel,
		FATAL: zapcore.FatalLevel,
		PANIC: zapcore.PanicLevel,
	}
	if g.dirPath == "" {
		g.dirPath = "."
	}
	encoder := GetEnCoder()
	//输出普通日志
	infowriter := GetInfoWriterSyncer(g.dirPath)
	//输出异常日志
	errwriter := GetErrorWriterSyncer(g.dirPath)
	//确定日志级别
	infolevel := zap.LevelEnablerFunc(func(l zapcore.Level) bool {
		if zap.InfoLevel >= level[g.level] {
			return l < zap.ErrorLevel && l >= level[g.level]
		}
		return l < zap.ErrorLevel && l >= zap.DebugLevel
	})
	errlevel := zap.LevelEnablerFunc(func(l zapcore.Level) bool {
		if level[g.level] >= zap.ErrorLevel {
			return l >= level[g.level]
		}
		return l >= zap.ErrorLevel
	})
	infocore := zapcore.NewCore(encoder, infowriter, infolevel)
	errcore := zapcore.NewCore(encoder, errwriter, errlevel)
	consolecore := zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), level[g.level])
	var logger *zap.Logger
	if g.saveFile {
		var core []zapcore.Core
		if level[g.level] >= zap.ErrorLevel {
			core = []zapcore.Core{errcore, consolecore}
		} else {
			core = []zapcore.Core{infocore, errcore, consolecore}
		}
		logger = zap.New(zapcore.NewTee(core...), zap.AddCaller())
	} else {
		logger = zap.New(consolecore, zap.AddCaller())
	}
	g.logger = logger.Sugar()
}

// 获取一个日志
func (g *Log) L() *zap.SugaredLogger {
	if g.logger == nil {
		g.GetLogger()
	}
	return g.logger
}

func (g *Log) SetLevel(level Level) {
	if level <= DEBUG {
		g.level = DEBUG
	} else if level >= PANIC {
		g.level = PANIC
	} else {
		g.level = level
	}
}

func (g *Log) SetSaveFile(save bool) {
	g.saveFile = save
}

func (g *Log) SetDirPath(path string) bool {
	if g.saveFile {
		g.dirPath = path
		return true
	}
	return false
}

测试用例

package logger_test

import (
	"testing"

	"test/common/logger"
)

func TestLoger(t *testing.T) {
	log := logger.NewDefaultLog()
	log.SetLevel(logger.WARN)
	log.SetSaveFile(true)
	log.SetDirPath("logggg")
	log.L().Debugw("调试信息1")
	log.L().Infow("调试信息2")
	log.L().Errorw("调试信息3")
}
posted @ 2022-11-19 15:24  厚礼蝎  阅读(106)  评论(0编辑  收藏  举报