# logging **Repository Path**: damon_SJTU/logging ## Basic Information - **Project Name**: logging - **Description**: 一个C++实现的日志系统 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-08-12 - **Last Updated**: 2025-08-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README 第二部分:从0到1的实现路线图 如果你要自己实现,千万不要一开始就奔着最终的复杂形态去,很容易迷失。遵循迭代开发的原则,一步步来。 第1步:同步日志器 (Synchronous Logger) - V1.0 目标:先实现一个最基本、能用的日志器,但它是同步的(调用就写文件)。 组件: 一个 LogLevel 枚举 (INFO, ERROR 等)。 一个 Logger 类。 Logger 类有一个私有成员 std::ofstream 用于文件操作。 一个公有方法 void log(LogLevel level, const std::string& message)。这个方法内部会: 获取当前时间。 将 [时间] [级别] 消息 格式化成一个字符串。 写入文件流。 刷新文件流 (flush)。 测试:在 main 函数里创建 Logger 对象,调用 log 方法,看看日志文件是否生成了正确内容。 第2步:引入单例模式 - V2.0 目标:让你的同步日志器可以在程序的任何地方被方便地访问。 改造: 将 Logger 的构造函数私有化。 删除拷贝构造和赋值操作。 提供一个静态公有方法 static Logger& getInstance(),内部使用 static Logger instance; 来返回唯一的实例。 测试:在 main 和其他函数中,都通过 Logger::getInstance().log(...) 来记录日志,确保它们都写进了同一个文件。 第3步:实现异步化 (Producer-Consumer) - V3.0 目标:这是最大的一次飞跃。将同步模型改为异步模型。 新增组件: 线程安全阻塞队列 BlockingQueue。你需要自己实现一个,内部使用 std::queue, std::mutex 和 std::condition_variable。这是非常好的并发编程练习。 Logger 类中增加一个 std::thread 成员(后台线程)和一个 bool 成员(用于通知线程退出的标志)。 改造: Logger 的构造函数中启动后台线程。线程的主体是一个循环函数(比如 asyncWriteLog)。 Logger 的析构函数中设置退出标志,并唤醒后台线程,最后 join() 等待其退出。 修改核心 log 方法:现在它不再写文件,而是把日志字符串 push 到阻塞队列里。 实现后台线程函数 asyncWriteLog:它在一个 while 循环中,从阻塞队列里 pop 日志,然后将弹出的字符串写入文件。 测试:创建多个线程,每个线程都疯狂调用 Logger::getInstance().log(...),观察主线程是否很快就完成了任务,而日志在后台被慢慢写入文件。 第4步:引入缓冲区优化 (Double Buffering) - V4.0 目标:减少 I/O 次数,提升吞吐量。 改造: 阻塞队列的类型从 BlockingQueue 变为 BlockingQueue>。这里的 Buffer 是一个大的 char 数组或 std::vector。 Logger 类里需要有两个(或更多)这样的 Buffer 指针,一个给前端用(currentBuffer_),一个给后端用。 修改 log 方法:现在是往 currentBuffer_ 里写日志。如果 currentBuffer_ 满了,就把它 push 到队列里,然后换一个新的空闲 Buffer 来当 currentBuffer_。 修改后台线程函数:现在它从队列里 pop 的是一个装满了日志的 Buffer。然后它将这个 Buffer 的所有内容一次性 fwrite 到文件里。写完后,这个 Buffer 可以被回收复用。 测试:在海量日志场景下,对比 V3.0 和 V4.0 的性能差异。你会发现 CPU 占用和程序响应速度有明显改善。 第5步:完善易用性和功能 - V5.0 (Final) 目标:增加宏、格式化等功能,让它成为一个真正“好用”的库。 新增组件: 日志格式化类 LogFormatter:用于定义日志的输出格式。 宏定义:在头文件中定义 LOG_INFO(format, ...) 宏,内部会调用 Logger::getInstance().log()。这里需要用到可变参数宏。 改造: log 方法现在可以接受 const char* format 和可变参数,像 printf 一样。在方法内部,使用 snprintf 或类似方法将日志格式化到缓冲区中。 测试:在你的项目里愉快地使用 LOG_INFO("User %s login failed, error code: %d", name, error_code);。 ![alt text](image.png) 阶段 1: 核心功能 - 同步单文件日志 创建基本的文件结构: include/mylog/logger.h: 声明 Logger 类(用于日志记录),LogLevel 枚举类(用于定义日志级别),和 Log 类(提供全局访问点)。 src/logger.cpp: 实现 Logger 类。 include/mylog/log_sink.h: 声明 LogSink 类(用于日志输出)。 src/log_sink.cpp: 实现 LogSink 类和 FileSink 类(将日志写入文件)。 example/main.cpp: 一个简单的示例程序,使用日志系统。 定义 LogLevel 枚举类: LogLevel::DEBUG, LogLevel::INFO, LogLevel::WARN, LogLevel::ERROR, LogLevel::FATAL 实现 LogSink 和 FileSink 类: LogSink 是一个抽象基类,定义 virtual void log(const std::string& message) = 0; FileSink 继承自 LogSink,构造函数接收文件路径,实现 log 方法将消息写入文件。 实现 Logger 类: 构造函数接收 LogLevel 和 LogSink 对象。 提供 log 方法,接收 LogLevel 和日志消息,根据配置的日志级别决定是否将消息写入 LogSink。 提供 debug、info、warn、error、fatal 方法,分别调用 log 方法并传入不同的 LogLevel。 实现 Log 类: 提供一个静态方法 GetLogger() 用于获取全局唯一的 Logger 实例 示例程序: 在 main.cpp 中,创建 Logger 和 FileSink 对象,并使用 Logger 的 info、error 等方法输出日志。 编译和运行: 确保程序能够编译通过,并且能够将日志消息写入到指定的文件中。 阶段 2: 格式化 添加 Formatter 类: include/mylog/formatter.h: 声明 Formatter 类。 src/formatter.cpp: 实现 Formatter 类。 Formatter 类负责将日志消息格式化为字符串。 构造函数接收格式化字符串(例如,"%Y-%m-%d %H:%M:%S [%level] %message")。 提供 format 方法,接收 LogLevel、时间戳和日志消息,根据格式化字符串生成最终的日志消息。 修改 Logger 类: 在 Logger 类中添加 Formatter 成员变量。 修改 log 方法,先调用 Formatter::format 方法格式化日志消息,然后再将格式化后的消息写入 LogSink。 修改示例程序: 创建 Formatter 对象,并将其传递给 Logger 的构造函数。 修改日志消息的内容,使其包含更多有用的信息。 编译和运行: 确保程序能够按照指定的格式输出日志消息。 阶段 3: 多 LogSink 支持 修改 Logger 类: 将 LogSink 成员变量改为 std::vector,支持多个 LogSink。 修改 log 方法,遍历所有的 LogSink 对象,并将格式化后的日志消息写入到每个 LogSink 中。 创建 StdoutSink 类: include/mylog/stdout_sink.h: 声明 StdoutSink 类。 src/stdout_sink.cpp: 实现 StdoutSink 类。 StdoutSink 继承自 LogSink,实现 log 方法将消息写入标准输出。 修改示例程序: 创建 FileSink 和 StdoutSink 对象,并将它们添加到 Logger 的 LogSink 列表中。 确保程序能够将日志消息同时写入到文件和控制台。 编译和运行: 确保程序能够将日志消息同时写入到文件和控制台。 阶段 4: 异步日志 创建 AsynLopper 类: include/mylog/asyn_lopper.h: 声明 AsynLopper 类。 src/asyn_lopper.cpp: 实现 AsynLopper 类。 AsynLopper 类使用一个线程来异步地将日志消息写入 LogSink。 AsynLopper 类使用一个阻塞队列来存储日志消息。 提供 push 方法,将日志消息放入队列。 提供 start 方法,启动异步线程。 提供 stop 方法,停止异步线程。 创建 AsyncLogger 类: include/mylog/async_logger.h: 声明 AsyncLogger 类。 src/async_logger.cpp: 实现 AsyncLogger 类。 AsyncLogger 继承自 Logger,使用 AsynLopper 来异步地写入日志消息。 在构造函数中,创建 AsynLopper 对象,并启动异步线程。 修改 log 方法,将日志消息放入 AsynLopper 的队列中。 修改示例程序: 创建 AsyncLogger 对象,并使用它来输出日志消息。 确保程序能够异步地将日志消息写入到目标。 编译和运行: 确保程序能够异步地将日志消息写入到目标。 处理程序退出: 在程序退出的时候,调用 AsynLopper::stop,保证所有的数据都写入到磁盘中。 阶段 5: 其他增强功能 滚动文件: 实现 RollBySizeSink 类,根据文件大小滚动日志文件。 日志级别控制: 允许在运行时动态地修改日志级别。 性能优化: 使用更高效的队列和锁。 减少内存分配和拷贝。 添加更多格式化选项: 例如,线程 ID、文件名、行号等。 添加更多 LogSink: 例如,MySQLSink、SocketSink 等。 支持配置文件: 使用配置文件来配置日志系统的各个参数。 小贴士: 逐步测试: 每完成一个阶段,都要进行充分的测试,确保代码的正确性。 代码风格: 保持良好的代码风格,例如,使用统一的命名规范、添加必要的注释等。 版本控制: 使用 Git 等版本控制工具来管理代码。 希望这个分解步骤能够帮助你成功实现一个完善的异步日志系统! 祝你编码愉快!