本 Chat 对日志系统和基于 Linux 的应用层日志系统设计方法进行了详细的介绍,并给出了完整的 C 代码实现。具体而言,本 Chat 包含以下内容:
本文适合有一定编程经验的软件研发人员阅读。
本文已参加 GitChat「我的技术实践」有奖征文活动,活动链接: GitChat「我的技术实践」有奖征文活动
如果世界上有一个人能够保证第一次写出来的代码就是百分之百正确的,那么毫无疑问,他一定是世界上最优秀的程序员,没有之一。为什么要求代码写好过后要进行充分的自测呢?就因为是人皆会犯错,是程序就会有 Bug。作为一名合格的软件开发人员,必须要学会对程序进行调试。
一般而言,对程序的调试有以下几种方法:
第一,凭肉眼看。在开发阶段,我们编写的每一行代码都需要用我们的“火 眼金睛”多审查几遍。如果要问,最好的代码调试工具是什么?我认为是人眼。 不管是代码还是文档,在用工具检查之前,都需要先过了我们眼睛这一关。
第二,对代码进行编译,以发现语法错误。编译器能够帮助我们发现代码中存在的语法错误,但对于那些隐蔽性的错误(如逻辑错误等)无能为力。
第三,用代码检查工具(如 Pclint 等)来走查代码。在学校的时候,我们一般认为只要程序能够运行就可以了。但在实际的软件开发项目中,程序能够跑起来,并不表示它就毫无问题。用代码检查工具可以发现很多编译器无法发现的错误,如变量定义了未引用、不同数据类型之间相互赋值、函数未声明便被调用等。
第四,对代码进行调试。对于运行正常而输出结果不正确的程序,我们可以用设置断点并进行单步跟踪调试的方法来发现其中存在的问题。例如,在 Visual Studio 里面,可实现对代码的单步调试,并输出变量在某一步产生的值,可据此判断程序逻辑正确与否。
第五,对程序的日志文件进行分析。对代码的单步调试只在代码行数较少的时候比较适用,但在实际的软件项目中,代码少则几千行,多则数万行,用单步调试的方法显然不恰当。为了解决大程序文件代码调试问题,日志系统应运而生。在程序中的重要地方打印日志,之后对产生的日志文件进行分析,可找到对应代码的问题。因此,日志文件分析成了大型软件项目中代码调试的主要手段。
以下对日志系统相关内容进行介绍。
在各类软件系统中大量使用日志,日志能够起到“按图索骥”的作用,它对于故障定位和系统正常运行维护具有举足轻重的作用。
日志文件是程序中写日志函数产生的记录程序执行情况的文件。在程序恰当的地方调用该函数,可对整个程序的运行状况有一个全面的了解,方便对程序的跟踪调试。
事有轻重缓急,日志信息也有重要与不重要之分。一般按照重要程度,将日志等级分为几类。在作者参与过的软件开发项目中,共有 6 个等级,如图 1.1 所示。
图 1.1 日志等级
从图 1.1 可以看到,从上到下,日志等级不断降低(即日志的重要程度不断降低)。开发人员根据所要打印日志的重要程度采用不同的日志等级。
由于不同软件程序行数、部署情况、实现功能等的差别,对日志打印的要求也不尽相同,因此需要由配置来控制日志的产生数量和显示情况等。
在后面日志系统的实现部分,将对日志配置进行详细的描述。
日志编写的总体原则是简单清晰、便于排查问题。
日志编写基本原则
日志编写基本要求
日志输出位置要求
注意事项
日志系统在软件程序中占有非常重要的地位,日志文件是排查程序问题的主要依据,是程序调试的利器。因此,熟练掌握日志系统的设计和实现方法,及准确通过日志文件来定位程序问题,是对一个合格的软件研发工程师的基本要求。
有了对日志系统的基本认识之后,我们接下来设计并实现一个基于 Linux 的应用层日志系统。
设计日志系统的初衷是为了监测软件运行状况及排查程序故障。在日志文件中存放程序流程中的一些重要信息,包括:变量值、函数返回值及其执行情况、脚本执行及调用情况等。通过阅读日志文件,研发或维护人员能够较快地跟踪程序流程,并定位程序问题。
一个完整的日志系统包括三大部分:软件程序、配置文件和日志文件,它们之间的关系如图 2.1 所示。
图 2.1 日志系统完整结构
从图 2.1 可以看出,软件程序处于主导地位,它会从配置文件中读取相关的配置信息,并将程序运行过程中的信息输出到日志文件中。
本节首先对日志文件和配置文件进行介绍,然后对生成日志文件代码的重要程序流程进行说明,最后给出完整的基于 Linux 的一个简单应用层日志系统的 C 代码实现。
本节对日志文件和配置文件进行说明。
日志文件是程序运行过程中生成的文件,里面记录了一些重要的程序状态信息,通过阅读该文件的内容,研发人员能够跟踪程序流程并快速定位程序问题。
对于日志文件的命名,不同的软件开发项目有不同的规定。一般说来,日志文件都是以 log 作为后缀,如本文中的日志文件命名为:WriteLog.log。
对于日志文件中记录的每条日志信息的格式,不同的软件也会有所不同。在本文中,日志信息的格式有以下两种(具体使用哪一种通过配置文件中的配置项决定)。
第一种:
[日志生成时间][文件名][函数名][代码行][日志等级]日志具体信息
第二种:
[日志生成时间][日志等级]日志具体信息
配置文件用于控制日志文件的输出信息,包括:日志等级、输出日志位置信息、日志存放目录等。
在本文中,为了便于程序处理,对配置文件的命名及内容格式约定如下:
[]
括起来,注释的内容以分号 ;
开头,配置项采用等号 =
进行赋值。本文中使用的配置文件 Config.ini 包括了两部分信息,如下所示:
[EMPLOYEEINFO]; the name of employeeEmployeeName=; the age of employeeEmployeeAge=[LOG] ; LogLevel, 0-Fatal 1-Error 2-Warn 3-Info 4-Trace 5-DebugLogLevel=; If output position info(filename/functionname/linenum), 1-Yes 0-NoLogPosition=; Log dirLogDir=
如上所示的配置文件,
“EMPLOYEEINFO”段的内容用于对我们编写的日志系统进行测试,它是指员工信息,包含员工姓名和员工年龄两个配置项。程序会将员工姓名和员工年龄读入,并输出到日志文件中。
生成日志文件的总体程序流程如图 2.2 所示。
图 2.2 生成日志文件的总体程序流程
可以看到,生成日志文件包括两大程序流程,一是读取配置文件,一是根据配置项生成日志文件。
在实际的软件开发项目中,为了在程序的不同地方打印不同的日志,要将生成日志的代码封装为函数,作为 API 供程序调用。
如果程序没有成功生成日志,那么就不要让其执行后续流程,而是要查找问题的原因,直到日志文件生成正常为止。
以下介绍配置文件读取和日志文件生成过程中的一些重要程序流程。
1. 配置文件读取操作总体流程
实现配置文件读取操作的总体程序流程如图 2.3 所示。
图 2.3 配置文件读取操作总体流程
从上图可以看出,配置文件的读取操作比较简单,遵循的步骤是:打开配置文件 —> 匹配段名 —> 匹配配置项名 —> 获取配置项值。通过阅读后面给出的日志系统实现代码,大家对配置文件读取流程会有更深入的理解。
2. 获取配置项值的程序流程
获取配置项值的程序流程如图 2.4 所示。
图 2.4 获取配置项值的程序流程
如图所示,程序首先找到段名,然后在该段之下去匹配配置项名,最后获取配置项的值。
在本文中,配置文件存放的全路径为 /home/zhouzhaoxiong/test/WriteLog。值得注意的是,Linux 下目录之间的分隔符为 /
,这个与 Windows 下的分隔符有区别。
该操作的程序流程如图 2.5 所示。
图 2.5 向日志文件中写入日志信息的程序流程
如图所示,该操作的流程具体为:
基于以上程序流程,本文用 C 代码实现了一个简单日志系统,笔者已将代码放到了 GitHub 上,链接为:
https://github.com/zhouzxi/WriteLog
对于程序的说明如下:
1. WriteLog.h 和 WriteLog.c 分别是日志系统的头文件和源代码文件,Test.c 是对日志系统进行测试的代码文件。
2. WriteLog.c 中,各函数的功能如下:
3. WriteLog.h 中,定义了一个函数宏 WRITELOGFILE 用来供其他程序调用完成写日志的操作;其他程序模块在调用 WRITELOGFILE 的时候,只需要传入日志等级和需要写入日志文件的具体信息即可。
测试程序见 Test.c 文件,为了测试本日志系统的功能是否正确,在 main 函数中设计了以下三类日志信息:
将日志系统代码、测试代码和配置文件放到 Linux 系统上,为了便于说明,将这些文件都存放到一个目录(本文中的存放目录为 /home/zhouzhaoxiong/test/WriteLog)下,并且日志文件也生成在该目录下。文件目录布局如图 3.1 所示。
图 3.1 文件目录布局
使用 gcc -g -o WriteLog Test.c WriteLog.c
命令对程序进行编译,生成“WriteLog”文件。
下面测试时使用 ./WriteLog
命令运行程序。
测试用例一
将配置文件中的各个配置项的值设置如下:
[EMPLOYEEINFO];the name of employeeEmployeeName=wang;the age of employeeEmployeeAge=25[LOG] ;LogLevel, 0-Fatal 1-Error 2-Warn 3-Info 4-Trace 5-DebugLogLevel=5;If output position info(filename/linenum), 1-Yes 0-NoLogPosition=1;Log dirLogDir=/home/zhouzhaoxiong/test/WriteLog
则生成的日志文件“WriteLog.log”的内容为:
[2019-09-09 18:03:51.537][Test.c][main][0045][INFO]Version [1.0], Build time[Sep 9 2019 18:03:15].[2019-09-09 18:03:51.537][Test.c][main][0049][FATAL]The Fatal log info![2019-09-09 18:03:51.537][Test.c][main][0053][ERROR]The Error log info![2019-09-09 18:03:51.537][Test.c][main][0057][WARN]The Warn log info![2019-09-09 18:03:51.537][Test.c][main][0061][INFO]The Info log info![2019-09-09 18:03:51.537][Test.c][main][0065][TRACE]The Trace log info![2019-09-09 18:03:51.537][Test.c][main][0069][DEBUG]The Debug log info![2019-09-09 18:03:51.537][Test.c][GetEmployeeInfo][0108][INFO]EmployeeName is wang, EmployeeAge is 25
对照配置文件和日志文件,我们可以看到,“LogLevel”设置的值是为 5,因此只有日志等级不低于 5 的日志(即 Fatal/Error/Warn/Info/Trace/Debug 等级的日志)被输出到了日志文件中;“LogPosition”设置的是为 1,因此在日志文件中显示了“文件名/函数名/代码行数”的信息。
测试用例二
将配置文件中的各个配置项的值设置如下:
[EMPLOYEEINFO];the name of employeeEmployeeName=li;the age of employeeEmployeeAge=28[LOG] ;LogLevel, 0-Fatal 1-Error 2-Warn 3-Info 4-Trace 5-DebugLogLevel=4;If output position info(filename/linenum), 1-Yes 0-NoLogPosition=0;Log dirLogDir=/home/zhouzhaoxiong/test/WriteLog
则生成的日志文件“WriteLog.log”的内容为:
[2019-09-09 18:05:18.747][INFO]Version [1.0], Build time[Sep 9 2019 18:03:15].[2019-09-09 18:05:18.747][FATAL]The Fatal log info![2019-09-09 18:05:18.747][ERROR]The Error log info![2019-09-09 18:05:18.747][WARN]The Warn log info![2019-09-09 18:05:18.747][INFO]The Info log info![2019-09-09 18:05:18.747][TRACE]The Trace log info![2019-09-09 18:05:18.747][INFO]EmployeeName is li, EmployeeAge is 28
对照配置文件和日志文件,我们可以看到,“LogLevel”设置的值是为 4,因此只有日志等级不低于 4 的日志(即 Fatal/Error/Warn/Info/Trace 等级的日志)被输出到了日志文件中;“LogPosition”设置的是为 0,因此在日志文件中不显示“文件名/函数名/代码行数”的信息。
为了全面验证本日志系统的功能,可以设计多组测试用例对之进行测试。
在使用本日志系统的过程中,除了关注代码本身及其功能之外,还有以下说明及扩展:
本 Chat 首先对日志系统及其框架结构、日志文件命名及内容格式、配置文件进行了详细的介绍,然后对日志系统的设计思路及重要程序流程进行了详细的描述,并给出了一个简单日志系统的完整 C 代码实现,最后对日志系统进行了测试验证。
熟练掌握日志系统的设计和实现方法,是对一个合格的软件研发工程师的基本要求。希望本 Chat 中的内容能够对大家的软件研发工作有所帮助,也希望大家能够在本 Chat 中代码的基础上设计出更加完美和高效的日志系统。
本文首发于 GitChat,未经授权不得转载,转载需与 GitChat 联系。
阅读全文: http://gitbook.cn/gitchat/activity/5d7f261d84257d2371a8b982
您还可以下载 CSDN 旗下精品原创内容社区 GitChat App ,阅读更多 GitChat 专享技术内容哦。