C++ 日志框架深度定制:高性能、低延迟日志系统构建

好的,各位观众老爷们,大家好!今天咱们来聊聊一个让程序员们又爱又恨的话题——日志!

都说程序猿三大定律:Debug、Bug Fix、日志!日志这玩意儿,平时你可能觉得它可有可无,但真到了线上出问题的时候,它就是救命稻草!但是,默认的日志框架,要么性能不行,要么定制性差,搞得我们抓耳挠腮。

所以,今天我就来教大家如何打造一个高性能、低延迟、高度定制的C++日志系统,让你在关键时刻不再掉链子!

第一章:磨刀不误砍柴工:需求分析与设计

咱们不能一上来就撸代码,得先想清楚我们要干啥。一个优秀的日志系统,起码要满足以下几点:

  1. 高性能: 不能因为写日志拖慢程序的运行速度,尤其是在高并发场景下。
  2. 低延迟: 日志要及时记录,不能等到问题都发生了才姗姗来迟。
  3. 可配置性: 能灵活配置日志级别、输出格式、输出目标等等。
  4. 易用性: 用起来要简单方便,不能增加程序员的学习成本。
  5. 扩展性: 方便后续添加新的功能,比如支持更多的输出目标,或者更高级的过滤规则。
  6. 线程安全: 必须保证在多线程环境下正常工作,避免数据竞争和死锁。

针对这些需求,咱们可以设计一个模块化的架构:

  • 日志器 (Logger): 负责接收日志消息,并将其传递给后续模块。
  • 格式化器 (Formatter): 负责将日志消息格式化成字符串。
  • 输出器 (Appender): 负责将格式化后的日志字符串输出到不同的目标,比如控制台、文件、网络等等。
  • 过滤器 (Filter): 负责根据一定的规则过滤日志消息,只输出符合条件的日志。

可以用一个表格来总结一下:

模块 职责
Logger 接收日志消息,并传递给后续模块。
Formatter 将日志消息格式化成字符串。
Appender 将格式化后的日志字符串输出到不同的目标(控制台、文件、网络等)。
Filter 根据规则过滤日志消息,只输出符合条件的日志。

第二章:工欲善其事:基础类与工具类

在开始编写核心模块之前,我们需要一些基础类和工具类来打好地基。

  1. 日志级别枚举:
enum class LogLevel {
    DEBUG,
    INFO,
    WARN,
    ERROR,
    FATAL
};

std::ostream& operator<<(std::ostream& os, LogLevel level) {
    switch (level) {
        case LogLevel::DEBUG:   os << "DEBUG";   break;
        case LogLevel::INFO:    os << "INFO";    break;
        case LogLevel::WARN:    os << "WARN";    break;
        case LogLevel::ERROR:   os << "ERROR";   break;
        case LogLevel::FATAL:   os << "FATAL";   break;
        default:                os << "UNKNOWN"; break;
    }
    return os;
}
  1. 日志事件类:
#include <chrono>
#include <string>
#include <thread>
#include <sstream>

class LogEvent {
public:
    LogEvent(LogLevel level, const std::string& filename, int line, const std::string& message)
        : level_(level), filename_(filename), line_(line), message_(message), timestamp_(std::chrono::system_clock::now()) {}

    LogLevel getLevel() const { return level_; }
    const std::string& getFilename() const { return filename_; }
    int getLine() const { return line_; }
    const std::string& getMessage() const { return message_; }
    std::chrono::system_clock::time_point getTimestamp() const { return timestamp_; }

    // 获取当前线程ID的字符串表示
    std::string getThreadId() const {
        std::stringstream ss;
        ss << std::this_thread::get_id();
        return ss.str();
    }

private:
    LogLevel level_;
    std::string filename_;
    int line_;
    std::string message_;
    std::chrono::system_clock::time_point timestamp_;
};
  1. 线程安全的单例模式: (避免多个Logger实例造成的混乱)
#include <mutex>

template <typename T>
class Singleton {
public:
    static T& getInstance() {
        std::call_once(onceFlag, []() { instance.reset(new T()); });
        return *instance;
    }

private:
    Singleton() {}
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    static std::unique_ptr<T> instance;
    static std::once_flag onceFlag;
};

template <typename T>
std::unique_ptr<T> Singleton<T>::instance = nullptr;

template <typename T>
std::once_flag Singleton<T>::onceFlag;

第三章:核心模块:Logger、Formatter、Appender

  1. Logger 类:
#include <vector>

class Logger {
public:
    using AppenderPtr = std::shared_ptr<class Appender>;  // Forward declaration
    using FormatterPtr = std::shared_ptr<class Formatter>; // Forward declaration

    Logger() : level_(LogLevel::INFO) {}

    void log(LogLevel level, const std::string& filename, int line, const std::string& message) {
        if (level >= level_) {
            LogEvent event(level, filename, line, message);
            for (auto& appender : appenders_) {
                appender->log(event);
            }
        }
    }

