流重定向方式实现日志多路输出(c++/c)

通过流重定向方式实现日志多路输出,设计为可扩展架构,支持动态添加多个输出目标(控制台、文件、调试窗口等)。其中C++方案更符合面向对象设计,而C方案则更轻量、更接近系统底层。

C++ 实现(基于流缓冲区)

  • 基于标准库的std::streambuf实现流重定向
  • 通过MultiStreambuf类将数据分发到多个目标缓冲区
  • 支持控制台、文件和调试窗口(OutputDebugString)输出
  • 使用单例模式管理日志器,方便全局访问
#include 
#include 
#include 
#include 
#include 
#include 

// 多重输出流缓冲区类
class MultiStreambuf : public std::streambuf {
private:
    std::vector<std::streambuf*> buffers;

protected:
    // 重写overflow函数,当缓冲区满时调用
    int overflow(int c) override {
        bool success = true;
        for (auto buf : buffers) {
            if (buf->sputc(c) == traits_type::eof()) {
                success = false;
            }
        }
        return success ? c : traits_type::eof();
    }

    // 重写sync函数,刷新缓冲区
    int sync() override {
        bool success = true;
        for (auto buf : buffers) {
            if (buf->pubsync() == -1) {
                success = false;
            }
        }
        return success ? 0 : -1;
    }

public:
    void addBuffer(std::streambuf* buf) {
        if (buf) buffers.push_back(buf);
    }
};

// 调试输出流缓冲区类
class DebugStreambuf : public std::streambuf {
protected:
    int overflow(int c) override {
        if (c != traits_type::eof()) {
            char ch = static_cast<char>(c);
            OutputDebugStringA(&ch);
        }
        return c;
    }

    int sync() override {
        return 0;
    }
};

// 日志管理器(单例模式)
class Logger {
private:
    static Logger instance;
    MultiStreambuf multiBuf;
    std::streambuf* originalCoutBuf;
    std::ofstream fileStream;
    DebugStreambuf debugBuf;

    Logger() : originalCoutBuf(std::cout.rdbuf()) {
        // 默认输出到控制台
        multiBuf.addBuffer(originalCoutBuf);
    }

    ~Logger() {
        // 恢复原始流缓冲区
        std::cout.rdbuf(originalCoutBuf);
    }

public:
    static Logger& get() {
        return instance;
    }

    void addConsoleOutput() {
        multiBuf.addBuffer(originalCoutBuf);
    }

    void addFileOutput(const std::string& filename) {
        fileStream.open(filename, std::ios::out | std::ios::app);
        if (fileStream.is_open()) {
            multiBuf.addBuffer(fileStream.rdbuf());
        }
    }

    void addDebugOutput() {
        multiBuf.addBuffer(&debugBuf);
    }

    void startLogging() {
        std::cout.rdbuf(&multiBuf);
    }
};

Logger Logger::instance;

// 使用示例
int main() {
    Logger::get().addFileOutput("D:/res/log.txt");
    Logger::get().addDebugOutput();
    Logger::get().startLogging();

    std::cout << "Hello, World!" << std::endl;
    return 0;
}

C 实现(基于文件描述符)

  • 手动实现日志目标管理和输出分发
  • 通过函数指针和联合体支持多种输出目标
  • 提供Logger_Log函数实现格式化输出
  • 需要手动管理内存和资源释放
#include 
#include 
#include 
#include 

// 输出目标类型
typedef enum {
    OUTPUT_CONSOLE,
    OUTPUT_FILE,
    OUTPUT_DEBUG
} OutputType;

// 输出目标结构体
typedef struct {
    OutputType type;
    union {
        FILE* file;
    };
} OutputTarget;

// 日志管理器结构体
typedef struct {
    OutputTarget* targets;
    int count;
    int capacity;
} Logger;

// 创建日志管理器
Logger* Logger_Create() {
    Logger* logger = (Logger*)malloc(sizeof(Logger));
    logger->count = 0;
    logger->capacity = 4;
    logger->targets = (OutputTarget*)malloc(sizeof(OutputTarget) * logger->capacity);
    return logger;
}

// 销毁日志管理器
void Logger_Destroy(Logger* logger) {
    for (int i = 0; i < logger->count; i++) {
        if (logger->targets[i].type == OUTPUT_FILE) {
            fclose(logger->targets[i].file);
        }
    }
    free(logger->targets);
    free(logger);
}

// 添加控制台输出
void Logger_AddConsole(Logger* logger) {
    if (logger->count >= logger->capacity) {
        logger->capacity *= 2;
        logger->targets = (OutputTarget*)realloc(
            logger->targets, sizeof(OutputTarget) * logger->capacity);
    }
    logger->targets[logger->count].type = OUTPUT_CONSOLE;
    logger->count++;
}

