最近看了一些开源项目,他们封装了自己的日志库,各有所长,也温习了很多C++的基础知识,学以致用,从今天开始,从零开始写一个 C++ 的日志库。
写日志库不是目的,目的是把技术细节梳理清晰,稳固所学。如果能对你有所帮助,那将是我最开心的事。
这个日志库将会很轻量,就叫 cllogger - c++ lite logger
目标
- 轻量易用
- 线程安全
- 支持消息等级
- 方便的格式化
- 可输出到调试器
- 可输出指定的文件
先看看 cllogger 的最终的使用方式:
cllogger& logger = GetLogger("D:\\app.log",true); // 第一个参数配置日志文件路径,第二个参数指定是否输出到调试器
logger.start();
如上两行便完成了日志库的配置、启动。随后即可调用不同等级的消息输出及格式化输出(以F结尾的函数),如:
LogDebug(L"START"); // debug消息
LogWarningF(L"APP %s","run"); // 支持格式化的警告消息
LogError(L"Failed."); // 异常消息
LogFatal(L"Quit."); // 严重错误消息
跑起来看看:
足够简单吧?
简单的背后需要精心的构造,现在我们一步步来开始构建 cllogger。
搭建个骨架
日志类需要哪些基础数据?消息等级、日志条目,那么我们把它们定义出来:
class cllogger {
public:
enum class Level {
Debug = 0,
Warning = 1,
Error = 2,
Fatal = 3
};
struct Entry {
Level level;
std::wstring timestamp;
std::wstring tag;
std::wstring messsage;
};
//...
}
Entry - 日志条目包含了 等级、时间戳、标识符、消息。
Level - 等级分为4类:调试、警告、错误、异常。
请注意 Level 这个枚举类型,我用了 enum class 类型,而不是 enum 类型,二者区别是什么呢?
enum class 和 enum
enum class Color { red, green, blue }; // C++11 enum class
enum color { red, green, blue }; // C++98 "plain" enum
C++98 的 enum 为 弱枚举 又称为 非限定作用域枚举 ;
C++11 的 enum class 为 强枚举 又被称为 限定作用域枚举 ;
具体什么含义呢,逐一解释:
-
类型安全性
强弱指的是对类型检查,是否存在隐式转换。
C++11 的 enum class 为 强枚举,意味着它不会隐式地转换为整数或其他enum class
类型。只能显式转换,如通:
Color c = Color::blue;
auot i = static_cast<double>(c);
C++98 的 enum 为 弱枚举 ,则被自动隐式转换:
enum Color { red, green, blue };
enum Animal { dog, cat, bird };
Color color = Color::red;
Animal animal = Animal::dog;
if (color == animal) {
std::cout << "red == dog" << std::endl;
}
-
作用域控制
即限定枚举值作用域。
C++11 的 enum class 的枚举值只在定义它的作用域内可见,是"域内枚举" (scoped enums),使用枚举量时,必须指明所属枚举类型,防止命名空间污染,如必须使用 Color::red 而不能直接使用 red 。
而 C++98 的 enum 是 "非域内枚举"(unscoped enums),枚举值可以直接使用。
// C++11 enum class
Color c = Color::blue;
// C++98 enum
Color c = blue;
这就衍生一个问题,枚举量的名字泄露到了包含这个枚举类型的作用域内,简单地说,枚举值变量名在作用域内都不可再声明,因此 :
// C++98 enum
Color c = blue;
// Error!
auto blue = 1;
-
前置声明
enum class 支持前置声明,即不用初始化枚举成员,声明一个枚举类型
enum class Color;
C++98中,弱枚举不支持前置声明,C++11中强、弱枚举都支持。弱枚举指定了基础类型就可以前置声明。
注:C++20中可以 using 枚举类,这样也可以不使用类名,而直接使用枚举值了。
-
指定底层表示类型
解决了不同平台上枚举类型大小不确定的问题。
enum class Color : unsigned int {
Red = 0xFF0000,
Green = 0x00FF00,
Blue = 0x0000FF
};
int main() {
Color c = Color::Red;
std::cout << "The value of Red is: " << static_cast<unsigned int>(c) << std::endl;
return 0;
}
C++11 之后的改进
C++11之后的版本继续改进了枚举的功能。例如,C++14引入了用户定义的枚举字面量,允许为枚举值定义更易读的字符串表示形式。
如何选择?
首先,上面说了 enum class 的各种优点,当然它也有缺点。它的优点即缺点,如严苛的类型检查,必须使用类型名访问,主要就是麻烦一些。
总的来说,一般情况下,优先enum class,但有时你也许正想利用enum可以隐式转换的特性,如:
enum week{Mon, Tue, Wed, Thur, Fri, Sat, Sun};
int main()
{
enum week day;
day = Wed;
printf("%d",day);
return 0;
}
具体到我们的 cllogger ,毫不犹豫的选择了 C++11 的 enum class。
至此,学习 enum class 让我们的 cllogger 迈出了一小步,后面将逐步丰满完善它,拭目以待 :)