You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 01:36:21