基于 Linux 的应用层日志系统设计实现

本 Chat 对日志系统和基于 Linux 的应用层日志系统设计方法进行了详细的介绍,并给出了完整的 C 代码实现。具体而言,本 Chat 包含以下内容:

  1. 日志系统介绍
  2. 日志系统框架结构
  3. 日志文件命名、内容格式及生成流程
  4. 日志系统配套的配置文件说明
  5. 日志系统设计思路及重要程序流程
  6. 一个简单日志系统的完整 C 代码实现
  7. 对日志系统的测试验证

本文适合有一定编程经验的软件研发人员阅读。

本文已参加 GitChat「我的技术实践」有奖征文活动,活动链接: GitChat「我的技术实践」有奖征文活动

      • 日志系统介绍
        • 程序调试方法
        • 日志系统介绍
        • 日志等级
        • 日志配置
        • 日志编写说明
      • 日志系统设计与实现
        • 日志文件和配置文件
          • 日志文件
          • 配置文件
        • 重要程序流程说明
          • 配置文件读取程序流程
          • 向日志文件中写入日志信息程序流程
        • 日志系统的 C 代码实现
      • 日志系统测试验证
        • 测试程序说明
        • 代码编译运行及测试验证
        • 其他说明及扩展
      • 总结

日志系统介绍

如果世界上有一个人能够保证第一次写出来的代码就是百分之百正确的,那么毫无疑问,他一定是世界上最优秀的程序员,没有之一。为什么要求代码写好过后要进行充分的自测呢?就因为是人皆会犯错,是程序就会有 Bug。作为一名合格的软件开发人员,必须要学会对程序进行调试。

程序调试方法

一般而言,对程序的调试有以下几种方法:

第一,凭肉眼看。在开发阶段,我们编写的每一行代码都需要用我们的“火 眼金睛”多审查几遍。如果要问,最好的代码调试工具是什么?我认为是人眼。 不管是代码还是文档,在用工具检查之前,都需要先过了我们眼睛这一关。

第二,对代码进行编译,以发现语法错误。编译器能够帮助我们发现代码中存在的语法错误,但对于那些隐蔽性的错误(如逻辑错误等)无能为力。

第三,用代码检查工具(如 Pclint 等)来走查代码。在学校的时候,我们一般认为只要程序能够运行就可以了。但在实际的软件开发项目中,程序能够跑起来,并不表示它就毫无问题。用代码检查工具可以发现很多编译器无法发现的错误,如变量定义了未引用、不同数据类型之间相互赋值、函数未声明便被调用等。

第四,对代码进行调试。对于运行正常而输出结果不正确的程序,我们可以用设置断点并进行单步跟踪调试的方法来发现其中存在的问题。例如,在 Visual Studio 里面,可实现对代码的单步调试,并输出变量在某一步产生的值,可据此判断程序逻辑正确与否。

第五,对程序的日志文件进行分析。对代码的单步调试只在代码行数较少的时候比较适用,但在实际的软件项目中,代码少则几千行,多则数万行,用单步调试的方法显然不恰当。为了解决大程序文件代码调试问题,日志系统应运而生。在程序中的重要地方打印日志,之后对产生的日志文件进行分析,可找到对应代码的问题。因此,日志文件分析成了大型软件项目中代码调试的主要手段。

以下对日志系统相关内容进行介绍。

日志系统介绍

在各类软件系统中大量使用日志,日志能够起到“按图索骥”的作用,它对于故障定位和系统正常运行维护具有举足轻重的作用。

日志文件是程序中写日志函数产生的记录程序执行情况的文件。在程序恰当的地方调用该函数,可对整个程序的运行状况有一个全面的了解,方便对程序的跟踪调试。

日志等级

事有轻重缓急,日志信息也有重要与不重要之分。一般按照重要程度,将日志等级分为几类。在作者参与过的软件开发项目中,共有 6 个等级,如图 1.1 所示。

基于 Linux 的应用层日志系统设计实现_第1张图片

图 1.1 日志等级

