LOG Helper

来源:互联网 发布:知乎 希尔瓦娜斯 7.0 编辑:程序博客网 时间:2024/06/06 04:27

在终端打印LOG是最基础、最易用、某种程度上也是最经济的Debug方式。但是Base的规模大了,管理LOG也不是一件轻松的事情。

最近项目组在Linux内核里写一个新的mach,和该mach专用的驱动。面对一坨一坨的日益增加的代码,Debug时想要从LOG上一眼找出问题点,以及想要更方便地禁止或启用某个module的LOG,越来越成为我逃避不了的需求。所以我花了几个小时的时间,把最近对LOG系统的需求总结了一下,发现其实我只需要一个LOG Helper头文件。

 

 

每天帮我清理纸篓的保洁阿姨也能看出,其实所谓的helper,就是那个_log_print宏函数,源码中其他内容都是这个helper的helper,用时下流行的话说,都是_log_print的助理。

 

__func__、__LINE__和__FILE__这几个变量是编译器内建符号,我们在代码中只要当做全局变量即可。

__func__:字符串,保存当前语句所在函数的函数名。

__LINE__:整型数,保存当前语句在源文件中的行号。

__FILE__:字符串,保存当前语句所在源文件的文件名(相对于编译路径)。

 

由于__FILE__保存的文件名有可能包含很长的相对路径(如果你的Project里有某些足够深的路径),所以打印出来的LOG可能不是你想要的效果。比如我,其实只要看到basename就足够明确了。所以我写了一个取得源文件的basename的函数。

 

__basename_()其实就是一个简单的字符串处理函数,查找输入字符串的最后一个'/'字符,然后返回下一个字符的指针,当然,要是没有发现'/',那么就直接返回输入字符串的指针。为了inline,我已经尽可能少地使用变量和循环,并且写得非常紧凑,所以这个函数基本上失去了可读性。总之它的效果是:

输入"/where/you/put/yourfile.c",输出"yourfile.c";

输入"alsoyourfile.c",输出"alsoyourfile.c"

 

打印LOG在大部分情况下被用来输出变量的信息,以便程序员了解程序的运转情况,这种情况下,LOG本身的性能并没有什么影响。但要是涉及到性能方面的调试,LOG本身的性能就是敏感的了。如果每一次打印LOG都要做一次获取basename的计算,那么即使只是把LOG打印在内存里,也是非常不划算的。

 

为了响应党中央节能减排的号召,我加入了一个静态变量__f_,用来保存__basename_函数计算得出的值。在需要打印源文件名的时候,我们先看看__f_这个变量的值,如果它不是NULL,就直接打印它;如果它是NULL,说明是头一次打印这个文件名,此时才调用一次__basename_函数。

 

这里,__f_和__basename_都使用到了C语言中static关键字的一个“名不副实”的功能:作用域限定。现在的程序员一定不会陌生,因为时下几乎所有IT公司的C程序员面试题的第一题都是“请说出C语言static关键字的作用”。具体来说就是,被static修饰的全局变量和函数,就只在定义它的这个.c文件内可见。C语言以.c文件为编译单元。编译时,每个编译单元都会导出自己的符号表,其中包括全局变量和函数。其他编译单元可以用extern关键字或者引用头文件等方式,使用其他编译单元中定义的符号。如果两个编译单元导出的外部符号表中有相同的元素,那么编译器就会在编译期报错,告诉你有重复定义(redefined)的符号,无论是变量还是函数。为了避免重复定义,或者说为了净化命名空间,用static关键字修饰“per-编译单元”的“局部全局变量”是一个好主意。——另外,在头文件中定义static变量和函数,也就相当于在引用这个头文件的.c文件中定义它,所以我给这两个符号加了好几个下划线,目的就是不容易跟引用loghelper.h的源文件中的符号同名。

 


注:请说出C语言static关键字的作用

1,生命周期限定,作用于函数中定义的变量。在函数中定义的变量一般被存放在函数的栈中,栈随着函数的调用而创建,随着函数的退出而释放。但是如果在函数中定义一个被static关键字修饰的变量,则该变量被放在静态数据区(即所谓的静态变量,另外一提,所有的全局变量也都是静态变量),这也是static关键字名字的由来。静态数据区在main函数第一行指令执行之前就被初始化完毕,并且直到程序所有线程结束后才被释放。

2,作用域限定,作用于全局变量和函数。参考上文。


 

最后提一下宏定义LOG_PROMPT。如果一个.c源文件在#include "loghelper.h"之前定义了LOG_PROMPT,那么打印出来的LOG就会以那个LOG_PROMPT为前缀提示符。不过呢,这个LOG_PROMPT是个可有可无的东东,你也可以不定义它。如果编译器编译到_log_print之前,都没有在其他地方发现LOG_PROMPT的定义,那就把它定义成空,于是LOG的开头就直接是源文件名。

 

有了这个loghelper.h头文件,在代码中定义一些更高层的LOG宏就方便许多。比如在某个.c文件中:

 

 

我定义了info和error这两个级别,你当然可以定义更多更细致的log级别,根据具体情况在代码中使用。

当你的模块在调试时,想要屏蔽掉某个级别的LOG,只需要设置内核参数logleve=即可。随着loglevel数值的减小,低重要性的LOG就会在终端消失(不用担心,它们不是真的消失,用dmesg依然能够找到)。

当你的模块通过了各种测试,准备release时,只需要将#define _TEST_DEBUG_注释掉即可。这时它们就真的消失了,因为它们不只是被你“关掉”了,而且是没有任何LOG代码参与编译。

 

原创粉丝点击