// 添加文件输出
void Logger_AddFile(Logger* logger, const char* filename) {
    if (logger->count >= logger->capacity) {
        logger->capacity *= 2;
        logger->targets = (OutputTarget*)realloc(
            logger->targets, sizeof(OutputTarget) * logger->capacity);
    }
    OutputTarget target;
    target.type = OUTPUT_FILE;
    target.file = fopen(filename, "a");
    if (target.file) {
        logger->targets[logger->count] = target;
        logger->count++;
    }
}

// 添加调试输出
void Logger_AddDebug(Logger* logger) {
    if (logger->count >= logger->capacity) {
        logger->capacity *= 2;
        logger->targets = (OutputTarget*)realloc(
            logger->targets, sizeof(OutputTarget) * logger->capacity);
    }
    logger->targets[logger->count].type = OUTPUT_DEBUG;
    logger->count++;
}

// 日志输出函数
void Logger_Log(Logger* logger, const char* format, ...) {
    va_list args;
    char buffer[1024];

    va_start(args, format);
    vsnprintf(buffer, sizeof(buffer), format, args);
    va_end(args);

    for (int i = 0; i < logger->count; i++) {
        OutputTarget* target = &logger->targets[i];
        switch (target->type) {
            case OUTPUT_CONSOLE:
                printf("%s", buffer);
                fflush(stdout);
                break;
            case OUTPUT_FILE:
                fputs(buffer, target->file);
                fflush(target->file);
                break;
            case OUTPUT_DEBUG:
                OutputDebugStringA(buffer);
                break;
        }
    }
}

// 使用示例
int main() {
    Logger* logger = Logger_Create();
    Logger_AddConsole(logger);
    Logger_AddFile(logger, "log.txt");
    Logger_AddDebug(logger);

    Logger_Log(logger, "Hello, World!\n");
    Logger_Destroy(logger);
    return 0;
}

其他方式实现日志多路输出

1、多目标输出函数(直接调用多个输出接口)

最直接的方式:在日志函数中显式调用多个输出接口(如printf输出到控制台、fprintf输出到文件、OutputDebugString输出到调试窗口),一次性完成多路输出。

#include 
#include 
#include 

// 日志级别
typedef enum { LOG_DEBUG, LOG_INFO, LOG_ERROR } LogLevel;

// 多路输出日志函数
void log_write(LogLevel level, const char* format, ...) {
    va_list args;
    va_start(args, format);

    // 1. 输出到控制台
    vprintf(format, args);

    // 2. 输出到文件(追加模式)
    FILE* f = fopen("log.txt", "a");
    if (f) {
        // 带时间戳
        time_t t = time(NULL);
        fprintf(f, "[%s] ", ctime(&t));
        vfprintf(f, format, args);
        fclose(f);
    }

    // 3. 输出到调试窗口(Windows)
    char debug_buf[1024];
    vsnprintf(debug_buf, sizeof(debug_buf), format, args);
    OutputDebugStringA(debug_buf);

    va_end(args);
}

// 使用示例
int main() {
    log_write(LOG_INFO, "程序启动\n");
    log_write(LOG_DEBUG, "调试信息:x = %d\n", 100);
    return 0;
}
  • 优点:实现简单,无需复杂封装,适合小型项目。
  • 缺点:硬编码输出目标,扩展性差(新增输出目标需修改函数);多线程下需手动加锁,否则可能输出混乱。
2、观察者模式(事件驱动)
#include 
#include 
#include 
#include 
#include 

// 抽象观察者(输出目标)
class LogObserver {
public:
    virtual void write(const std::string& msg) = 0;
    virtual ~LogObserver() = default;
};

// 控制台观察者
class ConsoleObserver : public LogObserver {
public:
    void write(const std::string& msg) override {
        std::cout << msg;
    }
};

// 文件观察者
class FileObserver : public LogObserver {
private:
    std::ofstream file;
public:
    FileObserver(const std::string& path) : file(path, std::ios::app) {}
    void write(const std::string& msg) override {
        if (file.is_open()) {
            file << msg;
        }
    }
};

// 调试窗口观察者(Windows)
class DebugObserver : public LogObserver {
public:
    void write(const std::string& msg) override {
        OutputDebugStringA(msg.c_str());
    }
};

// 日志管理器(主题)
class Logger {
private:
    std::vector<LogObserver*> observers;
    std::mutex mtx; // 多线程安全

public:
    // 注册观察者(添加输出目标)
    void addObserver(LogObserver* obs) {
        std::lock_guard<std::mutex> lock(mtx);
        observers.push_back(obs);
    }