    void addAppender(AppenderPtr appender) {
        appenders_.push_back(appender);
    }

    void setLevel(LogLevel level) {
        level_ = level;
    }

    LogLevel getLevel() const {
        return level_;
    }

    void setFormatter(FormatterPtr formatter) {
        formatter_ = formatter;
        for (auto& appender : appenders_) {
            appender->setFormatter(formatter_);
        }
    }

private:
    LogLevel level_;
    std::vector<AppenderPtr> appenders_;
    FormatterPtr formatter_;
};

// Helper macros
#define LOG_DEBUG(logger, msg) logger.log(LogLevel::DEBUG, __FILE__, __LINE__, msg)
#define LOG_INFO(logger, msg)  logger.log(LogLevel::INFO,  __FILE__, __LINE__, msg)
#define LOG_WARN(logger, msg)  logger.log(LogLevel::WARN,  __FILE__, __LINE__, msg)
#define LOG_ERROR(logger, msg) logger.log(LogLevel::ERROR, __FILE__, __LINE__, msg)
#define LOG_FATAL(logger, msg) logger.log(LogLevel::FATAL, __FILE__, __LINE__, msg)
  1. Formatter 类:
#include <sstream>
#include <iomanip>

class Formatter {
public:
    virtual std::string format(const LogEvent& event) {
        std::stringstream ss;
        auto timestamp = event.getTimestamp();
        auto time_t_timestamp = std::chrono::system_clock::to_time_t(timestamp);
        std::tm tm_timestamp;
    #ifdef _MSC_VER  // For Visual Studio
        localtime_s(&tm_timestamp, &time_t_timestamp);
    #else           // For other compilers (e.g., GCC, Clang)
        localtime_r(&time_t_timestamp, &tm_timestamp);
    #endif
        ss << std::put_time(&tm_timestamp, "%Y-%m-%d %H:%M:%S"); // Format timestamp
        ss << " [" << event.getLevel() << "] "; // Log level
        ss << "[" << event.getThreadId() << "] "; // Thread ID
        ss << event.getFilename() << ":" << event.getLine() << " - "; // File and line
        ss << event.getMessage(); // Message
        return ss.str();
    }

    virtual ~Formatter() = default;
};
  1. Appender 类:
#include <iostream>
#include <fstream>
#include <mutex>

class Appender {
public:
    using FormatterPtr = std::shared_ptr<Formatter>;

    virtual void log(const LogEvent& event) {
        std::lock_guard<std::mutex> lock(mutex_);  // 线程安全
        std::string formattedMessage = formatter_->format(event);
        append(formattedMessage);
    }

    void setFormatter(FormatterPtr formatter) {
        std::lock_guard<std::mutex> lock(mutex_);
        formatter_ = formatter;
    }

    virtual ~Appender() = default;

protected:
    virtual void append(const std::string& formattedMessage) = 0;

    FormatterPtr formatter_;
    std::mutex mutex_; // 用于线程安全的互斥锁
};

// Console Appender
class ConsoleAppender : public Appender {
protected:
    void append(const std::string& formattedMessage) override {
        std::cout << formattedMessage << std::endl;
    }
};

// File Appender
class FileAppender : public Appender {
public:
    FileAppender(const std::string& filename) : filename_(filename) {
        file_.open(filename_, std::ios::app); // Open file in append mode
        if (!file_.is_open()) {
            std::cerr << "Error opening file: " << filename_ << std::endl;
        }
    }

    ~FileAppender() override {
        if (file_.is_open()) {
            file_.close();
        }
    }

protected:
    void append(const std::string& formattedMessage) override {
        if (file_.is_open()) {
            file_ << formattedMessage << std::endl;
            file_.flush(); // Flush buffer to ensure immediate write
        }
    }

private:
    std::string filename_;
    std::ofstream file_;
};

第四章:锦上添花:Filter(可选)

如果你需要更精细的日志控制,可以添加Filter模块。

#include <regex>

class Filter {
public:
    virtual bool filter(const LogEvent& event) {
        return true; // 默认不进行过滤
    }

    virtual ~Filter() = default;
};

// 基于正则表达式的过滤器
class RegexFilter : public Filter {
public:
    RegexFilter(const std::string& pattern, bool matchMessage = true) : pattern_(pattern), matchMessage_(matchMessage) {}

    bool filter(const LogEvent& event) override {
        if (matchMessage_) {
            std::regex re(pattern_);
            return std::regex_search(event.getMessage(), re);
        } else {
            // 可以根据文件名、日志级别等进行过滤
            return true;
        }
    }

private:
    std::string pattern_;
    bool matchMessage_;
};

要使用Filter,需要在Logger中添加Filter列表,并在log方法中进行过滤。

