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

如何在spdlog中创建无大小限制的环形缓冲区日志?

解决方案:自定义spdlog Sink直接写入SQLite

核心问题解答

spdlog没有内置“无限制大小的环形缓冲区”——因为环形缓冲区的设计本质就是固定容量、循环覆盖旧数据,无限制存储的结构已经不属于环形缓冲区范畴。你需要的是能动态扩容的日志存储,同时直接对接SQLite以避免冗余文件IO。

最优方案:自定义SQLite Sink

直接实现spdlog的自定义Sink,将日志消息实时写入SQLite数据库,跳过中间文件步骤,完全匹配你的需求。

实现步骤

  1. 继承spdlog基础Sink类:根据线程需求选择base_sink_mt(多线程安全)或base_sink_st(单线程)。
  2. 实现日志写入逻辑:在log()方法中解析日志的时间、级别、内容等字段,执行SQLite插入操作。
  3. 保证SQLite线程安全:如果用多线程Sink,需通过互斥锁或SQLite内置线程安全配置(如WAL模式)确保DB操作安全。
  4. 注册Sink到Logger:将自定义Sink添加到spdlog的logger实例即可使用。

代码示例

#include <spdlog/spdlog.h>
#include <spdlog/sinks/base_sink.h>
#include <sqlite3.h>
#include <mutex>
#include <stdexcept>

// 多线程安全的SQLite日志Sink
class SQLiteSink : public spdlog::sinks::base_sink_mt {
private:
    sqlite3* db_;
    std::mutex db_mutex_;
    sqlite3_stmt* insert_stmt_;

    void init_db(const std::string& db_path) {
        int rc = sqlite3_open(db_path.c_str(), &db_);
        if (rc != SQLITE_OK) {
            throw std::runtime_error("Failed to open SQLite: " + std::string(sqlite3_errmsg(db_)));
        }

        // 创建日志表(不存在则创建)
        const char* create_table_sql = R"(
            CREATE TABLE IF NOT EXISTS logs (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
                level TEXT NOT NULL,
                message TEXT NOT NULL
            );
        )";
        char* err_msg = nullptr;
        rc = sqlite3_exec(db_, create_table_sql, nullptr, nullptr, &err_msg);
        if (rc != SQLITE_OK) {
            std::string err_str = err_msg;
            sqlite3_free(err_msg);
            sqlite3_close(db_);
            throw std::runtime_error("Failed to create logs table: " + err_str);
        }

        // 预编译插入语句,提升重复插入性能
        const char* insert_sql = "INSERT INTO logs (level, message) VALUES (?, ?);";
        rc = sqlite3_prepare_v2(db_, insert_sql, -1, &insert_stmt_, nullptr);
        if (rc != SQLITE_OK) {
            sqlite3_close(db_);
            throw std::runtime_error("Failed to prepare insert statement: " + std::string(sqlite3_errmsg(db_)));
        }
    }

protected:
    void sink_it_(const spdlog::details::log_msg& msg) override {
        std::lock_guard<std::mutex> lock(db_mutex_);

        // 格式化日志内容
        spdlog::memory_buf_t formatted_buf;
        formatter_->format(msg, formatted_buf);
        std::string message = fmt::to_string(formatted_buf);

        // 获取日志级别字符串
        std::string level = spdlog::level::to_string_view(msg.level).data();

        // 绑定参数并执行插入
        sqlite3_bind_text(insert_stmt_, 1, level.c_str(), -1, SQLITE_TRANSIENT);
        sqlite3_bind_text(insert_stmt_, 2, message.c_str(), -1, SQLITE_TRANSIENT);

        int rc = sqlite3_step(insert_stmt_);
        if (rc != SQLITE_DONE) {
            spdlog::error("SQLite insert failed: {}", sqlite3_errmsg(db_));
        }
        sqlite3_reset(insert_stmt_); // 重置语句以便复用
    }

    void flush_() override {
        std::lock_guard<std::mutex> lock(db_mutex_);
        sqlite3_exec(db_, "COMMIT;", nullptr, nullptr, nullptr);
    }

public:
    explicit SQLiteSink(const std::string& db_path) {
        init_db(db_path);
    }

    ~SQLiteSink() override {
        sqlite3_finalize(insert_stmt_);
        sqlite3_close(db_);
    }
};

// 使用示例
int main() {
    try {
        // 创建自定义SQLite Sink
        auto sqlite_sink = std::make_shared<SQLiteSink>("app_data.db");
        // 创建Logger并绑定Sink
        auto logger = std::make_shared<spdlog::logger>("app_logger", sqlite_sink);
        // 设置日志格式
        logger->set_pattern("[%Y-%m-%d %H:%M:%S] [%l] %v");
        // 设置为全局默认Logger
        spdlog::set_default_logger(logger);

        // 测试日志写入
        spdlog::info("Application initialized");
        spdlog::warn("Low disk space warning");
        spdlog::error("Failed to load configuration file");

        spdlog::shutdown();
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        return 1;
    }
    return 0;
}

简化方案:使用Callback Sink

如果不想自定义完整Sink类,可使用spdlog的callback_sink,将日志回调到自定义函数中再插入SQLite:

#include <spdlog/spdlog.h>
#include <spdlog/sinks/callback_sink.h>
#include <sqlite3.h>
#include <mutex>

std::mutex db_mutex;
sqlite3* db;

void log_to_sqlite(const spdlog::details::log_msg& msg) {
    std::lock_guard<std::mutex> lock(db_mutex);
    spdlog::memory_buf_t formatted_buf;
    spdlog::default_formatter()->format(msg, formatted_buf);
    std::string message = fmt::to_string(formatted_buf);
    std::string level = spdlog::level::to_string_view(msg.level).data();

    char* err_msg = nullptr;
    std::string insert_sql = fmt::format("INSERT INTO logs (level, message) VALUES ('{}', '{}');", level, message);
    sqlite3_exec(db, insert_sql.c_str(), nullptr, nullptr, &err_msg);
    if (err_msg != nullptr) {
        spdlog::error("SQLite insert error: {}", err_msg);
        sqlite3_free(err_msg);
    }
}

int main() {
    sqlite3_open("app_data.db", &db);
    // 提前创建logs表(省略表创建逻辑)

    auto callback_sink = std::make_shared<spdlog::sinks::callback_sink_mt>(&log_to_sqlite);
    auto logger = std::make_shared<spdlog::logger>("callback_logger", callback_sink);
    spdlog::set_default_logger(logger);

    spdlog::info("Test log via callback sink");

    sqlite3_close(db);
    spdlog::shutdown();
    return 0;
}

性能优化建议

  • 批量插入:若日志量极大,可在Sink中缓存一定数量的日志,达到阈值后批量插入,减少SQLite操作次数。
  • 异步日志:使用spdlog的异步工厂(spdlog::async_factory)创建Logger,解耦日志生产与DB写入操作,避免阻塞业务线程。
  • SQLite配置优化:开启WAL模式(PRAGMA journal_mode=WAL;)、调整同步级别(PRAGMA synchronous=NORMAL;),提升写入性能。

内容的提问来源于stack exchange,提问作者whataretheydoing

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 11:38:09