Logger 介绍

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

INFO,DEBUG,ERROR

默认的 Go Logger

Uber-go
https://golang.org/pkg/log/ 

实现 Go Logger

实现一个Go语言中的日志记录器非常简单——创建一个新的日志文件,然后设置它为日志的输出位置。

package main

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

func main() {
	//创建输出日志文件
	logFile, err := os.Create("./" + time.Now().Format("20060102") + ".txt")
	if err != nil {
		fmt.Println(err)
	}
	//创建一个Logger
	//参数1:日志写入目的地
	//参数2:每条日志的前缀
	//参数3:日志属性
	loger := log.New(logFile, "test_", log.Ldate|log.Ltime|log.Lshortfile)
	//Flags返回Logger的输出选项
	fmt.Println(loger.Flags())

	//SetFlags设置输出选项
	loger.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)

	//返回输出前缀
	fmt.Println("fix1_" + loger.Prefix())

	//设置输出前缀
	loger.SetPrefix("test_")

	//输出一条日志
	loger.Output(2, "打印一条日志信息")

	//格式化输出日志
	// loger.Printf("第%d行 内容:%s", 11, "我是错误")

	//等价于print();os.Exit(1);
	// loger.Fatal("我是错误")

	//等价于print();panic();
	// loger.Panic("我是错误33333333")

	//log的导出函数
	//导出函数基于std,std是标准错误输出
	//var std = New(os.Stderr, "", LstdFlags)

	//获取输出项
	fmt.Println(log.Flags())
	//获取前缀
	fmt.Printf("fix2_" + log.Prefix())
}

在这里插入图片描述

Go Logger 的优势和劣势

优势

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

io.Writer

劣势

INFO/DEBUG
Zap Logger

1. Uber-go Zap

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

Zap:https://github.com/uber-go/zap

2. 为什么选择 Uber-go zap

printf

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

以下是 Zap 发布的基准测试信息记录一条消息和10个字段:

printf

3. 安装

运行下面的命令安装 zap。

PS E:\go_test>  go get -u go.uber.org/zap
go: downloading go.uber.org/zap v1.24.0
go: downloading go.uber.org/atomic v1.7.0
go: downloading go.uber.org/multierr v1.6.0
go: downloading go.uber.org/atomic v1.10.0
go: downloading go.uber.org/multierr v1.9.0
go: added go.uber.org/atomic v1.10.0
go: added go.uber.org/multierr v1.9.0
go: added go.uber.org/zap v1.24.0
PS E:\go_test>

4. 配置 Zap Logger

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

  • Sugared Logger
  • Logger

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

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

5. Logger

zap.NewProduction()/zap.NewDevelopment()zap.Example()

上面的每一个函数都将创建一个 logger。

唯一的区别在于它将记录的信息不同。例如 production logger 默认记录调用函数信息、日期和时间等。

Info/Error

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

package main

import (
	"net/http"

	"go.uber.org/zap"
)

var logger *zap.Logger

func main() {
	InitLogger()
	defer logger.Sync()
	simpleHttpGet("www.baidu.com")
	simpleHttpGet("https://www.runoob.com")
}

func InitLogger() {
	logger, _ = zap.NewProduction()
}

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()
	}
}
Info/ Error

日志记录器方法的语法是这样的:

func (log *Logger) MethodXXX(msg string, fields ...Field)
Info / Error/ Debug / Panic

每个 zapcore.Field 其实就是一组键值对参数。

我们执行上面的代码会得到如下输出结果:

{
  "level": "error",
  "ts": 1673101310.2563064,
  "caller": "go_test/main.go:25",
  "msg": "Error fetching url..",
  "url": "www.baidu.com",
  "error": "Get \"www.baidu.com\": unsupported protocol scheme \"\"",
  "stacktrace": "main.simpleHttpGet\n\tE:/go_test/main.go:25\nmain.main\n\tE:/go_test/main.go:14\nruntime.main\n\tC:/Program Files/Go/src/runtime/proc.go:250"
}
{
  "level": "info",
  "ts": 1673101310.3977945,
  "caller": "go_test/main.go:30",
  "msg": "Success..",
  "statusCode": "200 OK",
  "url": "https://www.runoob.com"
}

6. Sugared Logger

现在让我们使用 Sugared Logger 来实现相同的功能。

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

下面是修改过后使用 SugaredLogger 代替Logger的代码:

package main

import (
	"net/http"

	"go.uber.org/zap"
)

