因为标准库的日志库缺少Log Level, 如果基于标准库扩展自己的logger也需要花一点时间, 看github的上很多开源项目都使用一个叫logrus的模块, 于是研究了下, 发现它具有高度的灵活性, 而且已经有很多写好了的插件, 比如和logstach对接的插件,所以项目后面就切换日志模块为logrus, 这篇博客就是介绍我们使用logrus时遇到的坑, 以及一些解决之道。

简介

logrus在github上相当受欢迎, 截止目前为止已经6k+的star了, 在很多开源项目中被使用, 比如docker, prometheus等使用该库进行日志记录.
官方声称API完全兼容标准包logger, 项目地址: logrus github 地址, 因此如果你之前项目使用的标准库那么可以无缝迁移.
logrus是一个结构化的、可插拔的日志记录器, 你可以通过插件将你的日志发往任意地方, 比如文件, 标准输出, logstash, influxdb, …。这正式它最吸引人的地方。

使用

logrus有6个日志等级, 分别为: Debug,Info,Warn,Error,Fatal,Panic, 经过简单的配置就可以使用了。
logrus有2种formater, 分别是: JSONFormatter, TextFormatter, 当然如果你觉得都不是你想要的, 可以通过实现Formatter接口定制自己的formater

基础使用

当初始化好logrus后, 可以直接调用Debug等进行日志记录。
WithFields是一个很好用的功能, 它用于记录你这条message的一些元数据信息, 比如你可以记录是有那个访问触发的(request_id)

使用Logger

logger是一种相对高级的用法, 对于一个大型项目, 往往需要一个全局的logger对象, 用于记录项目所有的日志

Hook

官方有很多现成的Hook, 可以实现 记录日志到文件(日志文件轮转), 记录到kafka, 记录到influxDB, 记录到MySQL…

问题

下面这个问题是我们遇到的最郁闷的问题, 因为就连标准库都已经实现的功能,没想到logrus会不提供。

不记录文件名和行号

使用logrus有一个很要命的问题: 不会记录日志打印的行号和文件名, 这在排错时会显得很不方便, 而github上关于log filename and line number的issue已经挂了3年了, 大家解决的办法大致为2类:

  • 自己写hook来记录


  • 通过装饰器模式来包装一层

Yes, unless someone can prove a negligible performance impact I think this should be a hook.
获取文件名和行号的原理

在runtime里面有一个caller函数, 源码里面是这样描述caller的:

简而言之, 我们通过caller可以追踪每一次函数调用的文件和行号信息, 但是我们的函数调用链往往有深, 而我们需要的仅仅是记录日志时的那一层的函数调用信息。如何找到这一层喃?
我们可以把每一层都打印出来, 看看有啥规律:

我们可以看出前面几次调用链, 都被logrus自己使用了, 因此我们仅需要除去logrus调用链的函数, 然后第一个被找到的就是我们记录日志的调用了, 比如最后一个: http/server.go: 71, 我在这里调用logrus: logger.Debug(“msg”)

那么logrus的调用链最多有几层喃?我们执行多少次递归才能找到我们自己的debug call?


1  cmd/service.go: 55 [logger.Debug()] ---> logrus/logger.go: 181 [entry.Debug()] ---> logrus/entry.go: 134 [entry.log()] ---> logrus/entry.go: 98 [entry.Logger.Hooks.Fire()]

2 handler/init.go: 42 [logger.Debugf()] ---> logrus/logger.go: 118 [entry.Debug()]  ---> logrus/entry.go: 182 [logger.releaseEntry()] ---> logrus/entry.go: 134 [entry.log()] ---> logrus/entry.go: 98 [entry.Logger.Hooks.Fire()]


由此我们可以看出当我们使用(Debug, Debugf, Debugln), 底层调用链的长度大致为3~4层, 因此我们最少需要递归5次才能找到我们自己的debug call.

最后找到了记录下来就ok了。

自己实现一个ContextHook

如何实现一个logrus的hook喃?

一个Entry为一条日志记录, 我们的Hook需要做的就是在每一个条的日志记录里面添加调用时的文件名和行号. 核心就是实现自己的Fire方法, 以下是参考实现: