阅读目录

Logger 介绍默认的 Go LoggerGo Logger 的优势和劣势

Zap Logger1. Uber-go Zap2. 为什么选择 Uber-go zap3. 安装4. 配置 Zap Logger5. Logger6. Sugared Logger定制 logger将 JSON Encoder 更改为普通的 Log Encoder更改时间编码并添加调用者详细信息修改时间编码器完整源码

日志切割归档1. 使用 Lumberjack 进行日志切割归档2. 安装3. zap logger 中加入 Lumberjack

Logger 介绍

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

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

默认的 Go Logger

在介绍 Uber-go 的 zap 包之前,让我们先看看Go语言提供的基本日志功能。

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 作为日志记录输出并向其发送要写入的日志。

劣势

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

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 LoggerLogger

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

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

5. Logger

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

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

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

通过 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()

}

}

在上面的代码中,我们首先创建了一个Logger,然后使用 Info/ Error 等Logger方法记录消息。

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

func (log *Logger) MethodXXX(msg string, fields ...Field)

其中 MethodXXX 是一个可变参数函数,可以是 Info / Error/ Debug / Panic 等。每个方法都接受一个消息字符串和任意数量的zapcore.Field 场参数。

每个 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:哪种级别的日志将被写入。

我们将修改上述部分中的 Logger 代码,并重写 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(..) 函数中添加一个 Option。

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

}

}

好文阅读

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: