[TOC]
0x00 前言简述 描述: 日志是现代编程中必不可少的手段,除了处理基本的错误之外,通过记录日志,也可以帮助我们完成一些基本的功能,比如开发及测试期间的Debug,记录请求的上下文,排除故障原因,数据统计及分析等等。
所以本节将主要分享 Go 语言中常用的日志记录库(包)即相关依赖包的下载使用,当前Go语言常用的日志库模块有 logrus , Zerolog, Zap, and Apex
等。
sirupsen/logrus 模块 - 日志记录 描述: Logrus 是一个结构化、可插拔的Go日志库,并且完全兼容官方的log库,具有很强的灵活性,有 TEXT 和 JSON 两种可选的日志输出格式,同时还提供了自定义格式的插件功能,支持 Feild 机制和可扩展的 Hook 机制。
项目地址: https://github.com/sirupsen/logrus 项目文档: https://pkg.go.dev/github.com/sirupsen/logrus
logrus 不推荐使用冗长的消息来记录运行信息,它推荐使用Fields来进行精细化的结构化的信息记录,例如:
[TOC]
0x00 前言简述 描述: 日志是现代编程中必不可少的手段,除了处理基本的错误之外,通过记录日志,也可以帮助我们完成一些基本的功能,比如开发及测试期间的Debug,记录请求的上下文,排除故障原因,数据统计及分析等等。
所以本节将主要分享 Go 语言中常用的日志记录库(包)即相关依赖包的下载使用,当前Go语言常用的日志库模块有 logrus , Zerolog, Zap, and Apex
等。
sirupsen/logrus 模块 - 日志记录 描述: Logrus 是一个结构化、可插拔的Go日志库,并且完全兼容官方的log库,具有很强的灵活性,有 TEXT 和 JSON 两种可选的日志输出格式,同时还提供了自定义格式的插件功能,支持 Feild 机制和可扩展的 Hook 机制。
项目地址: https://github.com/sirupsen/logrus 项目文档: https://pkg.go.dev/github.com/sirupsen/logrus
logrus 不推荐使用冗长的消息来记录运行信息,它推荐使用Fields来进行精细化的结构化的信息记录,例如:1 2 3 4 5 6 7 8 9 log.Fatalf("Failed to send event %s to topic %s with key %d" , event, topic, key) log.WithFields(log.Fields{ "event" : event, "topic" : topic, "key" : key, }).Fatal("Failed to send event" )
日志等级: 其中 Trace 优先级最低,Panic 优先级最高。
Panic:恐慌级别,也是最高级别的日志,会打印出错误堆栈。
Fatal:致命错误,输出日志后,执行 exit(1) 退出
Error:错误日志,必须记录与跟踪的日志
Warn:警告日志,主要记录需要提醒开发者的日志
Info:主要是提供一些必要的日志信息,在业务出现问题时,可以结合error日志快速定位问题,一般会默认使用该级别的日志。
Debug:调试信息,方便开发测试阶段的问题定位
Trace:比 debug 级别还低,一般很少用。
1 2 3 4 5 6 7 log.Trace("Something very low level." ) log.Debug("Useful debugging information." ) log.Info("Something noteworthy happened!" ) log.Warn("You should probably take a look at this." ) log.Error("Something failed but I'm not quitting." ) log.Fatal("Bye." ) log.Panic("I'm bailing." )
默认字段: 除了使用WithField或WithFields添加的字段外,还会自动将一些字段添加到所有日志事件中:
time : The timestamp when the entry was created.
msg :The logging message passed to {Info,Warn,Error,Fatal,Panic} after the AddFields call. E.g. Failed to send event.
level :The logging level. E.g. info.
日志格式
模块下载: 1 go get -v -u github.com/sirupsen/logrus
示例演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package mainimport ( "os" log "github.com/sirupsen/logrus" ) func main () { log.Println("标准输出!" ) log.Traceln("Trace 级别" ) log.Infoln("Info 级别" ) log.Errorln("Error 级别" ) log.WithFields(log.Fields{ "flag" : true , "name" : "WeiyiGeek" , "site" : "https://blog.weiyigeek.top" , }).Info("Info 级别信息" ) log.SetFormatter(&log.JSONFormatter{ TimestampFormat: "2006-01-02 15:04:05" , }) log.SetFormatter(&log.TextFormatter{ TimestampFormat: "2006-01-02 15:04:05" , ForceColors: true , FullTimestamp: true , }) log.SetOutput(os.Stdout) log.SetLevel(log.WarnLevel) }
执行结果:1 2 3 4 5 $ go run .\logging.go time="2023-04-14T13:18:10+08:00" level=info msg="标准输出!" time="2023-04-14T13:18:10+08:00" level=info msg="Info 级别" time="2023-04-14T13:18:10+08:00" level=error msg="Error 级别" time="2023-04-14T13:18:10+08:00" level=info msg="Info 级别信息" flag=true name=WeiyiGeek site="https://blog.weiyigeek.top"
示例2.同时将日志输出到终端和日志文件中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 package mainimport ( "io" "os" "path" "github.com/sirupsen/logrus" ) func main () { var logger = logrus.New() logger.SetFormatter(&logrus.JSONFormatter{ TimestampFormat: "2006-01-02 15:04:05" , }) logger.SetReportCaller(true ) logPath := "D:/Study/Project/Go/hello-gin/logs" logName := "app" logFile := path.Join(logPath, logName+".log" ) file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, os.ModeAppend) fileAndStdout := io.MultiWriter(file, os.Stdout) if err == nil { logger.SetOutput(fileAndStdout) } else { logger.Errorf("Failed to log to file, using default stderr" , err) } defer file.Close() logger.SetLevel(logrus.DebugLevel) logger.Debugln("我是Debug信息!" ) logger.Info("我是普通信息!" ) logger.Errorln("我是Error信息!" ) logger.WithFields(logrus.Fields{ "request_id" : "887B4F43-D330-5213-94DF-1B14FA3388C" , "ip" : "110.110.110.110" , "user_id" : 1001 , }).Info("Info Level" ) logger.WithField("request_id" , "887B4F43-D330-5213-94DF-1B14FA3388C" ).WithField("ip" , "110.110.110.110" ).WithField("user_id" , 1001 ).Info("Info Level" ); }
执行结果:1 2 3 4 $ go run .\logging.go {"level" :"debug" ,"msg" :"我是Debug信息!" ,"time" :"2023-04-14 14:39:44" } {"level" :"info" ,"msg" :"我是普通信息!" ,"time" :"2023-04-14 14:39:44" } {"level" :"error" ,"msg" :"我是Error信息!" ,"time" :"2023-04-14 14:39:44" }
温馨提示:我们还可以创建一个logrus.Entry实例,为这个实例设置默认Fields,把logrus.Entry实例设置到记录器Logger,再记录日志时每次都会附带上这些默认的字段。
1 2 3 logger := log.WithFields(log.Fields{"request_id" : "887B4F43-D330-5213-94DF-1B14FA3388C" }) logger.Info("Info Level" ) logger.Warn("Warn Levle" )
温馨提示:默认情况下,logrus的api都是线程安全的,其内部通过互斥锁来保护并发写。互斥锁工作于调用hooks或者写日志的时候。如果不需要锁,可以调用logger.SetNoLock()来关闭之,通常在没有设置hook,或者所有的hook都是线程安全的实现,再或者写日志到logger.Out已经是线程安全的了
。
扩展学习:
自定义 HOOK : logrus最令人心动的功能就是其可扩展的HOOK机制了,通过在初始化时为logrus添加hook,便可以实现各种扩展功能. logrus的hook接口定义如下,其原理是每此写入日志时拦截修改logrus.Entry.
1 2 3 4 5 6 type Hook interface { Levels() []Level Fire(*Entry) error }
例如,自定义一个DefaultFieldHook,它在所有级别的日志消息中加入默认字段appName=”weiyigeek”.
1 2 3 4 5 6 7 8 9 10 11 type DefaultFieldHook struct {} func (hook *DefaultFieldHook) Fire (entry *log.Entry) error { entry.Data["appName" ] = "weiyigeek" return nil } func (hook *DefaultFieldHook) Levels () []log .Level { return log.AllLevels }
lestrrat-go/file-rotatelogs 模块 - 日志分隔 描述: 由于logrus并不自带日志本地文件分割功能,所以我们使用file-rotatelogs模块进行分隔,它是提供一个 io.Writer 那定期转录文件在应用程序中, 注意 file-rotatelogs 项目已于 Jul 19, 2021 停止更新维护。
官网地址: https://github.com/lestrrat-go/file-rotatelogs
模块属性
Pattern : 日志文件名称模式。
WithLinkName(“/path/to/log”):实际日志文件的符号链接所在的路径。1 2 Main log file name -> Link name -> Linked path /path/to/log .%Y%m%d -> /path/to/log -> log.YYYYMMDD
WithRotationTime(24*time.Hour):默认24小时, 文件旋转之间的间隔。
WithMaxAge(-1):默认每7天清除之前旧日志。
WithRotationCount(7): 设置应保留文件的数量。
ForceNewFile() :确保每次调用new()时都会创建一个新文件,如果基本文件名已经存在,则执行隐式旋转。
温馨提示: WithMaxAge 和 WithRotationCount 二者只能设置一个。
示例演示: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 InfologWriter, err := rotatelogs.New( infologFile+".%Y%m%d" , rotatelogs.WithLinkName(infologFile), rotatelogs.WithMaxAge(7 *24 *time.Hour), rotatelogs.WithRotationTime(24 *time.Hour), ) if err != nil { fmt.Println("Failed to create logfile" + errorlogFile) panic (err) }
rifflock/lfshook 模块 - 本地文件系统挂钩 描述: 有时开发人员喜欢直接写入文件系统上的文件, 它是logrus的一个钩子,旨在允许用户这样做, 日志级别在钩子的实例化时是动态的,因此它能够在某些或所有级别进行日志记录。
项目地址: https://github.com/rifflock/lfshook
综合演示: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 package mainimport ( "io" "os" "path" "time" rotatelogs "github.com/lestrrat-go/file-rotatelogs" "github.com/rifflock/lfshook" "github.com/sirupsen/logrus" ) func NewLogger () *logrus .Logger { var logger = logrus.New() logPath := "D:/Study/Project/Go/hello-gin/logs" logName := "app" logFile := path.Join(logPath, logName) file, err := os.OpenFile(logFile+".log" , os.O_CREATE|os.O_WRONLY|os.O_APPEND, os.ModeAppend) fileAndStdout := io.MultiWriter(file, os.Stdout) if err == nil { logger.SetOutput(fileAndStdout) } else { logger.Errorf("Failed to log to file, using default stderr" , err) } InfologWriter, _ := rotatelogs.New( logFile+"-info.%Y%m%d" , rotatelogs.WithLinkName(logFile+".log" ), rotatelogs.WithMaxAge(7 *time.Duration(86400 )*time.Second), rotatelogs.WithRotationTime(time.Duration(86400 )*time.Second), ) DebuglogWriter, _ := rotatelogs.New( logFile+"-debug.%Y%m%d" , rotatelogs.WithMaxAge(7 *time.Duration(86400 )*time.Second), rotatelogs.WithRotationTime(time.Duration(86400 )*time.Second), ) WarnlogWriter, _ := rotatelogs.New( logFile+"-warn.%Y%m%d" , rotatelogs.WithMaxAge(7 *time.Duration(86400 )*time.Second), rotatelogs.WithRotationTime(time.Duration(86400 )*time.Second), ) ErrorlogWriter, _ := rotatelogs.New( logFile+"-error.%Y%m%d" , rotatelogs.WithMaxAge(7 *time.Duration(86400 )*time.Second), rotatelogs.WithRotationTime(time.Duration(86400 )*time.Second), ) lfHook := lfshook.NewHook(lfshook.WriterMap{ logrus.DebugLevel: DebuglogWriter, logrus.InfoLevel: InfologWriter, logrus.WarnLevel: WarnlogWriter, logrus.ErrorLevel: ErrorlogWriter, }, &logrus.JSONFormatter{ TimestampFormat: "2006-01-02 15:04:05" , }) logger.AddHook(lfHook) logger.SetFormatter(&logrus.JSONFormatter{ TimestampFormat: "2006-01-02 15:04:05" , }) logger.SetLevel(logrus.DebugLevel) return logger } func main () { log := NewLogger() log.Debug("Debug - Useful debugging information." ) log.Info("Info - Something noteworthy happened!" ) log.Warn("Warn - You should probably take a look at this." ) log.Error("Error - Something failed but I'm not quitting." ) }
执行结果:1 2 3 4 5 6 $ go run .\logrotate.go {"level" :"debug" ,"msg" :"Debug - Useful debugging information." ,"time" :"2023-04-14 16:04:25" } failed to rotate: failed to rename new symlink: rename D:/Study/Project/Go/hello-gin/logs/app-info.20230414_symlink D:/Study/Project/Go/hello-gin/logs/app.log: Access is denied. {"level" :"info" ,"msg" :"Info - Something noteworthy happened!" ,"time" :"2023-04-14 16:04:25" } {"level" :"warning" ,"msg" :"Warn - You should probably take a look at this." ,"time" :"2023-04-14 16:04:25" } {"level" :"error" ,"msg" :"Error - Something failed but I'm not quitting." ,"time" :"2023-04-14 16:04:25" }
温馨提示: 在执行时如果出现 failed to rotate: failed to create new symlink: symlink m A required privilege is not held by the client.
表示执行的终端没有管理员权限,如果你是WINDOWS此处你需要在开始菜单中右键以管理员运行Shell终端或者在Powershell中执行Start-Process powershell -Verb runAs
命令。