只要留意一下大项目的源码,你会发现,几乎无一例外的包括一个log模块。它的功能很直观:记录一些程序运行时信息,多数情况是用来辅助debug的。大项目都有一套的log的函数,在它的基础上开发,调用它提供的Log函数就行了,比如linux内核、apache等。也有开源log函数库,可以直接拿过用。这里,我们并不鼓励重新发明轮子,但在少数情况下,确实不得不编写自己的log函数。下面是对以前的经验的总结,和大家交流一下。
1. 按重要程度过滤log。在调试程序时,当然希望有较多的调试信息,能够为我们分析提供帮助,但使用太多Log是有代价的,它不但要占用空间,还要占用运行时间。我们不希望在Release版本中有太多的Log信息,那会浪费用户的资源。通常的做法是,把Log信息按重要程度分成不同的种类,在不同的情况下,打印不同种类的信息。错误的种类,一般分为严重错误、错误、警告、调试等几个级别。
2. 按模块过滤Log。对于大的项目的,可能由数十个甚至上百个模块组成。完成一个复杂的任务,需要在不同模块间切换好几次,得到的Log信息比较庞杂。多数情况下,我们的注意力,都放在几个容易出错的焦点模块上,Log信息太杂,要找出焦点模块中的Log会比较累,输出无用的Log也会增加系统的开销。这时,我们可以按模块过滤,只留下所关注模块的Log。可以设置一个过滤掩码(mask),这个模块占用一位,置1的模块表示打Log开关。
3. Log的格式。Log的格式最好能够统一起来,让人感觉比较舒服,查看时也有个好的心情。同时最好能与调试器配合起来,比如,在VC中,若按文件名(行号): 信息的格式输出,双击该信息,VC自动跳到打印该Log的代码位置。
4. Log的内容。Log的内容并不无固定规则,一般来说,Log提供下列信息,对调试会有一些帮助:
a) 模块名。
b) 打印该Log的位置,如文件名、行号、函数名等。
c) 打印该Log的时间。
d) 对由多个模块组成的项目,若这些模块又并不同步发布时,版本不匹配会引发一些问题,这就有必要打印出各模块的build时间或者版本信息。
5. 能重定向log的内容。常见的做法是把log信息打印到屏幕上,这也不尽然,特别对于非PC平台的情况,有的输出到串口,有的保存到文件,有的输出到socket,输出的目标多种多样。所以,在设计一个通用的log函数库时,最好考虑到这些需求,抽象出一套输出接口是较为理想的做法。
6. 动态配置Log。若每次配置Log的规则,都要重新编译程序,这常常是不能接受的。在设计时要考虑提供动态配置功能。动态配置的方法有多种,可以通过命令行参数,可以通过环境变量,可以通过配置文件,也可以提供一个设置界面,这要根据具体的情况而定。配置的内容常常包括:过滤级别、模块掩码、输出格式和输出目标等。
7. 常用的宏。在C语言中,有几个宏,可能对于编写Log函数库有用处:
a) __FILE__ 当前文件名。
b) __LINE__ 当前行号。
c) __func__ 当前函数,好像新标准才支持。
d) __DATE__ 编译日期。
e) __TIME__ 编译时间。
8. 参数可变。一般来说,Log函数的参数都是可以变化的,这很容易实现,C语言支持参数个数可变的函数。有时,为了方便,会定义一些宏包装一下Log函数,可往往不太走运,因为C语言新标准里才支持参数可变的宏,目前常用的gcc版本都支持,而VC这个冤大头却不支持。若非要使用变参宏,一定要考虑可以移植性问题。