从图 1.1 可以看到,从上到下,日志等级不断降低(即日志的重要程度不断降低)。开发人员根据所要打印日志的重要程度采用不同的日志等级。

日志配置

由于不同软件程序行数、部署情况、实现功能等的差别,对日志打印的要求也不尽相同,因此需要由配置来控制日志的产生数量和显示情况等。

在后面日志系统的实现部分,将对日志配置进行详细的描述。

日志编写说明

日志编写的总体原则是简单清晰、便于排查问题。

日志编写基本原则

  • 显式输出,关键信息必须输出。
  • 在编码时使用正确的日志级别,每个错误等级必须反应出实在的含义,不是特别严重的问题不能将日志等级定义为 Level 1(严重错误)。
  • 在写日志描述时,要使用正常、简单易懂的语言,不能使用晦涩难懂的语言或某些专业术语。
  • 在极少数特殊情况不希望用户知道时,可使用特殊日志标记。
  • 为了写出优美的日志,建议在自己修改或添加代码的地方,都要正确地打上标记(包括作者、日期信息等),方便通过日志追踪版本的演进情况。

日志编写基本要求

  • 分多条信息分别输出,不要企图一次将所有信息打印出来
  • 分时输出
  • 必须分日志级别,这样可根据等级迅速对日志进行分析
  • 控制日志信息的条数,不重要的信息尽量不要打印日志

日志输出位置要求

  • 所有的输入/输出,包括收消息和发消息都要求输出日志。
  • 关键控制点必须输出日志。
  • 调用底层或第三方软件,必须输出日志,而且对不可靠底层,必须加上 begin/end 两行日志。
  • 对方系统处理时间必须输出日志,以利以后维护时快速定位性能问题。

注意事项

  • 在编写日志时需要注重日志细节,目标是为了方便以后维护,在遇到问 题时,可以快速定位问题。
  • 不要在同一行中写意思重复的日志。
  • 日志需要足够的精简,不要随意换行。
  • 日志中字段之间可以用空格或其他符号分断,不能将日志一直连续而不 将其分断,尽量使日志本身具备进行“识文断句”的能力。
  • 对于日志中的特殊信息(如会话号、IP 地址等),用特殊的符号进行标识, 其主要目的是为了便于搜索。

日志系统在软件程序中占有非常重要的地位,日志文件是排查程序问题的主要依据,是程序调试的利器。因此,熟练掌握日志系统的设计和实现方法,及准确通过日志文件来定位程序问题,是对一个合格的软件研发工程师的基本要求。

有了对日志系统的基本认识之后,我们接下来设计并实现一个基于 Linux 的应用层日志系统。

日志系统设计与实现

设计日志系统的初衷是为了监测软件运行状况及排查程序故障。在日志文件中存放程序流程中的一些重要信息,包括:变量值、函数返回值及其执行情况、脚本执行及调用情况等。通过阅读日志文件,研发或维护人员能够较快地跟踪程序流程,并定位程序问题。

一个完整的日志系统包括三大部分:软件程序、配置文件和日志文件,它们之间的关系如图 2.1 所示。

基于 Linux 的应用层日志系统设计实现_第2张图片

图 2.1 日志系统完整结构

从图 2.1 可以看出,软件程序处于主导地位,它会从配置文件中读取相关的配置信息,并将程序运行过程中的信息输出到日志文件中。

本节首先对日志文件和配置文件进行介绍,然后对生成日志文件代码的重要程序流程进行说明,最后给出完整的基于 Linux 的一个简单应用层日志系统的 C 代码实现。

日志文件和配置文件

本节对日志文件和配置文件进行说明。

日志文件

日志文件是程序运行过程中生成的文件,里面记录了一些重要的程序状态信息,通过阅读该文件的内容,研发人员能够跟踪程序流程并快速定位程序问题。

对于日志文件的命名,不同的软件开发项目有不同的规定。一般说来,日志文件都是以 log 作为后缀,如本文中的日志文件命名为:WriteLog.log。

对于日志文件中记录的每条日志信息的格式,不同的软件也会有所不同。在本文中,日志信息的格式有以下两种(具体使用哪一种通过配置文件中的配置项决定)。

