C++实现单例Logger对象使用前自动调用成员函数做检查方案咨询
Logger自动前置校验实现方案
单例Logger要支持logger << "日志内容"的流式调用,且不需要手动调用初始化/校验接口,核心是把校验逻辑嵌入到operator<<的执行入口,不要依赖单例构造函数做初始化——全局单例的构造时机不受代码逻辑控制,可能在文件系统环境就绪前就执行,反而会触发异常。
核心逻辑设计
把所有前置检查封装为Logger的私有成员函数,每次触发operator<<写日志时,先执行检查逻辑再做写入:
- 首次写入场景:检查目标日志文件是否存在,不存在则新建文件,存在则以追加模式打开,自动将文件指针定位到末尾
- 非首次写入场景:做常规状态校验,包括文件流是否正常打开、流错误位是否被触发、文件指针是否停在末尾,出现异常则自动恢复文件流状态
- 所有校验逻辑完全对上层使用者透明,不需要额外调用任何初始化接口
参考实现代码
#include <fstream> #include <filesystem> #include <string> #include <mutex> class Logger { private: std::ofstream log_file; std::mutex log_mtx; // 日志路径,如需固定到源码目录可通过编译宏传入源码路径拼接 const std::string log_path = "./app.log"; bool is_initialized = false; // 单例模式:私有构造/析构,禁用拷贝、移动 Logger() = default; ~Logger() { if (log_file.is_open()) { log_file.flush(); log_file.close(); } } Logger(const Logger&) = delete; Logger& operator=(const Logger&) = delete; // 前置校验核心逻辑 void pre_write_check() { std::lock_guard<std::mutex> lock(log_mtx); // 首次初始化分支 if (!is_initialized) { if (std::filesystem::exists(log_path)) { // 文件存在:追加模式打开,自动定位到末尾 log_file.open(log_path, std::ios::app); } else { // 文件不存在:创建新文件 log_file.open(log_path, std::ios::out); } is_initialized = true; return; } // 常规校验分支:处理文件被意外关闭、流状态异常的情况 if (!log_file.is_open() || log_file.fail()) { log_file.clear(); // 先重置流错误位 log_file.open(log_path, std::ios::app); } // 每次写入前强制将指针移到文件末尾,避免指针错位 log_file.seekp(0, std::ios::end); } public: // 获取单例实例 static Logger& get_instance() { static Logger inst; return inst; } // 重载<<运算符,支持任意可流式输出的内容类型 template<typename T> Logger& operator<<(const T& content) { pre_write_check(); log_file << content; log_file.flush(); // 实时刷盘,避免程序崩溃丢日志 return *this; } }; // 全局别名,和预期使用方式对齐,不用每次调用单例获取接口 #define logger Logger::get_instance()
注意点
- 日志路径不要直接写相对路径:相对路径是相对于程序启动时的工作目录,不是源码文件所在目录,要固定写入源码同目录的话,可以在编译阶段传入源码路径宏,比如CMake环境下添加编译定义
-DSRC_DIR="${CMAKE_CURRENT_SOURCE_DIR}",代码里拼接log_path = std::string(SRC_DIR) + "/app.log"即可。 - 多线程场景必须加互斥锁,否则会出现日志内容错乱、文件指针错位的问题,上述代码已经内置锁保护,如果是单线程使用可以去掉锁逻辑减少开销。
- 实时刷盘逻辑可以根据性能需求调整,如果日志量很大,可以改成攒够固定条数/大小再刷盘,降低IO开销。
- 后续要扩展其他校验逻辑(比如日志文件大小超限切分、写权限检查等),直接往
pre_write_check()函数里添加即可,上层调用逻辑不需要做任何修改。
内容的提问来源于stack exchange,提问作者n16hthowl
相关产品推荐
相关产品推荐

