选择golang日志库时, 使用logrus的主要原因就是因为star比较多, 而且社区活跃度非常高. 在项目使用过程中, 发现logrus的调用入口, 性能, 插件, 自定义插件, 输出格式等都非常优秀, 值得学习一下
整体结构图
整体来看总共提供两种方式调用:
- logrus.Info("hello logrus")
- logrus.WithField(logruns.Fields{"key1":"v1"}).Info("hello logrus")
这些函数都在exported.go
文件中. 当然为了提供不同级别输出日志的功能, 里面实现了各种各样的print
函数, 如: Infof
, Error
, Errorf
, Panic
等等
在直接使用logrus等情况下exported.go
是唯一入口, 但是我们可以简单封装一下, 跟项目框架更加贴合, 这个留在后面用具体例子来解释这么做的原因和好处
简单介绍使用方法
普通用法
package main
import (
log "github.com/sirupsen/logrus"
)
func main() {
log.Info("A walrus appears")
}
time="2019-07-16T22:51:31+08:00" level=info msg="hello logrus"
注意log "github.com/sirupsen/logrus"
这里将logrus的别名设置为log, 然后就直接调用了log.Info. 假如你的项目现在使用的标准库log, 则可以无缝迁移到logrus上, 因为标准库实现的print函数较少, logrus全部已经实现, 只需要简单引入这个别名即可
WithFields
由于logrus不建议下面的用法:
log.Fatalf("Failed to send event %s to topic %s with key %d")
因为logrus鼓励结构化的日志输出, 上面的用法就非常的不人性化, 不美观. 应该改成下面的方式:
log.WithFields(log.Fields{
"event": event,
"topic": topic,
"key": key,
}).Fatal("Failed to send event")
但是根据实际使用过程中发现, 一般要使用WithFields输出日志字段时, 那些字段一般都是公共字段, 比如: request_id, token等等, 程序里到处打印WithFields也不是个优美的办法(后续会解释怎么做)
所以尽管logrus
不建议我们使用Printf
, 但是程序该需要用到Printf
的地方还是需要的
设置打印格式
logrus自带两种方式的输出格式: 纯文本和JSON格式的.
JSONFormatter
func main() {
log.SetFormatter(&log.JSONFormatter{})
log.Info("hello logrus")
}
{"level":"info","msg":"hello logrus","time":"2019-07-17T22:47:14+08:00"}
TextFormatter
默认情况下就是TextFormatter
, 默认情况下是带颜色输出的. 当然也不是任何时候都输出带颜色的结果, 取决于在终端输出并且不是运行在windows系统, 或者是否设置过ForceColors=true
, 如果设置了就会按照有颜色的方式输出.
程序会在启动的时候检测是否是终端运行, 具体的实现就是terminal_check_(OS).go
, 具体实现后续关于TextFormatter的具体实现再看
func main() {
log.SetFormatter(&log.TextFormatter{})
log.Info("hello logrus")
}
INFO[0000] hello logrus
也可以禁用
func main() {
log.SetFormatter(&log.TextFormatter{
DisableColors: true,
})
log.Info("hello logrus")
}
time="2019-07-17T23:44:42+08:00" level=info msg="hello logrus"
同时, 你可以根据自己的实际需求, 去定制自己的Formater
, 只需要实现Format
方法即可
设置调用log的位置
func main() {
log.SetFormatter(&log.TextFormatter{
DisableColors: true,
})
log.SetReportCaller(true)
log.Info("hello logrus")
}
time="2019-07-18T10:40:21+08:00" level=info msg="hello logrus" func=main.main file="/Users/haohongfan/goproject/test/logrus_test/main.go:33"
但是请注意:
Note that this does add measurable overhead - the cost will depend on the version of Go,
but is between 20 and 40% in recent tests with 1.6 and 1.7.You can validate this in your environment
via benchmarks: go test -bench=.*CallerTracing
也就设置这个是有性能问题的, 生产环境是一定不能启动用, 其实也没有必要, 我们并不关心是哪一行打印的(如果你的日志确实需要靠这个来确定的话, 那你的日志是需要优化一下的)
设置日志级别
logrus日志一共7级别, 从高到低: panic, fatal, error, warn, info, debug, trace.
在生产环境时选择打印Info以上级别的日志, 就可以log.SetLevel(log.InfoLevel), 那么Debug, Trace就不会打印出来. 源码实现这个功能很简单, 就是判断Print函数的级别是否大于SetLevel的值
log.SetLevel(log.ErrorLevel)这个函数要求传入的参数是Level
类型的值(其实也就是uint32, type Level uint32), 我们在封装我们代码时, 肯定要定义panic等这些级别. logrus本身提供将panic
转换成PanicLevel
的函数和获取xxLevel
对应的字符串. 这些都封装在logrus.go
里面
func main() {
// log.SetLevel(log.ErrorLevel)
level, _ := log.ParseLevel("info")
log.SetLevel(level)
log.Info("hello logrus")
fmt.Println(log.ErrorLevel)
}
error
time="2019-07-18T11:41:02+08:00" level=info msg="hello logrus"
Hook
Hook
是一大特色, 也给logrus留下各种各样的扩展机会. 比如: lfshook
, dingrus
等
你可以根据自己的特殊需求扩展自己的Hook, 只需要简单实现Levels() []Level
, Fire(*Entry) error
即可. logrus提供一个syslog
, test
的插件, 同时github上可以找到很多
后面说源码的时候, 我会选择lfshook作为例子进行分析其实现细节, 同时我们也会选择一个功能实现一个
日志的文件输出, 切分, 删过期文件
logrus本身不提供这样的功能, 需要借助第三方插件lfshook进行
相对高级的用法
前面说到程序里到处log.WithFields{log.Field{xxx}}是一种比较不好的用法, 故我们开发的框架在集成logrus的时候要简单封装一下. logrus README也有提到
requestLogger := log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})
requestLogger.Info("something happened on that request") # will log request_id and user_ip
requestLogger.Warn("something not great happened")
下面说具体如何操作, 可以参考bilibili sniper
比如跟gin
的结合使用, 这是我的项目的一段实际的代码. 在log目录下创建log.go
// Entry ling-nest log Entry
func Entry(ctx *gin.Context) *logrus.Entry {
return logrus.WithFields(logrus.Fields{
"device_type": ctx.Value("device_type"),
"channel": ctx.Value("channel"),
"license": ctx.Value("license"),
"v4": ctx.Value("V4"),
})
}
实际使用时: log.Entry(context).Info("xxxxxx")
总结
第一篇关于logrus源码阅读主要是为了介绍相关的用法. 从下面开始将正式进入源码阶段. 下一篇主要根据源码介绍logrus的整个生命周期
有疑问加站长微信联系(非本文作者)