第一种:

[日志生成时间][文件名][函数名][代码行][日志等级]日志具体信息

第二种:

[日志生成时间][日志等级]日志具体信息
配置文件

配置文件用于控制日志文件的输出信息,包括:日志等级、输出日志位置信息、日志存放目录等。

在本文中,为了便于程序处理,对配置文件的命名及内容格式约定如下:

  • 第一,配置文件的后缀为 ini,如本文中使用到的配置文件为 Config.ini。
  • 第二,配置文件的内容由段名、注释和配置项组成,其中,段名用 [] 括起来,注释的内容以分号 ; 开头,配置项采用等号 = 进行赋值。

本文中使用的配置文件 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=

如上所示的配置文件,

  • “LOG”段是指日志信息,包含日志等级、日志代码位置标识和输出日志文件的目录三个配置项。
  • 对于“LogLevel”配置项,只有代码中日志等级不低于配置值的日志信息才会被输出到日志文件中(例如,LogLevel=4,那么只有 Fatal、Error、Warn、Info 和 Trace 等级的日志会被输出到日志文件中)。
  • “LogPosition”配置项的值用于控制是否在日志文件中显示“文件名/函数名/代码行数”信息,1 则显示,0 则不显示。
  • “LogDir”配置项的值表示生成的日志文件存放的目录。

“EMPLOYEEINFO”段的内容用于对我们编写的日志系统进行测试,它是指员工信息,包含员工姓名和员工年龄两个配置项。程序会将员工姓名和员工年龄读入,并输出到日志文件中。

重要程序流程说明

生成日志文件的总体程序流程如图 2.2 所示。

基于 Linux 的应用层日志系统设计实现_第3张图片

图 2.2 生成日志文件的总体程序流程

可以看到,生成日志文件包括两大程序流程,一是读取配置文件,一是根据配置项生成日志文件。

在实际的软件开发项目中,为了在程序的不同地方打印不同的日志,要将生成日志的代码封装为函数,作为 API 供程序调用。

如果程序没有成功生成日志,那么就不要让其执行后续流程,而是要查找问题的原因,直到日志文件生成正常为止。

以下介绍配置文件读取和日志文件生成过程中的一些重要程序流程。

配置文件读取程序流程

1. 配置文件读取操作总体流程

实现配置文件读取操作的总体程序流程如图 2.3 所示。

基于 Linux 的应用层日志系统设计实现_第4张图片

图 2.3 配置文件读取操作总体流程

从上图可以看出,配置文件的读取操作比较简单,遵循的步骤是:打开配置文件 —> 匹配段名 —> 匹配配置项名 —> 获取配置项值。通过阅读后面给出的日志系统实现代码,大家对配置文件读取流程会有更深入的理解。

2. 获取配置项值的程序流程

获取配置项值的程序流程如图 2.4 所示。

基于 Linux 的应用层日志系统设计实现_第5张图片

图 2.4 获取配置项值的程序流程

如图所示,程序首先找到段名,然后在该段之下去匹配配置项名,最后获取配置项的值。

在本文中,配置文件存放的全路径为 /home/zhouzhaoxiong/test/WriteLog。值得注意的是,Linux 下目录之间的分隔符为 /,这个与 Windows 下的分隔符有区别。

向日志文件中写入日志信息程序流程

该操作的程序流程如图 2.5 所示。

基于 Linux 的应用层日志系统设计实现_第6张图片

图 2.5 向日志文件中写入日志信息的程序流程

如图所示,该操作的流程具体为:

  • 第一步,判断当前代码中的日志信息对应的日志等级是否高于配置中的日志等级;如果不是,则不用进行后续写日志操作。
  • 第二步,打开日志文件,如果打开失败,则不执行后续操作。
  • 第三步,将产生日志的时间写入日志文件中。
  • 第四步,根据配置(即是否在日志内容前写入“filename/functionname/linenum”)将日志内容写入日志文件中。
  • 第五步,写日志成功之后要关闭文件,防止文件句柄打开过多而导致整个系统句柄满的情况。

