开源日志包-《GO开发知识笔记》

admin 2025-11-04 01:30:21 编程 来源:ZONE.CI 全球网 0 阅读模式
  • 优秀的开源日志包
    • 标准库log包使用
    • glog
    • logrus介绍
    • zap包介绍
    • 其它开源包
    • 开源日志包选择

    我们可以通过修改一些优秀的开源日志包,来实现项目的日志包。Go 生态中有很多优秀的开源日志包,例如标准库 log 包、glog、logrus、zap、seelog、zerolog、log15、apex/log、go-logging 等。其中,用得比较多的是标准库 log 包、glog、logrus 和 zap。

    优秀的开源日志包

    在做项目开发时,我们可以从0编写自己的日志包、也可以使用Go语言标准库的log包,还可以使用开源的日志包,但更多的是基于优秀的开源日志包进行二次开发,来实现定制化的日志功能。Go生态中也有一些非常优秀的开源日志包,例如标准库log包、glog、logrus、zap、seelog、zerolog等。用的比较多的是glog、logrus和zap。本节会简单介绍些这些日志包,并给出简短的使用示例。

    标准库log包使用

    标准库log包功能非常简单,提供了开箱,仅提供了Print、Panic和Fatal三类函数用于日志输出。因为是标准库自带的,所以不需要我们下载安装,使用起来非常方便。标准库log包只有不到400行的代码量,如果读者想研究如何实现一个日志包,阅读标准库log包是一个不错的开始。Go的标准库大量使用了log包,例如:net/http、net/rpc等。log包使用在使用log包时,需要首先创建一个*log. Logger类型的log实例,所有的日志输出都是通过该实例提供的方法来完成的。可以使用log包提供的全局全局变量std,std定义如下(位于Go标准包log目录下的log.go文件中):

    1. var std = New(os.Stderr, "", LstdFlags)

    也也可以使用log.New函数创建自己的logger,在创建时,可以指定输出的位置、每行日志的前缀和日志属性,例如:

    1. logger := log.New(logFile, "[Debug]", log.Lshortfile)

    有如下几种日志属性可供选择:

    • Ldate:当前时区的日期,格式是:2009/01/23。
    • Ltime:当前时区的时间,格式是:01:23:23,精确到秒。
    • Lmicroseconds:当前时区的时间,格式是:01:23:23.862600,精确到微妙。
    • Llongfile:全文件名和行号。
    • Lshortfile:当前文件名和行号,会覆盖Llongfile。
    • LUTC:使用UTC而非本地时区。
    • Lmsgprefix:将“前缀”从行的开头移至消息之前。
    • LstdFlags:标准Logger的默认值(Ldate、Ltime)。

    除了在执行log.New时配置log.Logger之外,创建之后还可以通过log.Logger提供的3种方法来改变log.Logger的配置:

    • SetOutput:指定输出的位置。
    • SetPrefix:设置每行日志的前缀。
    • SetFlags:设置日志属性。

    log.Logger提供了Print、Panic、Fatal函数来记录日志:

    • Print:打印日志,例如:log.Print(“call Print: line1”)
    • Panic:打印日志后执行panic(s),s为日志内容。
    • Fatal:打印日志后执行os.Exit(1)。

    Print、Panic、Fatal函数还提供Println、Printf、Panicln、Panicf、Fatalln、Fatalf来格式化打印日志。Print底层调用fmt.Sprint(v…),Println底层调用fmt.Sprintln(v…),Printf底层调用了fmt.Sprintf(format, v…)。标准库log的使用示例(main.go文件):

    1. package main
    2. import (
    3. "log"
    4. "os"
    5. )
    6. func main() {
    7. // 输出到文件
    8. logFile, err := os.Create("./log.log")
    9. defer logFile.Close()
    10. if err != nil {
    11. log.Fatalln("create file log.log failed")
    12. }
    13. logger := log.New(logFile, "[Debug] ", log.Lshortfile)
    14. logger.SetOutput(os.Stdout)
    15. logger.Print("call Print: line1")
    16. logger.Println("call Println: line2")
    17. // 修改日志配置
    18. logger.SetPrefix("[Info] ")
    19. logger.SetFlags(log.Ldate)
    20. logger.SetOutput(os.Stdout)
    21. logger.Print("Info check stdout")
    22. }

    执行如下命令执行上述程序:

    1. $ go run main.go
    2. [Debug] main.go:17: call Print: line1
    3. [Debug] main.go:18: call Println: line2
    4. [Info] 2020/11/28 Info check stdout

    glog

    glog是Google推出的日志包,跟标准库log包一样,是一个轻量级的日志包,使用简单方便,但要比标准库log包提供更多的功能,glog具有如下特性:

    • 支持4种日志级别:INFO、WARNING、ERROR、FATAL。
    • 支持命令行选项,例如:-alsologtostderr、-log_backtrace_at、-log_dir、-logtostderr、-v等,每个参数实现某种功能。
    • 支持根据文件大小切割日志文件。
    • 支持日志按级别分类输出。
    • 支持V level,V level特性可以使开发者自定义日志级别。
    • 支持vmodule,vmodule可以使开发者对不同的文件使用不同的日志级别。
    • 支持traceLocation,traceLocation可以打印出指定位置的栈信息。

    kubernetes项目就使用了基于glog封装的klog作为其日志库。glog使用方法glog使用非常简单,常见的用法如下。

    1. 基本用法

    glog的最常用的使用方法:

    1. package main
    2. import (
    3. "flag"
    4. "github.com/golang/glog"
    5. )
    6. func main() {
    7. glog.MaxSize = 1024 * 1024 * 1024 // 1G自动分割
    8. flag.Parse()
    9. defer glog.Flush()
    10. glog.Info("This is info message")
    11. glog.Infof("This is info message: %v", 123)
    12. glog.Warning("This is warning message")
    13. glog.Warningf("This is warning message: %v", 123)
    14. glog.Error("This is error message")
    15. glog.Errorf("This is error message: %v", 123)
    16. //glog.Fatal("This is fatal message")
    17. //glog.Fatalf("This is fatal message: %v", 123)
    18. }

    glog支持4种日志级别,从低到高依次为:INFO、WARNING、ERROR、FATAL。glog支持命令行参数,在程序中,只需要在使用glog之前调用flag.Parse()即可,支持如下7个命令行参数:

    • -alsologtostderr:同时将日志打印到文件和标准错误输出。
    • -log_backtrace_at value:指定代码运行到指定行时,把该代码的栈信息打印出来。
    • -log_dir:指定日志存储的文件夹。
    • -logtostderr:日志打印到标准错误输出,而不是文件中。
    • -stderrthreshold value:指定大于或者等于该级别的日志才会被输出到标准错误输出,默认为ERROR。
    • -v value:指定日志级别。
    • -vmodule value:对不同的文件使用不同的日志级别。

    执行上述代码(假设保存在example1.go文件中):

    1. $ mkdir -p log && go run example1.go -log_dir=log -alsologtostderr
    2. I1202 09:43:49.618480 26223 example1.go:14] This is info message
    3. I1202 09:43:49.618781 26223 example1.go:15] This is info message: 123
    4. W1202 09:43:49.618792 26223 example1.go:17] This is warning message
    5. W1202 09:43:49.618830 26223 example1.go:18] This is warning message: 123
    6. E1202 09:43:49.618840 26223 example1.go:20] This is error message
    7. E1202 09:43:49.618877 26223 example1.go:21] This is error message: 123

    以上命令会同时将日志打印在log目录和标准错误输出中(-alsologtostderr),log目录下文件列表为:

    1. main.colin.colin.log.ERROR.20201202-081133.24123
    2. main.colin.colin.log.INFO.20201202-081133.24123
    3. main.colin.colin.log.WARNING.20201202-081133.24123
    4. main.ERROR -> main.colin.colin.log.ERROR.20201202-081133.24123
    5. main.INFO -> main.colin.colin.log.INFO.20201202-081133.24123
    6. main.WARNING -> main.colin.colin.log.WARNING.20201202-081133.24123

    main.INFO文件是一个软链接,链接到最新的INFO级别的日志文件,低优先级的日志文件包含高优先级的日志,例如INFO级别的日志文件中包含WARNING、ERROR、FATAL级别的日志。默认情况下,当单个日志文件达到1.8G时,,glog会对日志文件进行转存:关闭当前的文件,新建日志文件,可以通过glog.MaxSize设置转存阈值。从上面的输出可以发现,glog的日志输出格式为:

    ] ,其中header的格式为:Lmmdd hh:mm:ss.uuuuuu threadid file:line:

    • Lmmdd:L代表了glog的日志级别:I -> INFO、W -> WARNING、E -> ERROR、F -> FATAL。
    • hh:mm:ss.uuuuuu:代表了时间信息,例如10:12:32.995956。
    • threadid,是进程PID,即os.Getpid()的返回值。
    • file:line:指明了打印日志的位置:文件名和行号。

    使用glog.Info、glog.Warning等函数记录日志后,为了提高性能,这些日志会暂存在内存的buffer中,而不是直接写入文件中,只有显式的调用glog.Flush(),数据才会被写入文件。glog的init函数中启动了一个goroutine来周期性的调用glog.Flush(),默认的flush间隔为30秒。如果程序退出,自上次glog.Flush()函数执行之后产生的日志,就会被丢失,所以在程序退出时,需要调用glog.Flush()将日志刷新到磁盘文件中。这里要注意,调用glog.Fatal函数后,glog会打印日志并退出程序,在程序退出前,会将缓存中的所有日志都写入日志,但是对于glog.Info、glog.Warning、glog.Error函数则不会。

    1. vmodule功能

    glog 最常用的就是 V level 的功能,V越小,说明日志级别越高。示例如下:(保存在example2.go文件中):

    1. package main
    2. import (
    3. "flag"
    4. "github.com/golang/glog"
    5. )
    6. func main() {
    7. flag.Parse()
    8. defer glog.Flush()
    9. glog.V(3).Info("LEVEL 3 message") // 使用日志级别 3
    10. glog.V(5).Info("LEVEL 5 message") // 使用日志级别 5
    11. glog.V(7).Info("LEVEL 7 message") // 使用日志级别 7
    12. glog.V(8).Info("LEVEL 8 message") // 使用日志级别 8
    13. }

    执行上述代码:

    1. $ go run example2.go -log_dir=log -alsologtostderr

    上面的命令不会有任何输出,因为日志级别不够,可以通过-v设置日志级别:

    1. $ go run example2.go -log_dir=log -alsologtostderr -v=5
    2. I1202 09:52:44.163989 29042 example2.go:13] LEVEL 3 message
    3. I1202 09:52:44.164335 29042 example2.go:14] LEVEL 5 message

    此时,日志级别高于或者等于5(V值小于或者等于5)的日志将被打印出来。glog还支持对不同的文件使用不同的日志级别(-vmodule),例如:

    1. $ go run main.go foo.go -v=3 -log_dir=log -alsologtostderr -vmodule=foo=5

    通过指定-vmodule=foo=5参数,可以设置对foo.go文件使用5级别,对其它文件使用3级别。-vmodule的输入参数省去了.go后缀,语法格式为:-vmodule=file1=2,file2=1,fs*=3。

    1. traceLocation功能

    traceLocation可以打印出指定位置的栈信息(-log_backtrace_at=filename:line_number),例如有如下代码:

    1. package main
    2. import (
    3. "flag"
    4. "github.com/golang/glog"
    5. )
    6. func main() {
    7. glog.MaxSize = 1024 * 1024 * 1024 // 1G自动分割
    8. flag.Parse()
    9. defer glog.Flush()
    10. glog.Info("This is info message")
    11. }

    执行以上代码(保存在example3.go文件中):

    1. $ go run example3.go -log_dir=log -alsologtostderr -log_backtrace_at=example3.go:13
    2. I1202 10:12:41.304582 1340 example3.go:13] This is info message
    3. goroutine 1 [running]:
    4. ... 打印backtrace,此处省略 ...
    5. I1202 10:12:41.304779 1340 example3.go:14] This is info message: 123

    logrus介绍

    logrus是目前Github上star数量最多的日志包,功能强大、性能高效、高度灵活,还提供了自定义插件的功能。很多优秀的开源项目,例如:docker、prometheus等都使用了logrus。logrus除了具有日志的基本功能外,还具有如下特性:

    • 支持常用的日志级别,logrus支持如下日志级别:Debug、Info、Warn、Error、Fatal和Panic。
    • 可扩展,logrus的hook机制允许使用者通过hook的方式将日志分发到任意地方,例如:本地文件、标准输出、elasticsearch、logstash、kafka等。
    • 支持自定义日志格式,logrus内置了2种格式:JSONFormatter和TextFormatter。除此之外,logrus允许使用者通过实现Formatter接口,来自定义日志格式。
    • 结构化日志记录,logrus的Field机制可以允许使用者自定义日志字段,而不是通过冗长的消息来记录日志。
    • 预设日志字段,logrus的Default Fields机制可以给一部分或者全部日志统一添加共同的日志字段,例如给某次HTTP请求的所有日志添加X-Request-ID字段。
    • Fatal handlers:logrus允许注册一个或多个handler,当发生fatal级别的日志时调用。当我们的程序需要优雅关闭时,该特性会非常有用。

    logrus使用方法如下:

    1. 基本用法

    logrus可以通过简单的配置,来定义输出、格式或者日志级别等,示例如下:

    1. package main
    2. import (
    3. "os"
    4. "github.com/sirupsen/logrus"
    5. )
    6. func main() {
    7. // logrus设置
    8. logrus.SetFormatter(&logrus.JSONFormatter{})
    9. logrus.SetOutput(os.Stdout)
    10. logrus.SetLevel(logrus.WarnLevel)
    11. // logrus使用
    12. logrus.Debug("Useful debugging information.")
    13. logrus.Info("Something noteworthy happened!")
    14. logrus.Warn("You should probably take a look at this.")
    15. logrus.Error("Something failed but I'm not quitting.")
    16. logrus.WithFields(logrus.Fields{
    17. "animal": "walrus",
    18. "size": 10,
    19. }).Info("A group of walrus emerges from the ocean")
    20. logrus.WithFields(logrus.Fields{
    21. "omg": true,
    22. "number": 122,
    23. }).Warn("The group's number increased tremendously!")
    24. }

    可以通过logrus.SetFormatter设置输出的日志格式,logrus自带有JSONFormatter和TextFormatter。通过logrus.SetLevel来设置日志级别,通过logrus.SetOutput设置日志输出等。假设上述代码保存在example1.go文件中,运行代码:

    1. $ go run example1.go
    2. {"level":"warning","msg":"You should probably take a look at this.","time":"2020-12-03T22:35:35+08:00"}
    3. {"level":"error","msg":"Something failed but I'm not quitting.","time":"2020-12-03T22:35:35+08:00"}
    4. {"level":"warning","msg":"The group's number increased tremendously!","number":122,"omg":true,"time":"2020-12-03T22:35:35+08:00"}
    1. Default Fields

    通常,在一个应用中、或者应用的一部分中,始终附带一些固定的记录字段会很有帮助。比如在处理用户HTTP请求时,上下文中所有的日志都会有request_id。为了避免每次记录日志都要使用:

    1. logrus.WithFields(logrus.Fields{"request_id”", request_id})

    我们可以创建一个logrus.Entry实例,为这个实例设置默认Fields,把logrus.Entry实例设置到记录器Logger,再记录日志时每次都会附带上这些默认的字段。

    1. logger := log.WithFields(log.Fields{"request_id": request_id})
    2. logger.Info("something happened on that request") // 也会记录request_id
    3. logger.Warn("something not great happened")
    1. Hook接口

    logrus具有hook能力,允许我们自定义一些日志处理逻辑,实现一个hook只需要实现如下接口:// logrus在记录Levels()返回的日志级别的消息时会触发HOOK, // 按照Fire方法定义的内容修改

    1. // logrus在记录Levels()返回的日志级别的消息时会触发HOOK,
    2. // 按照Fire方法定义的内容修改logrus.Entry.
    3. type Hook interface {
    4. Levels() []Level
    5. Fire(*Entry) error
    6. }

    一个简单自定义hook如下,DefaultFieldHook定义会在所有级别的日志消息中加入默认字段

    1. type DefaultFieldHook struct {
    2. }
    3. func (hook *DefaultFieldHook) Fire(entry *log.Entry) error {
    4. entry.Data["myHook"] = " MyHookTest "
    5. return nil
    6. }
    7. func (hook *DefaultFieldHook) Levels() []log.Level {
    8. return log.AllLevels
    9. }

    实现了hook之后,只需要调用log.AddHook(hook)即可将自定义的hook注册到logrus中。通过hook可以实现很多强大的日志处理功能,比较常见的用法是,当有指定级别的日志产生时,邮件通知或者告警给相关负责人。

    zap包介绍

    zap是uber开源的日志包,以高性能著称,很多公司的日志包都是基于zap改造而来。zap除了具有日志基本的功能之外,还具有很多强大的特性:

    • 支持常用的日志级别,例如:Debug、Info、Warn、Error、DPanic、Panic、Fatal。
    • 性能非常高,zap具有非常高的性能,适合对性能要求比较高的场景。
    • 像logrus一样,支持结构化的日志记录。
    • 支持预设日志字段。
    • 支持针对特定的日志级别,输出调用堆栈。
    • 支持hook。

    zap使用方法

    1. 基本用法

    zap使用方法跟其他日志包使用方法比较类似,如下是一个常见的用法:

    1. package main
    2. import (
    3. "time"
    4. "go.uber.org/zap"
    5. )
    6. func main() {
    7. logger, _ := zap.NewProduction()
    8. defer logger.Sync() // flushes buffer, if any
    9. url := "http://marmotedu.com"
    10. logger.Info("failed to fetch URL",
    11. zap.String("url", url),
    12. zap.Int("attempt", 3),
    13. zap.Duration("backoff", time.Second),
    14. )
    15. sugar := logger.Sugar()
    16. sugar.Infow("failed to fetch URL",
    17. "url", url,
    18. "attempt", 3,
    19. "backoff", time.Second,
    20. )
    21. sugar.Infof("Failed to fetch URL: %s", url)
    22. }

    将上述代码保存在example1.go文件中,运行:

    1. {"level":"info","ts":1607006503.3008754,"caller":"zap/example1.go:13","msg":"failed to fetch URL","url":"http://marmotedu.com","attempt":3,"backoff":1}
    2. {"level":"info","ts":1607006503.3009226,"caller":"zap/example1.go:20","msg":"failed to fetch URL","url":"http://marmotedu.com","attempt":3,"backoff":1}
    3. {"level":"info","ts":1607006503.300958,"caller":"zap/example1.go:25","msg":"Failed to fetch URL: http://marmotedu.com"}

    默认的日志输出格式为JSON格式,并记录了文件名和行号。上述代码通过zap.NewProduction()创建了一个logger,zap还提供了zap.NewExample()、zap.NewDevelopment()来快速创建一个logger,不同方法创建的logger具有不同的设置,Example适合用在测试代码中,Development在开发环境中使用,Production用在生产环境。如果想自定义logger,可以调用zap.New()方法来创建。logger提供了Debug、Info、Warn、Error、Panic、Fatal等方法,用来记录不同级别的日志。在程序退出时,注意要调用defer logger.Sync()将缓存中的日志刷新到磁盘文件中。当我们对日志的性能要求比较高时,可以使用Logger而非SugaredLogger,Logger性能更好,内存分配次数更少。为了提高性能,Logger没有使用interface和反射,并且Logger只支持结构化的日志,所以在使用Logger时,需要指定具体的类型和key-value格式的日志字段,例如:

    1. logger.Info("failed to fetch URL",
    2. zap.String("url", url),
    3. zap.Int("attempt", 3),
    4. zap.Duration("backoff", time.Second),
    5. )

    如果觉得Logger的日志格式比较繁琐,可以使用更加便捷的SugaredLogger,调用logger.Sugar()即可创建SugaredLogger。SugaredLogger的使用比Logger简单,但性能比Logger低 50% 左右,可以用在调用次数不高的函数中,调用方式如下:

    1. sugar := logger.Sugar()
    2. sugar.Infow("failed to fetch URL",
    3. "url", url,
    4. "attempt", 3,
    5. "backoff", time.Second,
    6. )
    7. sugar.Infof("Failed to fetch URL: %s", url)
    1. 定制Logger

    可以使用NexExample()/NewDevelopment()/NewProduction()函数创建默认的Logger,每种方法创建的Logger配置不一样,也可以创建一个定制化的Logger,创建方式如下:

    1. package main
    2. import (
    3. "encoding/json"
    4. "go.uber.org/zap"
    5. )
    6. func main() {
    7. rawJSON := []byte(`{
    8. "level":"debug",
    9. "encoding":"json",
    10. "outputPaths": ["stdout", "test.log"],
    11. "errorOutputPaths": ["stderr"],
    12. "initialFields":{"name":"dj"},
    13. "encoderConfig": {
    14. "messageKey": "message",
    15. "levelKey": "level",
    16. "levelEncoder": "lowercase"
    17. }
    18. }`)
    19. var cfg zap.Config
    20. if err := json.Unmarshal(rawJSON, &cfg); err != nil {
    21. panic(err)
    22. }
    23. logger, err := cfg.Build()
    24. if err != nil {
    25. panic(err)
    26. }
    27. defer logger.Sync()
    28. logger.Info("server start work successfully!")
    29. }

    以上示例调用zap.Config的Build方法创建了一个输出到标准输出和文件test.log的Logger,将上述代码保存在example2.go文件中,运行:

    1. $ go run example2.go
    2. {"level":"info","message":"server start work successfully!","name":"dj"}

    zap.Config定义如下:

    1. type Config struct {
    2. Level AtomicLevel
    3. Development bool
    4. DisableCaller bool
    5. DisableStacktrace bool
    6. Sampling *SamplingConfig
    7. Encoding string
    8. EncoderConfig zapcore.EncoderConfig
    9. OutputPaths []string
    10. ErrorOutputPaths []string
    11. InitialFields map[string]interface{}
    12. }

    Config结构体,各字段说明如下:

    • Level:日志级别。
    • Development:设置Logger的模式为development模式。
    • DisableCaller:禁用调用信息. 该字段值为 true 时, 日志中将不再显示该日志所在的函数调用信息。
    • DisableStacktrace:禁用自动堆栈跟踪捕获。
    • Sampling:流控配置, 也叫采样. 单位是每秒钟, 作用是限制日志在每秒钟内的输出数量, 以防止CPU和IO被过度占用。
    • Encoding:指定日志编码器, 目前仅支持两种编码器:console和json,默认为json。
    • EncoderConfig:编码配置。
    • OutputPaths:配置日志标准输出,可以配置多个日志输出路径, 一般情况可以仅配置标准输出或输出到文件, 如有需求的话, 也可以两者同时配置。
    • ErrorOutputPaths:错误输出路径,可以是多个。
    • InitialFields:初始化字段配置, 该配置的字段会以结构化的形式打印在每条日志输出中。

    调用zap.Config的Build()方法,可以使用zap.Config配置创建一个Logger。其中EncoderConfig为编码配置:

    1. type EncoderConfig struct {
    2. MessageKey string `json:"messageKey" yaml:"messageKey"`
    3. LevelKey string `json:"levelKey" yaml:"levelKey"`
    4. TimeKey string `json:"timeKey" yaml:"timeKey"`
    5. NameKey string `json:"nameKey" yaml:"nameKey"`
    6. CallerKey string `json:"callerKey" yaml:"callerKey"`
    7. FunctionKey string `json:"functionKey" yaml:"functionKey"`
    8. StacktraceKey string `json:"stacktraceKey" yaml:"stacktraceKey"`
    9. LineEnding string `json:"lineEnding" yaml:"lineEnding"`
    10. EncodeLevel LevelEncoder `json:"levelEncoder" yaml:"levelEncoder"`
    11. EncodeTime TimeEncoder `json:"timeEncoder" yaml:"timeEncoder"`
    12. EncodeDuration DurationEncoder `json:"durationEncoder" yaml:"durationEncoder"`
    13. EncodeCaller CallerEncoder `json:"callerEncoder" yaml:"callerEncoder"`
    14. EncodeName NameEncoder `json:"nameEncoder" yaml:"nameEncoder"`
    15. ConsoleSeparator string `json:"consoleSeparator" yaml:"consoleSeparator"`
    16. }

    常用的设置如下:

    • MessageKey:日志中信息的键名,默认为msg。
    • LevelKey:日志中级别的键名,默认为level。
    • EncodeLevel:日志中级别的格式,默认为小写,如debug/info。
    1. 选项

    zap支持多种选项,选项的使用方式如下:

    1. package main
    2. import "go.uber.org/zap"
    3. func main() {
    4. logger, _ := zap.NewProduction(zap.AddCaller())
    5. defer logger.Sync()
    6. logger.Info("hello world")
    7. }

    将上述代码保存在example3.go中,执行:

    1. $ go run example3.go
    2. {"level":"info","ts":1607010625.6718638,"caller":"zap/example3.go:9","msg":"hello world"}

    上述日志输出了日志的调用信息(文件名:行号)”caller”:”zap/example3.go:9”。zap提供了多个选项可供选择:

    • AddStacktrace(lvl zapcore.LevelEnabler):用来在指定级别及以上级别输出调用堆栈。
    • zap.WithCaller(enabled bool):指定是否在日志输出内容中增加文件名和行号。
    • zap.AddCaller():与zap.WithCaller(true)等价,指定在日志输出内容中增加行号和文件名。
    • zap. AddCallerSkip(skip int):指定在调用栈中跳过的调用深度,否则通过调用栈获得的行号可能总是日志组件中的行号。
    • zap. IncreaseLevel(lvl zapcore.LevelEnabler):提高日志级别,如果传入的lvl比当前logger的级别低,则不会改变日志级别。
    • ErrorOutput(w zapcore.WriteSyncer):指定日志组件中出现异常时的输出位置。
    • Fields(fs …Field):添加公共字段。
    • Hooks(hooks …func(zapcore.Entry) error):注册钩子函数,用来在日志打印时同时调用hook方法。
    • WrapCore(f func(zapcore.Core) zapcore.Core):替换Logger的zapcore.Core。 - Development():将Logger修改为Development模式。
    1. 预设日志字段

    如果每条日志都期望加一些公共字段,例如requestID,可以在创建Logger时使用Fields(fs …Field)选项,如下代码中,添加了requestID、userID公共字段到每条日志中:

    1. package main
    2. import "go.uber.org/zap"
    3. func main() {
    4. logger := zap.NewExample(zap.Fields(
    5. zap.Int("userID", 10),
    6. zap.String("requestID", "fbf54504"),
    7. ))
    8. logger.Debug("This is a debug message")
    9. logger.Info("This is a info message")
    10. }

    将上述代码保存到preset_field.go文件中,运行:

    1. $ go run preset_field.go
    2. {"level":"debug","msg":"This is a debug message","userID":10,"requestID":"fbf54504"}
    3. {"level":"info","msg":"This is a info message","userID":10,"requestID":"fbf54504"}
    1. 全局Logger

    zap提供了2个全局Logger,可以方便我们调用:

    • *zap.Logger:可通过zap.L()获得,提供了Debug()、Info()、Warn()、Error()、Panic()、DPanic()、Fatal()方法来记录日志。
    • *zap.SugaredLogger:可通过zap.S()获得,提供了Debugf()、Debugw()、Infof()、Infow()、Warnf()、Warnw()、Errorf()、Errorw()、Panicf()、Panicw()、DPanicf()、DPanicw()、Fatalf()、Fatalw()方法来记录日志。

    默认的全局Logger不会记录任何日志,它是一个无用的Logger,例如zap.L()返回了名为_globalL的Logger,_globalL定义为:

    1. _globalL = NewNop()
    2. func NewNop() *Logger {
    3. return &Logger{
    4. core: zapcore.NewNopCore(),
    5. errorOutput: zapcore.AddSync(ioutil.Discard),
    6. addStack: zapcore.FatalLevel + 1,
    7. }
    8. }

    zapcore.NewNopCore()函数定义为:

    1. type nopCore struct{}
    2. // NewNopCore returns a no-op Core.
    3. func NewNopCore() Core { return nopCore{} }
    4. func (nopCore) Enabled(Level) bool { return false }
    5. func (n nopCore) With([]Field) Core { return n }
    6. func (nopCore) Check(_ Entry, ce *CheckedEntry) *CheckedEntry { return ce }
    7. func (nopCore) Write(Entry, []Field) error { return nil }
    8. func (nopCore) Sync() error { return nil }
    9. // NewCore creates a Core that writes logs to a WriteSyncer.
    10. func NewCore(enc Encoder, ws WriteSyncer, enab LevelEnabler) Core {
    11. return &ioCore{
    12. LevelEnabler: enab,
    13. enc: enc,
    14. out: ws,
    15. }
    16. }

    可以看到NewNop()创建一个不记录任何日志、任何内部错误、不执行任何钩子的Logger。可以使用ReplaceGlobals函数将全局Logger替换为我们创建的Logger,例如:

    1. package main
    2. import "go.uber.org/zap"
    3. func main() {
    4. zap.L().Info("default global Logger")
    5. zap.S().Info("default global SugaredLogger")
    6. logger := zap.NewExample()
    7. defer logger.Sync()
    8. zap.ReplaceGlobals(logger)
    9. zap.L().Info("replaced global Logger")
    10. zap.S().Info("replaced global SugaredLogger")
    11. }

    假设上述代码保存在global_logger.go文件中,运行:

    1. $ go run global_logger.go
    2. {"level":"info","msg":"replaced global Logger"}
    3. {"level":"info","msg":"replaced global SugaredLogger"}

    可以看到在zap.ReplaceGlobals(logger)之前的日志,并没有被打印出来。

    其它开源包

    还有很多其它优秀的开源日志包供我们选择,例如:log15、zerolog、seelog、apex/log、go-logging等。你可以在开发中,都详加调研,选择一个适合自己的日志包。

    开源日志包选择

    我们介绍了很多日志包,每种日志包使用的场景不同,你可以根据自己的需求结合日志包的特性进行选择:

    • 标准库log包: 标准库log包不支持日志级别、日志分割、日志格式等功能,所以在大型项目中很少直接使用,通常用于一些短小的程序,比如:用于生成JWT Token的main.go文件中。标准库日志包也很适合一些简短的代码,用于快速调试和验证。
    • glog: glog实现了日志包的基本功能,对于一些对日志功能要求不多的小型项目非常适合。
    • logrus: logrus功能强大,不仅实现了日志包的基本功能,还有很多高级特性,适合一些大型项目,尤其是需要结构化日志记录的项目。
    • zap: zap提供了很强大的日志功能,性能高,内存分配次数少,适合对日志性能要求很高的项目。另外,zap包中的子包zapcore,提供了很多底层的日志接口,适合用来做二次封装。例如笔者就基于zap和zapcore封装了marmotedu/log日志包,该日志包可以很好的兼容glog,封装背景是因为在做容器云平台开发时,发现kubernetes源码中大量使用了glog,需要日志包能够兼容glog。
    以太坊cppgolang区别 编程

    以太坊cppgolang区别

    以太坊是一种去中心化的开源平台,它采用智能合约技术,旨在构建和运行不受干扰的分布式应用程序。作为目前最受欢迎的区块链平台之一,以太坊提供了多种编程语言的支持,其
    progolang 编程

    progolang

    Go语言(Golang)是由Google开发的一门静态类型编程语言。作为一名专业的Golang开发者,我深知这门语言的优势和特点。在本文中,我将介绍Golang
    golangn个发送者 编程

    golangn个发送者

    Golang是一种开源的编程语言,由Google团队开发,旨在提高程序的并发性和简化软件开发过程。在Go语言中,有时需要向多个接收者发送信息。本文将介绍如何在G
    golang技能图谱 编程

    golang技能图谱

    从互联网行业的快速发展到人工智能技术的日益成熟,各种编程语言也应运而生。而在这众多的编程语言中,Golang(即Go)作为一门强大且高效的开发语言备受关注。Go
    评论:0   参与:  16