Logrus源码阅读(1)--基本用法

2019-07-16
4分钟阅读时长

选择golang日志库时, 使用logrus的主要原因就是因为star比较多, 而且社区活跃度非常高. 在项目使用过程中, 发现logrus的调用入口, 性能, 插件, 自定义插件, 输出格式等都非常优秀, 值得学习一下

整体结构图

logrus

整体来看总共提供两种方式调用:

  1. logrus.Info(“hello logrus”)
  2. 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的整个生命周期