日志系统的 C 代码实现

基于以上程序流程,本文用 C 代码实现了一个简单日志系统,笔者已将代码放到了 GitHub 上,链接为:

https://github.com/zhouzxi/WriteLog

对于程序的说明如下:

1. WriteLog.h 和 WriteLog.c 分别是日志系统的头文件和源代码文件,Test.c 是对日志系统进行测试的代码文件。

2. WriteLog.c 中,各函数的功能如下:

  • LogLevel:获取对应的日志等级
  • GetTime:获取当前日志生成的时间
  • GetConfigValue:获取各配置项的值
  • GetStringContentValue:获取字符串的值
  • GetConfigFileStringValue:从配置文件中获取属性为字符串的配置项的值
  • GetConfigFileIntValue:从配置文件中获取属性为整型的配置项的值
  • WriteLogFile:将日志内容写到日志文件中

3. WriteLog.h 中,定义了一个函数宏 WRITELOGFILE 用来供其他程序调用完成写日志的操作;其他程序模块在调用 WRITELOGFILE 的时候,只需要传入日志等级和需要写入日志文件的具体信息即可。

日志系统测试验证

测试程序说明

测试程序见 Test.c 文件,为了测试本日志系统的功能是否正确,在 main 函数中设计了以下三类日志信息:

  • 第一类:打印程序的版本号及编译时间
  • 第二类:打印 Fatal、Error、Warn、Info、Trace、Debug 这六个等级的日志各一条
  • 第三类:调用 GetEmployeeInfo 函数打印读取到的员工姓名和年龄

将日志系统代码、测试代码和配置文件放到 Linux 系统上,为了便于说明,将这些文件都存放到一个目录(本文中的存放目录为 /home/zhouzhaoxiong/test/WriteLog)下,并且日志文件也生成在该目录下。文件目录布局如图 3.1 所示。

基于 Linux 的应用层日志系统设计实现_第7张图片

图 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,因此在日志文件中不显示“文件名/函数名/代码行数”的信息。

为了全面验证本日志系统的功能,可以设计多组测试用例对之进行测试。

其他说明及扩展

在使用本日志系统的过程中,除了关注代码本身及其功能之外,还有以下说明及扩展:

  • 配置文件中“LOG”段只包括了日志等级、日志代码位置标识和输出日志文件的目录三个配置项。在实际的软件开发项目中,还会有更多的配置参数,例如:存放的日志文件的最大个数、每个日志文件的大小阈值、每个已写入完成的日志文件的命名等。可以在本程序的基础上进一步扩展来实现复杂的日志功能。
  • 本文中对日志信息的写入采用的是直接在日志文件后面追加的方式,因此每次测试之前,要在“log”目录下删除上一次产生的“WriteLog.log”文件,否则新的日志信息会写入旧的日志文件中。
  • 由于写日志函数 WriteLogFile 的入参较多,编写调用代码较为繁琐,因此使用一个宏 WRITELOGFILE 来代替,在遇到参数较多的函数时,大家都可以采用类似这种定义宏的方式来简化调用操作。

总结

本 Chat 首先对日志系统及其框架结构、日志文件命名及内容格式、配置文件进行了详细的介绍,然后对日志系统的设计思路及重要程序流程进行了详细的描述,并给出了一个简单日志系统的完整 C 代码实现,最后对日志系统进行了测试验证。

熟练掌握日志系统的设计和实现方法,是对一个合格的软件研发工程师的基本要求。希望本 Chat 中的内容能够对大家的软件研发工作有所帮助,也希望大家能够在本 Chat 中代码的基础上设计出更加完美和高效的日志系统。


本文首发于 GitChat,未经授权不得转载,转载需与 GitChat 联系。

阅读全文: http://gitbook.cn/gitchat/activity/5d7f261d84257d2371a8b982

您还可以下载 CSDN 旗下精品原创内容社区 GitChat App ,阅读更多 GitChat 专享技术内容哦。

FtooAtPSkEJwnW-9xkCLqSTRpBKX

你可能感兴趣的:(基于 Linux 的应用层日志系统设计实现)