    // 日志输出(通知所有观察者)
    void log(const std::string& msg) {
        std::lock_guard<std::mutex> lock(mtx);
        for (auto obs : observers) {
            obs->write(msg); // 每个观察者自行处理输出
        }
    }
};

// 使用示例
int main() {
    Logger logger;
    logger.addObserver(new ConsoleObserver());
    logger.addObserver(new FileObserver("log.txt"));
    logger.addObserver(new DebugObserver());

    logger.log("程序启动\n");
    logger.log("调试信息:x = 100\n");

    return 0;
}
  • 优点:高度解耦,新增输出目标只需实现LogObserver接口,无需修改日志核心逻辑;扩展性强,适合中大型项目。
  • 缺点:实现稍复杂,需要设计抽象类和接口;多线程下需对观察者列表加锁。
3、异步日志队列(生产者-消费者模式)
#include 
#include 
#include 
#include 
#include 
#include 
#include 

class AsyncLogger {
private:
    std::queue<std::string> msgQueue;
    std::mutex mtx;
    std::condition_variable cv;
    std::thread bgThread;
    bool running = true;

    // 后台线程:处理日志并输出到多目标
    void backgroundThread() {
        while (running) {
            std::string msg;
            // 等待队列中有消息
            {
                std::unique_lock<std::mutex> lock(mtx);
                cv.wait(lock, [this] { return !msgQueue.empty() || !running; });
                if (!running && msgQueue.empty()) break;
                msg = msgQueue.front();
                msgQueue.pop();
            }

            // 输出到多目标
            std::cout << msg; // 控制台
            std::ofstream("log.txt", std::ios::app) << msg; // 文件(简化版)
            OutputDebugStringA(msg.c_str()); // 调试窗口
        }
    }

public:
    AsyncLogger() {
        bgThread = std::thread(&AsyncLogger::backgroundThread, this);
    }

    ~AsyncLogger() {
        running = false;
        cv.notify_one();
        bgThread.join();
    }

    // 前台线程调用:添加日志到队列
    void log(const std::string& msg) {
        std::lock_guard<std::mutex> lock(mtx);
        msgQueue.push(msg);
        cv.notify_one(); // 通知后台线程
    }
};

// 使用示例
int main() {
    AsyncLogger logger;
    logger.log("程序启动\n");
    logger.log("调试信息:x = 100\n");
    return 0;
}
  • 优点:前台线程无阻塞(日志放入队列即可返回),适合高性能、低延迟、高并发场景;多线程安全(队列加锁);可灵活扩展输出目标。
  • 缺点:实现较复杂;日志可能有延迟(异步特性);需处理队列溢出问题(可加限制或丢弃策略)。
4、宏定义封装(简化多目标输出)
#include 
#include 

// 宏定义:输出到控制台+文件+调试窗口
#define LOG(...) do { \
    printf(__VA_ARGS__); \
    FILE* f = fopen("log.txt", "a"); \
    if (f) { fprintf(f, __VA_ARGS__); fclose(f); } \
    char buf[1024]; \
    snprintf(buf, sizeof(buf), __VA_ARGS__); \
    OutputDebugStringA(buf); \
} while(0)

// 使用示例
int main() {
    LOG("程序启动\n");
    LOG("调试信息:x = %d\n", 100);
    return 0;
}
  • 优点:使用简单,一行代码实现多路输出;可通过条件编译(#ifdef)灵活开关某个输出目标(如#ifdef DEBUG才输出到调试窗口)。
  • 缺点:宏定义可读性较差;多线程下需手动加锁;扩展性一般(修改输出目标需改宏)。
5、使用日志库
  1. spdlog(C++):

    • 轻量级、高性能,支持多线程和异步输出。
    • 可通过std::vector配置多个输出目标(控制台、文件、调试窗口等)。
    #include "spdlog/spdlog.h"
    #include "spdlog/sinks/stdout_color_sinks.h"
    #include "spdlog/sinks/basic_file_sink.h"
    
    int main() {
        // 创建多个输出目标(sink)
        auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
        auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("log.txt");
        // 多路输出
        spdlog::logger logger("multi_sink", {console_sink, file_sink});
        logger.info("程序启动");
        logger.debug("调试信息:x = {}", 100);
        return 0;
    }
    
  2. glog(Google Log,C++):

    • 支持输出到文件、控制台,可配置日志级别和滚动策略。
  3. log4c(C语言):

    • C语言的日志库,支持多输出目标和布局配置。

你可能感兴趣的:(流重定向方式实现日志多路输出(c++/c))