var sugarLogger *zap.SugaredLogger

func main() {
	InitLogger()
	defer sugarLogger.Sync()
	simpleHttpGet("https://www.baidu.com")
	simpleHttpGet("https://www.csdn.net")
}

func InitLogger() {
	logger, _ := zap.NewProduction()
	sugarLogger = logger.Sugar()
}

func simpleHttpGet(url string) {
	sugarLogger.Debugf("Trying to hit GET request for %s", url)
	resp, err := http.Get(url)
	if err != nil {
		sugarLogger.Errorf("Error fetching URL %s : Error = %s", url, err)
	} else {
		sugarLogger.Infof("Success! statusCode = %s for URL %s", resp.Status, url)
		resp.Body.Close()
	}
}
{
  "level": "info",
  "ts": 1673101999.8570902,
  "caller": "go_test/main.go:29",
  "msg": "Success! statusCode = 200 OK for URL https://www.baidu.com"
}
{
  "level": "info",
  "ts": 1673101999.9190702,
  "caller": "go_test/main.go:29",
  "msg": "Success! statusCode = 200 OK for URL https://www.csdn.net"
}

你应该注意到的了,到目前为止这两个logger都打印输出JSON结构格式。

定制 logger

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

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

func New(core zapcore.Core, options ...Option) *Logger

zapcore.Core 需要三个配置——Encoder,WriteSyncer,LogLevel。

1、Encoder:编码器(如何写入日志)。

NewJSONEncoder()ProductionEncoderConfig()

2、WriterSyncer :指定日志将写到哪里去。

zapcore.AddSync()
file, _ := os.Create("./test.log")
writeSyncer := zapcore.AddSync(file)

3、Log Level:哪种级别的日志将被写入。

InitLogger()
main()/SimpleHttpGet()
package main

import (
	"net/http"
	"os"

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

var sugarLogger *zap.SugaredLogger

func main() {
	InitLogger()
	defer sugarLogger.Sync()
	simpleHttpGet("https://www.baidu.com")
	simpleHttpGet("https://c.runoob.com")
}

func InitLogger() {
	writeSyncer := getLogWriter()
	encoder := getEncoder()
	core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)

	logger := zap.New(core)
	sugarLogger = logger.Sugar()
}

func getEncoder() zapcore.Encoder {
	return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
}

func getLogWriter() zapcore.WriteSyncer {
	//如果想要追加写入可以查看我的博客文件操作那一章
	file, _ := os.Create("./test.log")
	return zapcore.AddSync(file)
}

func simpleHttpGet(url string) {
	sugarLogger.Debugf("Trying to hit GET request for %s", url)
	resp, err := http.Get(url)
	if err != nil {
		sugarLogger.Errorf("Error fetching URL %s : Error = %s", url, err)
	} else {
		sugarLogger.Infof("Success! statusCode = %s for URL %s", resp.Status, url)
		resp.Body.Close()
	}
}

当使用这些修改过的 logger 配置调用上述部分的 main() 函数时,以下输出将打印在文件 test.log 中。

{
  "level": "debug",
  "ts": 1673102827.8652663,
  "msg": "Trying to hit GET request for https://www.baidu.com"
}
{
  "level": "info",
  "ts": 1673102827.9873803,
  "msg": "Success! statusCode = 200 OK for URL https://www.baidu.com"
}
{
  "level": "debug",
  "ts": 1673102827.9873803,
  "msg": "Trying to hit GET request for https://c.runoob.com"
}
{
  "level": "info",
  "ts": 1673102828.0684626,
  "msg": "Success! statusCode = 200 OK for URL https://c.runoob.com"
}

将 JSON Encoder 更改为普通的 Log Encoder

现在,我们希望将编码器从 JSON Encoder 更改为普通 Encoder。

为此,我们需要将 NewJSONEncoder() 更改为 NewConsoleEncoder()。

return zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())

当使用这些修改过的 logger 配置调用上述部分的 main() 函数时,以下输出将打印在文件——test.log中。

func getEncoder() zapcore.Encoder {
	// return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
	return zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())
}

在这里插入图片描述

更改时间编码并添加调用者详细信息

鉴于我们对配置所做的更改,有下面两个问题:

  • 时间是以非人类可读的方式展示,例如 1.572161051846623e+09。
  • 调用方函数的详细信息没有显示在日志中 我们要做的第一件事是覆盖默认的ProductionConfig(),并进行以下更改。

