重点内容
视频解说:《C++Linux编程进阶:从0实现muduo C++网络框架系列》-第5讲.实现C++日记库
代码改动
lesson5代码
- 实现:base/LogStream.h/cc
- 实现:base/Logging.h/cc
- examples/test_basic_log.cc
- examples/test_logging.cc
特别要注意CMakeLists.txt的 宏定义改动,debug模式时,lesson4声明的DEBUG宏定义和日记库的Debug日记级别符号有冲突,如果不修改会产买卖想不到的编译报错。
1. 日记体系整体架构
1.1 日记体系整体架构
LOG_DEBUG << "debug log test";
LOG_INFO << "info log test";
1.2 模块详细作用
1.2.1 日记宏 (LOG_XXX)
比如LOG_DEBUG, LOG_INFO等
提供简洁易用的接口,自动捕获文件名和行号,根据当前日记级别决定是否记录日记,创建临时Logger对象并返回流式接口
利用范例:
- LOG_DEBUG << "debug log test";
- LOG_INFO << "int: " << 42;
复制代码 1.2.2 Logger 类
日记体系核心类,管理日记生命周期,提供静态方法控制全局日记行为(级别、输出方式),根据日记级别和上下文创建日记实例,析构时完成日记输出。
LOG_INFO << "info log test"; 现实调用
- if (mymuduo::Logger::logLevel() <= mymuduo::Logger::INFO) \
- mymuduo::Logger(__FILE__, __LINE__).stream() << "info log test";
复制代码 1.2.3 Logger::Impl 类
日记格式化分为两大模块:
Logger的内部实现类,封装日记格式化逻辑,管理时间戳和源文件信息,处理日记的详细格式,添加行号、文件名等上下文信息。
比如日记格式:20250324 09:08:15.248185 WARN warn 输出 - test_basic_log.cc:26
- 20250324 09:08:15.248185 时间戳
- WARN 日记级别打印
- test_basic_log.cc 文件名
- :26 行号
1.2.4 LogStream 类
提供流式接口(<<操作符),管理内部缓冲区,实现各种数据范例的格式化转换,特别优化了数值转换效率,支持链式调用。
包括Logger::impl类里的时间戳 日记级别 文件名 行号的缓存,
这里我们重点讲流式接口 <<操作符,范例如下所示:
- mymuduo::LogStream logstream;
- logstream << "LogStream 输出" << 78 << "abc";
- std::cout << "cout: " << logstream.buffer().toString() << std::endl;
复制代码 输出:
cout: LogStream 输出78abc
1.2.5 FixedBuffer 类
预分配固定大小的内存缓冲区,避免动态内存分配,提供高效的追加、重置等基本操作,作为LogStream的内部存储。
1.2.6 输出函数 (OutputFunc/FlushFunc)
日记终极写入的目标地,函数指针范例(OutputFunc和FlushFunc),允许自定义日记输出目标和革新方式,可以是控制台、文件、网络套接字或自定义设备,由OutputFunc和FlushFunc控制,支持灵活配置。
1.2.7 日记级别 (LogLevel)
定义日记的严峻水平(TRACE、DEBUG、INFO、WARN、ERROR、FATAL),控制日记过滤,配合宏实现条件记录,支持运行时调解。
1.2.8 Fmt 格式化类
联合C风格格式化字符串的灵活性和C++范例安全性,预格式化数据到内部缓冲区,通过<<操作符集成到LogStream中。
- // 测试整数格式化
- Fmt intFmt("%d", 42);
- std::cout << "整数格式化: " << intFmt.data() << std::endl;
-
- // 测试浮点数格式化
- Fmt floatFmt("%.2f", 3.14159);
- std::cout << "浮点数格式化: " << floatFmt.data() << std::endl;
复制代码 打印输出:
整数格式化: 42 浮点数格式化: 3.14
1.2.9 SourceFile 类
高效处理源文件路径,从完整路径中提取文件名,避免运行时重复盘算,优化日记性能,存储在Logger::Impl中提供位置信息。
- // 测试不同路径形式
- std::cout << "原始的文件名获取:" << __FILE__ << std::endl;
- const char* paths[] = {
- "/home/user/project/file.cpp",
- "src/file.cpp",
- "file.cpp"
- };
- for (const char* path : paths)
- {
- Logger::SourceFile sf(path);
- std::cout << "原始路径: " << path << "\n";
- std::cout << "提取文件名: " << sf.data_ << "\n";
- std::cout << "文件名长度: " << sf.size_ << "\n\n";
- }
复制代码 打印输出
原始的文件名获取:/home/lqf/long/spark_muduo/lesson5/examples/test_basic_log.cc 原始路径: /home/user/project/file.cpp 提取文件名: file.cpp 文件名长度: 8 原始路径: src/file.cpp 提取文件名: file.cpp 文件名长度: 8 原始路径: file.cpp 提取文件名: file.cpp 文件名长度: 8
2 格式化日记输出LogStream类
2.1 核心计划理念
LogStream 的核心计划理念是提供一个高效、范例安全、易用的日记流式接口,主要体现在:
1.流式语法计划:
通过重载 << 操作符,实现雷同 std::cout 的链式调用语法
并重载不同的数据范例
- self& operator<<(short);
- self& operator<<(unsigned short);
- self& operator<<(const char* str)
- self& operator<<(const std::string& v)
每个 << 操作符返回自身引用(self&),支持链式表达式,比如LOG_INFO << "int: " << 42;
2.高效内存管理(计划FixedBuffer类):
- 利用预分配的固定大小缓冲区而非动态内存分配
- 避免了频繁的内存分配/释放操作,淘汰内存碎片
2.2 计划框架图
2.3 核心实现分析
2.3.1 内存管理战略
LogStream 通过 FixedBuffer 模板类管理内存,采用两种预定义大小:
- // 在detail命名空间中定义两种常用缓冲区大小
- const int kSmallBuffer = 4000; // 4KB,用于一般日志消息
- const int kLargeBuffer = 4000*1000; // 4MB,用于特别长的日志消息
- // 定义LogStream使用的缓冲区类型
- typedef detail::FixedBuffer<detail::kSmallBuffer> Buffer;
复制代码 FixedBuffer 的核心在于预分配内存并利用指针跟踪当前位置:
- template<int SIZE>
- class FixedBuffer : noncopyable {
- private:
- char data_[SIZE]; // 固定大小的字符数组
- char* cur_; // 当前写入位置
-
- public:
- FixedBuffer() : cur_(data_) {}
-
- void append(const char* buf, size_t len) {
- if (avail() > static_cast<int>(len)) {
- memcpy(cur_, buf, len);
- cur_ += len;
- }
- }
-
- // 其他辅助方法...
- };
复制代码 这种计划避免了动态内存分配,特别适合短生命周期、高频调用的日记场景。
2.3.2 优化的数值转换算法
LogStream 对数值转换做了专门优化,利用自定义的转换算法替代标准库函数:
- // Wilson的高效整数转字符串算法
- template<typename T>
- size_t convert(char buf[], T value) {
- T i = value;
- char* p = buf;
- do {
- int lsd = static_cast<int>(i % 10);
- i /= 10;
- *p++ = zero[lsd]; // 使用预定义的字符表 查表获取数字
- } while (i != 0);
- if (value < 0) {
- *p++ = '-';
- }
- *p = '\0';
- std::reverse(buf, p); // 反转得到正确顺序
- return p - buf;
- }
- // 十六进制转换算法,用于指针
- size_t convertHex(char buf[], uintptr_t value) {
- // 类似实现...
- }
复制代码 这些算法直接操作字符数组,避免了格式化函数的开销和临时对象创建。
2.3.3 流式接口实现
LogStream 通过大量的操作符重载实现流式接口:
- // 将不同类型的数据格式化写入缓冲区
- LogStream& LogStream::operator<<(int v) {
- formatInteger(v);
- return *this; // 返回自身引用,支持链式调用
- }
- LogStream& LogStream::operator<<(const char* str) {
- if (str) {
- buffer_.append(str, strlen(str));
- }
- else {
- buffer_.append("(null)", 6);
- }
- return *this;
- }
- // 其他类型的重载...
复制代码 关键是每个操作符都返回自身引用(*this),使得多个 << 操作可以连续调用。
2.4 范例安全与格式化
LogStream 通过模板和静态断言提供范例安全保证:
- // 将所有整数类型统一处理
- template<typename T>
- void LogStream::formatInteger(T v) {
- // 实现整数格式化...
- }
- // Fmt类使用静态断言确保类型安全
- template<typename T>
- Fmt::Fmt(const char* fmt, T val) {
- // 编译期类型检查
- static_assert(std::is_arithmetic<T>::value == true,
- "Must be arithmetic type");
-
- // 格式化...
- }
复制代码 3 章节总结
1.重点内容:
- 理解如何利用日记级别控制日记是否记录
- 理解LogStream如何利用输出操作符 以C++ cout方式格式化日记。
2.扩展:
- 异步日记支持:
- 将 LogStream 与异步日记体系集成,提高性能
- 计划双缓冲机制,支持高并发场景
- 日记压缩与滚动:
- 支持日记文件自动滚动和压缩
- 实现日记清理战略,避免磁盘占用过多
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |