字节RPC框架Kitex的日志库klog竟然也这么小巧!

2,756 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情

前言

这篇文章将着重于分析字节跳动开源的RPC框架Kitex的日志库klog的源码,通过对比Go原生日志库log的实现,探究其作出的改进。

为了平滑学习曲线,我写下了这篇分析Go原生log库的文章,希望你可以对比阅读:juejin.cn/post/710379…

本文的分析基于:github.com/cloudwego/kitex/pkg/klog的源码。

klog库的使用

image-20220602161518842

结果如下:

image-20220602103107720

klog.xxx能直接打印日志的原因

image-20220602162933166

通过观察源码,klog包的default.go文件中,封装了三类日志的打印的函数提供直接使用:普通日志、格式化的日志、格式化的Context日志

每一类包含了7个的日志输出级别的函数可使用:InfoDebugNoticeWarnErrorFatalTrace

并且这21个函数中频繁使用到了一个logger实例,只要我们引入klog包,logger就会完成初始化,并且作为默认的log实例。

image-20220602164445885

可以看到logger实例默认的日志打印级别是LevelInfoklog通过常量计数器定义了0~6种日志级别:

image-20220602172735438

FullLogger接口

image-20220602164335103

默认的logger实例是通过defaultLogger结构初始化的,且defaultLogger结构实现了FullLogger接口定义的所有函数(接口定义了上面说的三类,每一类7种日志打印函数)

image-20220602170223047

并且观察defaultLogger结构的属性stdlog,就是Go原生的日志库log定义的Logger类型,因此klog的所有日志操作,最终都是借助Go原生log库实现的。

image-20220602170523008

相当于klog在Go原生log库的基础上对格式化输出日志打印级别作了封装,便于直接使用。

串联一下日志打印函数执行的过程:

  • main函数中调用:klog.Info("一条普通的日志")
  • 进一步调用初始化好的defaultLogger实例(名为logger)的实现自FullLogger接口的函数:logger.Info()
  • 进一步调用ll.logf()函数(下面重点分析

ll.logf()

上面的这三类共21个日志打印函数最终都调用了ll.logf()方法,因此ll.logf()也是klog库的核心函数,看一下代码:

image-20220602172144229

  • 日志过滤:如果调用的打印函数代表的日志级别低于logger实例初始化的日志级别,则不会打印(如默认级别是LevelInfo == 2,则调用klog.Trace()将被过滤)
  • 格式化打印信息存入msg
  • 调用Go原生日志库logOutput()函数,打印日志(这一部分在上一篇分析Go的log库的文章中已经充分讲解)

关于calldepth的问题

calldepth表示调用层数,这里声明了4,是为了配合获取调用日志打印函数的文件名和所在行数。

image-20220602175219202

  • calldepth == 0,表示获取调用runtime.Caller(calldepth)的文件名和行数
  • calldepth == 1,表示获取调用std.Output()的文件名和行数
  • calldepth == 2,表示获取调用ll.logf()的文件名和行数
  • calldepth == 3,表示获取调用logger.Info()的文件名和行数
  • calldepth == 4,表示获取调用klog.Info()的文件名和行数(也就是main.go文件)

基于klog再度进行封装,在打印日志获取文件名时可能会有问题,下面是摘自Kitex文档的一句描述:

image-20220602180622096

猜测原因就是klog的封装,固定了calldepth == 4,确保其在获取文件信息时能定位到main.go文件中,而如果对klog再封几层,会导致calldepth需要更大才能定位到最外层main.go文件,而这个值并不能通过klog的提供的实现进行修改。

在初始化时通过log.New()函数指定了日志输出的位置需要打印的前置信息(文件名、行数、日期)

image-20220602175507724

定制自己的Logger

image-20220602181634895

可以使用klog.SetLogger()来替换掉默认的logger实现,需要传入一个实现了所有FullLogger接口中定义的方法的实例。

值得注意的是:SetLogger()函数并非是并发安全的,这个方法不应该在你使用了默认的defaultLogger定义实例之后再去使用(会覆盖掉默认的logger实例)。

当然完全重新定制比较复杂,大多数时候,我们只需要在默认的logger基础上重定向日志输出或者修改默认日志级别即可:

image-20220602183649139

下面修改日志打印级别为Notice(高于Info),并且重定向日志的输出:

image-20220602184046958

这里指定了日志输出到文件log.txt中,并且因为Info级别低于声明的Notice,因此日志输出操作被忽略:

image-20220602184350533

小结

通过分析,我们发现klogGo原生log库的基础上,进行了精简的二次封装,一定程度上约束了打印的日志的内容为:日期 + 时间微秒级 + 调用文件名 + 所在行数 + 日志级别 + 格式化的日志内容,使用十分便捷。

当然它也提供了SetLogger()方法去供你自己实现logger实例,以满足更多的定制化需求。