修改时间编码器

在日志文件中使用大写字母记录日志级别。

func getEncoder() zapcore.Encoder {
    encoderConfig := zap.NewProductionEncoderConfig()
    encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
    encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
    return zapcore.NewConsoleEncoder(encoderConfig)
}

接下来,我们将修改 zap logger 代码,添加将调用函数信息记录到日志中的功能。

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

完整源码

package main

import (
	"net/http"
	"os"

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

var sugarLogger *zap.SugaredLogger

func main() {
	InitLogger()
	defer sugarLogger.Sync()
	simpleHttpGet("https://www.baidu.com")
	simpleHttpGet("https://c.runoob.com")
}

func InitLogger() {
	writeSyncer := getLogWriter()
	encoder := getEncoder()
	core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)

	logger := zap.New(core)
	sugarLogger = logger.Sugar()
}

// func getEncoder() zapcore.Encoder {
// return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
// return zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())
// }

func getEncoder() zapcore.Encoder {
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
	return zapcore.NewConsoleEncoder(encoderConfig)
}

func getLogWriter() zapcore.WriteSyncer {
	//如果想要追加写入可以查看我的博客文件操作那一章
	file, _ := os.Create("./test.log")
	return zapcore.AddSync(file)
}

func simpleHttpGet(url string) {
	sugarLogger.Debugf("Trying to hit GET request for %s", url)
	resp, err := http.Get(url)
	if err != nil {
		sugarLogger.Errorf("Error fetching URL %s : Error = %s", url, err)
	} else {
		sugarLogger.Infof("Success! statusCode = %s for URL %s", resp.Status, url)
		resp.Body.Close()
	}
}

在这里插入图片描述

日志切割归档

1. 使用 Lumberjack 进行日志切割归档

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

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

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

Lumberjack
https://github.com/natefinch/lumberjack

2. 安装

执行下面的命令安装 Lumberjack。

PS E:\go_test> go get -u github.com/natefinch/lumberjack
go: downloading github.com/natefinch/lumberjack v2.0.0+incompatible
go: added github.com/natefinch/lumberjack v2.0.0+incompatible
PS E:\go_test>

3. zap logger 中加入 Lumberjack

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

func getLogWriter() zapcore.WriteSyncer {
    lumberJackLogger := &lumberjack.Logger{
        Filename:   "./test.log",
        MaxSize:    10,
        MaxBackups: 5,
        MaxAge:     30,
        Compress:   false,
    }
    return zapcore.AddSync(lumberJackLogger)
}

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

  • Filename: 日志文件的位置
  • MaxSize:在进行切割之前,日志文件的最大大小(以MB为单位)
  • MaxBackups:保留旧文件的最大个数
  • MaxAges:保留旧文件的最大天数
  • Compress:是否压缩/归档旧文件
Zap/Lumberjack logger
package main

import (
	"net/http"

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

var sugarLogger *zap.SugaredLogger

func main() {
	InitLogger()
	defer sugarLogger.Sync()
	simpleHttpGet("https://www.baidu.com")
	simpleHttpGet("https://c.runoob.com")
}

func InitLogger() {
	writeSyncer := getLogWriter()
	encoder := getEncoder()
	core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)

	logger := zap.New(core)
	sugarLogger = logger.Sugar()
}

// func getEncoder() zapcore.Encoder {
// return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
// return zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())
// }

func getEncoder() zapcore.Encoder {
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
	return zapcore.NewConsoleEncoder(encoderConfig)
}

// func getLogWriter() zapcore.WriteSyncer {
//如果想要追加写入可以查看我的博客文件操作那一章
// file, _ := os.Create("./test.log")
// return zapcore.AddSync(file)
// }

func getLogWriter() zapcore.WriteSyncer {
	lumberJackLogger := &lumberjack.Logger{
		Filename:   "./test.log",
		MaxSize:    1,
		MaxBackups: 5,
		MaxAge:     30,
		Compress:   false,
	}
	return zapcore.AddSync(lumberJackLogger)
}

func simpleHttpGet(url string) {
	sugarLogger.Debugf("Trying to hit GET request for %s", url)
	resp, err := http.Get(url)
	if err != nil {
		sugarLogger.Errorf("Error fetching URL %s : Error = %s", url, err)
	} else {
		sugarLogger.Infof("Success! statusCode = %s for URL %s", resp.Status, url)
		resp.Body.Close()
	}
}

在这里插入图片描述