Go的日志库Logrus
一 logrus介绍
1.1 log标准库优缺点
优点
Go标准库的log日志库非常简单
可以便设置任何
io.Writer
作为日志输出位置
缺点
1 仅仅提供了print,panic和fatal三个函数,不支持info/debug等多个级别
2 记录错误有Fatal和Panic;Fatal通过调用
os.Exit(1)
来结束程序;Panic在写入日志后抛出一个panic;缺少ERROR日志级别,在不抛出异常和退出程序的情况下记录日志3 不支持多输出 - 同时支持标准输出,文件等
4 缺乏日志格式化的能力,例如:记录函数名和行号,格式化日期和时间格式等
5 可读性与结构化差,没有Json格式或有分隔符,不方便后续的日志采集、监控等
6 对于更精细的日志级别、日志文件分割,以及日志分发等,没有提供支持
1.2 Go中常用第三方日志库
在Go的世界,流行的日志框架有logrus、zap、zerolog等
logrus
目前Github上star数量最多的日志库
项目地址: https://github.com/sirupsen/logrus
Stars数量:20.3k
zap
是Uber推出的一个快速、结构化的分级日志库
项目地址:https://github.com/uber-go/zap
官方文档:https://pkg.go.dev/go.uber.org/zap
Stars数量:20.3k
zerolog
它的 API 设计非常注重开发体验和性能。zerolog
只专注于记录 JSON 格式的日志,号称 0 内存分配
项目地址:https://github.com/rs/zerolog
Stars数量:6.2k
二 logrus
2.1 logrus特点
优点
- 完全兼容Go标准库日志模块:logrus拥有六种日志级别:debug、info、warn、error、fatal和panic,这是golang标准库日志模块的API的超集。如果之前项目使用标准库日志模块,完全可以以最低的代价迁移到logrus上
- 可扩展的Hook机制:允许使用者通过hook的方式将日志分发到任意地方,如本地文件系统、标准输出、linfluxdb、logstash、elasticsearch或者mq等,或者通过hook定义日志内容和格式等
- 可选的日志输出格式:logrus内置了两种日志格式,JSONFormatter和TextFormatter,如果这两个格式不满足需求,可以自己动手实现接口Formatter,来定义自己的日志格式
- Field机制:logrus鼓励通过Field机制进行精细化的、结构化的日志记录,而不是通过冗长的消息来记录日志
- logrus是一个可插拔的、结构化的日志框架,很多开源项目,如docker,prometheus等,都是用了logrus来记录其日志
缺点
尽管 logrus有诸多优点,但是为了灵活性和可扩展性,官方也削减了很多实用的功能,例如:
- 没有提供行号和文件名的支持
- 输出到本地文件系统没有提供日志分割功能
- 官方没有提供输出到ELK等日志处理中心的功能
但是这些功能都可以通过自定义hook来实现
2.2 logrus配置
日志级别
logrus有7个日志级别,依次是Trace --> Debug --> Info --> Warning -->Error --> Fatal -->Panic
// 只输出不低于当前级别是日志数据 logrus.SetLevel(logrus.DebugLevel)
日志格式
logrus内置了JSONFormatter
和TextFormatter
两种格式,也可以通过Formatter
接口定义日志格式
// TextFormatter格式 logrus.SetFormatter(&logrus.TextFormatter{ ForceColors: true, EnvironmentOverrideColors: true, TimestampFormat: "2006-01-02 15:04:05", //时间格式 // FullTimestamp:true, // DisableLevelTruncation:true, }) // JSONFormatter格式 logrus.SetFormatter(&logrus.JSONFormatter{ PrettyPrint: false, //格式化 TimestampFormat: "2006-01-02 15:04:05", //时间格式 })
输出文件
logfile, _ := os.OpenFile("./log.log", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644) logrus.SetOutput(logfile) //默认为os.stderr //logrus.SetOutput(io.MultiWriter(os.Stdout, logfile)) // 输出到多个位置
日志定位
定位行号(如:func=main.main file="./xxx.go:38"
)
logrus.SetReportCaller(true)
2.3 快速使用
go get github.com/sirupsen/logrus
package main import ( "github.com/sirupsen/logrus" "os" ) func init() { // 1 日志级别为debug logrus.SetLevel(logrus.DebugLevel) //2 日志格式为json格式 logrus.SetFormatter(&logrus.JSONFormatter{ TimestampFormat: "2006-01-02 15:04:05", }) // 日志格式为文本格式 //logrus.SetFormatter(&logrus.TextFormatter{ //ForceColors: true, //EnvironmentOverrideColors: true, //TimestampFormat: "2006-01-02 15:04:05", //时间格式 //FullTimestamp:true, // 显示完整时间 //DisableLevelTruncation:true, //}) //3 输出文件为app.log logfile, _ := os.OpenFile("./app.log", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644) //logrus.SetOutput(io.MultiWriter(os.Stdout,logfile)) // 即写到控制台,又写到文件中 logrus.SetOutput(logfile) // 只写到文件中 //不写默认为os.stderr // 4 日志定位--显示打印日志文件和位置 logrus.SetReportCaller(true) } func main() { logrus.Infoln("info--日志数据") logrus.Debugln("debug--日志数据") logrus.Errorln("err--日志数据") }
2.4 两个自带formatter和自定义
TextFormatter
type TextFormatter struct { DisableColors bool // 开启颜色显示 DisableTimestamp bool // 开启时间显示 TimestampFormat string// 自定义时间格式 QuoteEmptyFields bool//空字段括在引号中 CallerPrettyfier func(*runtime.Frame) (function string, file string) //用于自定义方法名和文件名的输出 }
JsonFormatter
type JSONFormatter struct { TimestampFormat string // 自定义时间格式 DisableTimestamp bool // 开启时间显示 CallerPrettyfier func(*runtime.Frame) (function string, file string) //用于自定义方法名和文件名的输出 PrettyPrint bool //将缩进所有json日志 }
自定义Formatter
//只需要实现该接口 type Formatter interface { Format(*Entry) ([]byte, error) } // 其中entry参数 type Entry struct { // Contains all the fields set by the user. Data Fields // Time at which the log entry was created Time time.Time // Level the log entry was logged at: Trace, Debug, Info, Warn, Error, Fatal or Panic Level Level //Calling method, with package name Caller *runtime.Frame //Message passed to Trace, Debug, Info, Warn, Error, Fatal or Panic Message string //When formatter is called in entry.log(), a Buffer may be set to entry Buffer *bytes.Buffer }
2.5 日志打印方法
FieldLogger接口: FieldLogger
定义了所有日志打印的方法
type FieldLogger interface { WithField(key string, value interface{}) *Entry WithFields(fields Fields) *Entry WithError(err error) *Entry Debugf(format string, args ...interface{}) Infof(format string, args ...interface{}) Printf(format string, args ...interface{}) Warnf(format string, args ...interface{}) Warningf(format string, args ...interface{}) Errorf(format string, args ...interface{}) Fatalf(format string, args ...interface{}) Panicf(format string, args ...interface{}) Debug(args ...interface{}) Info(args ...interface{}) Print(args ...interface{}) Warn(args ...interface{}) Warning(args ...interface{}) Error(args ...interface{}) Fatal(args ...interface{}) Panic(args ...interface{}) Debugln(args ...interface{}) Infoln(args ...interface{}) Println(args ...interface{}) Warnln(args ...interface{}) Warningln(args ...interface{}) Errorln(args ...interface{}) Fatalln(args ...interface{}) Panicln(args ...interface{}) }
2.6 logrus实例
实例日志打印方式一
默认实例 (函数)
,即通过logrus包提供的函数(覆盖了FieldLogger
接口的所有方法),直接打印日志。但其实logrus包函数是调用了logrus.Loger
默认实例。
// 直接调用包函数 func main() { logrus.Infoln("info--日志") logrus.Errorln("err--日志") }
实例日志打印方式二
Logger实例(对象)
,它实现了FieldLogger
接口。
func main() { //var loger = logrus.New() var loger = logrus.StandardLogger() // 看源码,本质就是logrus.New() loger.Formatter = &logrus.JSONFormatter{TimestampFormat: "2006-01-02 15:04:05"} loger.Infoln("info--日志") }
实例日志打印方式三
Entry示例(对象)
,它也实现了FieldLogger
接口,是最终是日志打印入口。
- 这里用到了
Field
机制,logrus鼓励通过Field
机制进行精细化的、结构化的日志记录,而不是通过冗长的消息来记录日志。
func main() { logrus.SetFormatter(&logrus.JSONFormatter{TimestampFormat: "2006-01-02 15:04:05"}) // Entry实例 entry := logrus.WithFields(logrus.Fields{ "global": "全局字段-每个日志都会输出", }) entry.WithFields(logrus.Fields{"module": "自定义字段--用户模块"}).Info("info--日志") entry.WithFields(logrus.Fields{"module": "自定义字段--商品模块"}).Error("Error--日志") }
2.7 HOOK机制
- hook即钩子,拦截器。它为logrus提供了强大的功能扩展,如将日志分发到任意地方,如本地文件系统、
logstash
、es
等,或者切割日志、定义日志内容和格式等。hook接口原型如下:
type Hook interface { Levels() []Level //日志级别 Fire(*Entry) error //打印入口(Entry对象) }
Hook - 实现日志切割功能
需要借助于第三方(日志轮转库):github.com/lestrrat-go/file-rotatelogs
和:github.com/rifflock/lfshook
package main import ( "github.com/lestrrat-go/file-rotatelogs" "github.com/rifflock/lfshook" "github.com/sirupsen/logrus" "time" ) // 说明:按时间切割日志文件(2秒创建一个日志文件) func main() { // 保存日志文件名为app_hook开头,2s切换一个日志文件,最多保留5份 hook := NewLfsHook("app_hook", time.Second*2, 5) // 加入钩子 logrus.AddHook(hook) // 先打印一句日志 logrus.Infoln("info---测试开始") // 通过WithFields方式创建log,写入通用内容module log := logrus.WithFields(logrus.Fields{"module": "用户模块"}) // 每隔一秒,调用一次info,一次err,最终只保留5个日志文件 for i := 0; i < 15; i++ { log.Infoln("info--->成功", i) time.Sleep(time.Second) log.Errorln("err--->成功", i) } } // 日志钩子(日志拦截,并重定向) func NewLfsHook(logName string, rotationTime time.Duration, leastDay uint) logrus.Hook { writer, err := rotatelogs.New( // 1 日志文件名字 logName+".%Y%m%d%H%M%S", // 2 日志周期(默认每86400秒/一天旋转一次) rotatelogs.WithRotationTime(rotationTime), // 3 清除历史 (WithMaxAge和WithRotationCount只能选其一) //rotatelogs.WithMaxAge(time.Hour*24*7), //默认每7天清除下日志文件 rotatelogs.WithRotationCount(leastDay), //只保留最近的N个日志文件 ) if err != nil { panic(err) } // 可设置按不同level创建不同的文件名,咱们把6中日志都写到同一个writer中 lfsHook := lfshook.NewHook(lfshook.WriterMap{ logrus.DebugLevel: writer, logrus.InfoLevel: writer, logrus.WarnLevel: writer, logrus.ErrorLevel: writer, logrus.FatalLevel: writer, logrus.PanicLevel: writer, }, &logrus.JSONFormatter{TimestampFormat: "2006-01-02 15:04:05"}) return lfsHook }
Hook - 写入Redis
将日志输出到redis
需要借助于第三方模块:github.com/rogierlommers/logrus-redis-hook
package main import ( logredis "github.com/rogierlommers/logrus-redis-hook" "github.com/sirupsen/logrus" ) func init() { hookConfig := logredis.HookConfig{ Host: "localhost", Key: "test", Format: "v1", App: "my_app_name", Port: 6379, Hostname: "my_app_hostname", DB: 0, TTL: 3600, } hook, err := logredis.NewHook(hookConfig) if err == nil { logrus.AddHook(hook) } else { logrus.Errorf("日志写入redis配置出错: %q", err) } } func main() { logrus.WithFields(logrus.Fields{"module": "用户模块"}).Info("info--日志--写入redis") logrus.WithFields(logrus.Fields{"module": "用户模块"}).Error("Error--日志--写入redis") } // 测试: // 1.启动redis服务: redis-server // 2.监控redis数据: redis-cli monitor
其他Hook
MongoDb
:https://github.com/weekface/mgorusRedis
:https://github.com/rogierlommers/logrus-redis-hookInfluxDb
:https://github.com/abramovic/logrus_influxdbLogstash
:https://github.com/bshuster-repo/logrus-logstash-hook
2.8 Fatal处理
logrus的Fatal
输出,会执行os.Exit(1)
。logrus提供RegisterExitHandler
方法,可以在系统异常时调用一些资源释放api等,让应用正确地关闭。
func main() { logrus.RegisterExitHandler(func() { fmt.Println("发生了fatal异常,执行关闭文件等工作") }) logrus.Warnln("warn测试") logrus.Fatalln("fatal测试") logrus.Infoln("info测试") //不会执行 }
三 Gin中集成
- 将gin框架的日志定向到logrus日志文件
package main import ( "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" "io" "os" ) func init() { // 日志输出格式 logrus.SetFormatter(&logrus.JSONFormatter{TimestampFormat: "2006-01-02 15:04:05"}) // 日志输出路径 logfile, _ := os.OpenFile("./app.log", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644) logrus.SetOutput(io.MultiWriter(os.Stdout, logfile)) // 日志写到控制台和文件中 // Gin日志重定向 gin.DisableConsoleColor() //不需要颜色 // gin的日志写到控制台和日志文件中 gin.DefaultWriter = io.MultiWriter(os.Stdout, logfile) //默认是:os.Stdout //gin.DefaultWriter = logfile } //测试:curl 0.0.0.0:8080/index func main() { log := logrus.WithFields(logrus.Fields{ "module": "用户模块", }) r := gin.Default() r.GET("/", func(c *gin.Context) { log.Infoln("info--->gin日志数据") c.String(200, "ok") }) r.Run(":8080") }
四 logrus线程安全
- 默认情况下,logrus的api都是线程安全的,其内部通过互斥锁来保护并发写。
- 互斥锁在调用hooks或者写日志的时候执行。
- 如果不需要锁,可以调用
logger.SetNoLock()
来关闭。
可以关闭logrus互斥锁的情形:
- 没有设置hook,或者所有的hook都是线程安全的实现。
- 写日志到logger.Out已经是线程安全的了。例如,logger.Out已经被锁保护,或者写文件时,文件是以O_APPEND方式打开的,并且每次写操作都小于4k。
本文作者:oaoa
本文链接:https://www.cnblogs.com/oaoa/p/17149826.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步