日志框架logrus
介绍
Logrus
是一个结构化日志处理框架,并且api
完全兼容golang
标准库的logger
日志api
, 意味着你可以直接使用Logrus
替换logger
。logrus
具有以下特性:
完全兼容golang
标准库日志模块:logrus
拥有七种日志级别:debug
、info
、warn
、error
、fatal
、panic和Trace
,这是golang
标准库日志模块的API
的超集。如果您的项目使用标准库日志模块,完全可以以最低的代价迁移到logrus
上。
可扩展的Hook
机制:允许使用者通过hook
的方式将日志分发到任意地方,如本地文件系统、标准输出、logstash
、elasticsearch
或者mq
等,或者通过hook
定义日志内容和格式等。
可选的日志输出格式:logrus
内置了两种日志格式,JSONFormatter
和TextFormatter
,如果这两个格式不满足需求,可以自己动手实现接口Formatter
,来定义自己的日志格式。
Field
机制:logrus
鼓励通过Field
机制进行精细化的、结构化的日志记录,而不是通过冗长的消息来记录日志。
logrus
是一个可插拔的、结构化的日志框架。
1 安装 1 go get github.com/sirupsen/logrus
2 第一个示例
logrus与golang
标准库日志模块完全兼容,因此您可以使用log "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 39 40 package mainimport ( "os" log "github.com/sirupsen/logrus" ) func main () { log.SetLevel(log.DebugLevel) log.SetOutput(os.Stdout) log.SetFormatter(&log.JSONFormatter{}) log.Debug("调试信息" ) log.Info("提示信息" ) log.Warn("警告信息" ) log.Error("错误信息" ) log.WithFields(log.Fields{ "user_id" : 1001 , "ip" : "192.168.0。100" , "request_id" : "ec2bf8e55a11474392f8867e92624e04" , }).Info("用户登陆失败." ) } -----------------------------------输出结果如下------------------------------------ {"level" :"debug" ,"msg" :"调试信息" ,"time" :"2020-03-06T23:52:41+08:00" } {"level" :"info" ,"msg" :"提示信息" ,"time" :"2020-03-06T23:52:41+08:00" } {"level" :"warning" ,"msg" :"警告信息" ,"time" :"2020-03-06T23:52:41+08:00" } {"level" :"error" ,"msg" :"错误信息" ,"time" :"2020-03-06T23:52:41+08:00" } {"ip" :"192.168.0。100" ,"level" :"info" ,"msg" :"用户登陆失败." ,"request_id" :"ec2bf8e55a11474392f8867e92624e04" ,"time" :"2020-03-06T23:52:41+08:00" ,"user_id" :1001 }
3 Logger
logger
是一种相对高级的用法,对于一个大型项目,往往需要一个全局的logrus
实例,即logger
对象来记录项目所有的日志。如
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 package mainimport ( "github.com/sirupsen/logrus" "os" ) var log = logrus.New()func main () { log.Out = os.Stdout log.Formatter = &logrus.JSONFormatter{} log.WithFields(logrus.Fields{ "name" : "tom" , "address" : "chengdu" , }).Info("Make a little progress every day" ) } -----------------------------输出结果如下------------------------------- {"address" :"chengdu" ,"level" :"info" ,"msg" :"Make a little progress every day" ,"name" :"tom" ,"time" :"2020-03-07T00:01:31+08:00" }
4 Fields
Logrus
鼓励通过日志字段进行谨慎的结构化日志记录,而不是冗长的、不可解析的错误消息。例如下面的记录日志的方式:
1 log.Fatalf("Failed to send event %s to topic %s with key %d" , event, topic, key)
在logrus中不太提倡,logrus鼓励使用以下方式替代之: 1 2 3 4 5 log.WithFields(log.Fields{ "event" : event, "topic" : topic, "key" : key, }).Fatal("Failed to send event" )
上面的WithFields API
可以规范使用者按照其提倡的方式记录日志。但是WithFields
依然是可选的,因为某些场景下,使用者确实只需要记录仪一条简单的消息。
5 Hooks
logrus
最令人心动的功能就是其可扩展的HOOK
机制了,通过在初始化时为logrus
添加hook
,logrus
可以实现各种扩展功能。
5.1 Hook接口定义
logrus
的hook
接口定义如下,其原理是每此写入日志时拦截,修改logrus.Entry
。
1 2 3 4 5 6 type Hook interface { Levels() []Level Fire(*Entry) error }
5.2 简单Hook定义示例
一个简单自定义hook
如下,DefaultFieldHook
定义会在所有级别的日志消息中加入默认字段appName="myAppName"
。
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" ] = "MyAppName" return nil } func (hook *DefaultFieldHook) Levels() []log.Level { return log.AllLevels }
5.3 Hook简单使用
hook
的使用也很简单,在初始化前调用log.AddHook(hook)
添加相应的hook
即可。
logrus
官方仅仅内置了syslog 的hook
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package mainimport ( log "github.com/sirupsen/logrus" "gopkg.in/gemnasium/logrus-airbrake-hook.v2" logrus_syslog "github.com/sirupsen/logrus/hooks/syslog" "log/syslog" ) func init () { log.AddHook(airbrake.NewHook(123 , "xyz" , "production" )) hook, err := logrus_syslog.NewSyslogHook("udp" , "localhost:514" , syslog.LOG_INFO, "" ) if err != nil { log.Error("Unable to connect to local syslog daemon" ) } else { log.AddHook(hook) } }
6 将日志保存到文件
我们如何将日志保存到本地文件。前面的例子我们知道,可以通过SetOutput
函数设置将日志保存到什么地方,下面演示如何将日志保存到文件中。
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 package mainimport ( "github.com/sirupsen/logrus" "os" ) var log = logrus.New()func main () { file, err := os.OpenFile("logrus.log" , os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666 ) if err == nil { log.SetOutput(file) } else { log.Info("打开日志文件失败,默认输出到stderr" ) } log.Debug("调试信息" ) log.Info("提示信息" ) log.Warn("警告信息" ) log.Error("错误信息" ) }
7 记录函数名
如果你希望将调用的函数名添加为字段,请通过以下方式设置:
1 log.SetReportCaller(true )
这会将调用者添加为method
,如下所示:
1 2 {"name" :"Tom" ,"level" :"fatal" ,"method" :"github.com/sirupsen/arcticcreatures.migrate" ,"msg" :"a penguin swims by" , "time" :"2020-03-07 23:57:38.562543129 -0400 EDT" }
8 设置日志级别
你可以在Logger
上设置日志记录级别,然后它只会记录具有该级别或以上级别任何内容的条目 日志级别大小说明:Panic>Fatal>Error>Warn>Info>Debug>Trace,举例如下:
1 2 log.SetLevel(log.InfoLevel)
如果你的程序支持debug
或环境变量模式,设置log.Level = logrus.DebugLevel
会很有帮助。
9 日志本地文件分割
logrus
本身不带日志本地文件分割功能,但是我们可以通过file-rotatelogs
进行日志本地文件分割.每次当我们写入日志的时候,logrus
都会调用file-rotatelogs
来判断日志是否要进行切分。关于本地日志文件分割的例子网上很多,这里不再详细介绍,奉上代码
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 package mainimport ( "github.com/lestrrat-go/file-rotatelogs" "github.com/pkg/errors" "github.com/rifflock/lfshook" log "github.com/sirupsen/logrus" "path" "time" ) func ConfigLocalFilesystemLogger (logPath string , logFileName string , maxAge time.Duration, rotationTime time.Duration) { baseLogPath := path.Join(logPath, logFileName) writer, err := rotatelogs.New( baseLogPath+"-%Y%m%d%H%M.log" , rotatelogs.WithMaxAge(maxAge), rotatelogs.WithRotationTime(rotationTime), ) if err != nil { log.Errorf("config local file system logger error. %+v" , errors.WithStack(err)) } lfHook := lfshook.NewHook(lfshook.WriterMap{ log.DebugLevel: writer, log.InfoLevel: writer, log.WarnLevel: writer, log.ErrorLevel: writer, log.FatalLevel: writer, log.PanicLevel: writer, }, &log.TextFormatter{DisableColors: true }) log.SetReportCaller(true ) log.AddHook(lfHook) } func ConfigLocalFilesystemLogger1 (filePath string ) { writer, err := rotatelogs.New( filePath+"-%Y%m%d%H%M.log" , rotatelogs.WithLinkName(filePath), rotatelogs.WithMaxAge(time.Second*60 *3 ), rotatelogs.WithRotationTime(time.Second*60 ), ) if err != nil { log.Fatal("Init log failed, err:" , err) } log.SetReportCaller(true ) log.SetOutput(writer) log.SetLevel(log.InfoLevel) } func main () { ConfigLocalFilesystemLogger("D:/benben" , "sentalog" , time.Second*60 *3 , time.Second*60 ) for { log.Debug("调试信息" ) log.Info("提示信息" ) log.Warn("警告信息" ) log.Error("错误信息" ) time.Sleep(500 * time.Millisecond) } }
10 其他注意事项 10.1 Fatal处理
和很多日志框架一样,logrus
的Fatal
系列函数会执行os.Exit(1)
。但是logrus
提供可以注册一个或多个fatal handler
函数的接口logrus.RegisterExitHandler(handler func() {} )
,让logrus
在执行os.Exit(1)
之前进行相应的处理。fatal handler
可以在系统异常时调用一些资源释放api
等,让应用正确的关闭。
10.2 线程安全
默认情况下,logrus的api
都是线程安全的,其内部通过互斥锁来保护并发写。互斥锁工作于调用hooks
或者写日志的时候,如果不需要锁,可以调用logger.SetNoLock()
来关闭之。可以关闭logrus
互斥锁的情形包括:
没有设置hook
,或者所有的hook
都是线程安全的实现。
写日志到logger.Out
已经是线程安全的了,如logger.Out
已经被锁保护,或者写文件时,文件是以O_APPEND
方式打开的,并且每次写操作都小于4k。
参考资料 日志框架logrus
Go进阶10:logrus日志使用教程