// Logger class (updated)
class Logger {
public:
    using AppenderPtr = std::shared_ptr<class Appender>;
    using FormatterPtr = std::shared_ptr<class Formatter>;
    using FilterPtr = std::shared_ptr<class Filter>;

    Logger() : level_(LogLevel::INFO) {}

    void log(LogLevel level, const std::string& filename, int line, const std::string& message) {
        if (level >= level_) {
            LogEvent event(level, filename, line, message);
            bool shouldLog = true;
            for (auto& filter : filters_) {
                if (!filter->filter(event)) {
                    shouldLog = false;
                    break;
                }
            }
            if (shouldLog) {
                for (auto& appender : appenders_) {
                    appender->log(event);
                }
            }
        }
    }

    void addAppender(AppenderPtr appender) {
        appenders_.push_back(appender);
    }

    void addFilter(FilterPtr filter) {
        filters_.push_back(filter);
    }

    // ... (其他方法)

private:
    LogLevel level_;
    std::vector<AppenderPtr> appenders_;
    std::vector<FilterPtr> filters_;
    FormatterPtr formatter_;
};

第五章:性能优化:异步日志与缓冲

在高并发场景下,同步写日志可能会成为性能瓶颈。为了解决这个问题,我们可以采用异步日志的方式,将日志消息先放入一个缓冲区,然后由一个单独的线程负责将缓冲区中的日志写入到目标。

#include <queue>
#include <condition_variable>

class AsyncAppender : public Appender {
public:
    AsyncAppender(std::unique_ptr<Appender> appender) : appender_(std::move(appender)), running_(true) {
        worker_thread_ = std::thread([this]() {
            while (running_) {
                std::unique_lock<std::mutex> lock(mutex_);
                condition_.wait(lock, [this]() { return !queue_.empty() || !running_; });

                if (!queue_.empty()) {
                    std::string message = queue_.front();
                    queue_.pop();
                    lock.unlock(); // Unlock before writing to appender
                    appender_->append(message);
                }
            }
        });
    }

    ~AsyncAppender() override {
        {
            std::lock_guard<std::mutex> lock(mutex_);
            running_ = false;
        }
        condition_.notify_one();
        worker_thread_.join();
    }

protected:
    void append(const std::string& formattedMessage) override {
        {
            std::lock_guard<std::mutex> lock(mutex_);
            queue_.push(formattedMessage);
        }
        condition_.notify_one();
    }

private:
    std::unique_ptr<Appender> appender_;
    std::queue<std::string> queue_;
    std::mutex mutex_;
    std::condition_variable condition_;
    std::thread worker_thread_;
    bool running_;
};

第六章:使用示例:让日志飞起来

int main() {
    // 获取 Logger 实例
    Logger& logger = Singleton<Logger>::getInstance();

    // 创建 Formatter
    auto formatter = std::make_shared<Formatter>();

    // 创建 Appender (Console)
    auto consoleAppender = std::make_shared<ConsoleAppender>();
    consoleAppender->setFormatter(formatter);

    // 创建 Appender (File)
    auto fileAppender = std::make_shared<FileAppender>("my_log.txt");
    fileAppender->setFormatter(formatter);

    // 将 Appender 添加到 Logger
    logger.addAppender(consoleAppender);
    logger.addAppender(fileAppender);

    // 创建异步Appender,将FileAppender放到异步Appender中
    auto asyncFileAppender = std::make_unique<AsyncAppender>(std::make_unique<FileAppender>("my_async_log.txt"));
    asyncFileAppender->setFormatter(formatter);
    logger.addAppender(std::shared_ptr<AsyncAppender>(asyncFileAppender.release()));

    // 设置日志级别
    logger.setLevel(LogLevel::DEBUG);

    // 记录日志
    LOG_DEBUG(logger, "This is a debug message.");
    LOG_INFO(logger, "This is an info message.");
    LOG_WARN(logger, "This is a warning message.");
    LOG_ERROR(logger, "This is an error message.");
    LOG_FATAL(logger, "This is a fatal message.");

    // 使用循环模拟高并发
    for (int i = 0; i < 1000; ++i) {
        LOG_INFO(logger, "Loop iteration: " + std::to_string(i));
    }

    return 0;
}

第七章:总结与展望

今天我们一起打造了一个高性能、低延迟、高度定制的C++日志系统。 当然,这只是一个基础框架,你还可以根据自己的需求进行扩展,比如:

  • 支持更多的输出目标,比如数据库、网络等等。
  • 支持更高级的过滤规则,比如基于时间、线程等等。
  • 支持自定义的日志格式,比如JSON、XML等等。
  • 集成到现有的项目中,并进行性能测试。
  • 使用第三方库,如spdlog, glog等。

记住,日志系统不是一蹴而就的,需要不断地迭代和优化。希望今天的分享能对你有所帮助,让你在日志的世界里自由翱翔!

感谢各位观众老爷的观看,咱们下期再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注