日志对于一些大一些的项目来说,可以在项目运行出现问题时更好的帮助
项目的维护人员快速的定位到问题出现的地方并且知道出现问题的原因,
并且日志也可以帮助程序员很好的进行项目的Debug,那么今天我就来实
现一个C++编写的一个简单的日志功能。
1. 简单功能的实现
我的第一步就是先写出一个大致的框架,能用就行:
现在我们要让它输出消息,对于日志来说,一个日志一定要有日志的等级,日志的时间,日志的内容,前两点比较容易,日志的等级可以使用枚举来表示,而日志的时间也很容易,对于日志的内容来说可以实现的更加多元化一点,这一点可以由用户输出自己想输出的内容,所以这里使用了可变参数列表:
有人看到这个会不会很熟悉,C标准库中的printf好像参数就长这样:
我就是想用这样的方式,来让用户输入自己想输出的格式,然后附带对应参数输出,由于输出的参数的个数和类型都不确定,所以我们使用可变参数来实现用户的传参。有人就问了,那这个可变参数中的参数怎么拿到呢?难道我们就这样给printf传过去吗?
显然是不行的,所以这里就有必要介绍如何获取到可变参数了。
可变参数
C语言针对可变参数做了几个宏,以便于用户能够获取到可变参数里的参数:
其中关于携带可变参数的函数,该函数的可变参数前必须有一个明确类型的参数:
原因就是,在调用函数之后,函数开始运行前要进行压栈,以及函数参数的加载,而函数参数的加载从右往左的,也就是说,可变参数前的参数是为了能够方便定位到可变参数的位置。
对于获取可变参数中的参数,我们首先需要一个va_list类型的变量(其实就是一个指针):
然后使用va_start对该变量进行初始化,这个宏的第一个参数就是arg,第二个参数就是可变参数前面的参数,通过va_arg根据顺序和参数类型拿到参数(这个顺序和类型都是用户已知的),最后使用va_end对arg进行销毁(其实就是置空):
而对于实现到日志上的可变参数,反正我们对这些参数是能够打印出来就行了,所以这里再介绍一批接口:
可以看到C标准库种有专门的针对va_list变量的打印措施,所以对于上面的代码我们也可以这么写:
有了上面的准备工作我们就可以继续编写日志代码了:
但是我们发现我们无法识别到任务的等级是什么,只是一个数字,并且我们的时间是一个时间戳,不方便进行观察,所以这里再次做出优化:
关于时间这里再次介绍一个接口:
这个接口可以将时间戳转化成一个对应年月日时分秒的结构体:
这样这个日志打印出来就比较直观了。
这样的日志是打印在屏幕上,但是大多数情况日志是存储在文件中的,所以我们的日志要支持自定义式的让用户来决定日志打印到哪里,而打印到文件中,可能是一个文件,也可能是根据日志等级来分开打印,所以我们的日志打印又要支持多文件打印,也要支持单个文件的打印,所以我们的Log类中要添加两个字段:打印格式,目标文件:
那么现在我们又要对日志输出函数进行更深层次的封装了:
那么关于向文件中写日志,要为文件定制一个名字,这里采用. + 日志等级为后缀,如果是向一个文件中打印的话,后缀就是.all:
现在我们就可以以三种方式输出日志了:
屏幕:
单个文件:
多个文件:
2. 优化
我们看到我们的代码大部分使用的都是C++中的string,但是使用的接口大多都是C语言的接口,导致输出字符串时,都得使用c_str()来转换,这个接口打起来可能有些麻烦,所以我们宏定义一下:
上面展示宏使用的这个snprintf中的内容优有点难看,这里也需要优化一下:
我们也可以利用仿函数来将我们日志接口的使用来变得更简单:
但是我们发现,可变参数并不能够在多个函数上进行传递,所以我们这里依旧利用va_list来实现传参:
这样我们在使用日志接口时就可以是这样:
并将LogMessage函数置为私有,我们也知道,日志功能在一个项目中输出的格式应该是一致的,所以日志对象应该是只有一个,所以:
至此我们的日志的使用起来就是这样:
而我们想要修改日志的输出格式只需要修改类内Conf的全局对象就可以:
至此,一个简单的日志功能就实现了。