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

网友投稿 344 2022-11-27

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

前言

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

为了平滑学习曲线,我写下了这篇分析Go原生​​log​​​库的文章,​

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

klog库的使用

结果如下:

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

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

每一类包含了7个的日志输出级别的函数可使用:​​Info​​​、​​Debug​​​、​​Notice​​​、​​Warn​​​、​​Error​​​、​​Fatal​​​、​​Trace​​。

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

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

FullLogger接口

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

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

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

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

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

ll.logf()

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

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

关于calldepth的问题

​​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文档的一句描述:

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

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

定制自己的Logger

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

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

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

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

小结

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

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

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:USB-IF推出Type-C认证项目,可抵御恶意硬件攻击
下一篇:springmvc @RequestBody String类型参数的使用
相关文章

 发表评论

暂时没有评论,来抢沙发吧~