如何在spdlog中创建无大小限制的环形缓冲区日志?
解决方案:自定义spdlog Sink直接写入SQLite
核心问题解答
spdlog没有内置“无限制大小的环形缓冲区”——因为环形缓冲区的设计本质就是固定容量、循环覆盖旧数据,无限制存储的结构已经不属于环形缓冲区范畴。你需要的是能动态扩容的日志存储,同时直接对接SQLite以避免冗余文件IO。
最优方案:自定义SQLite Sink
直接实现spdlog的自定义Sink,将日志消息实时写入SQLite数据库,跳过中间文件步骤,完全匹配你的需求。
实现步骤
- 继承spdlog基础Sink类:根据线程需求选择
base_sink_mt(多线程安全)或base_sink_st(单线程)。 - 实现日志写入逻辑:在
log()方法中解析日志的时间、级别、内容等字段,执行SQLite插入操作。 - 保证SQLite线程安全:如果用多线程Sink,需通过互斥锁或SQLite内置线程安全配置(如WAL模式)确保DB操作安全。
- 注册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
相关产品推荐
相关产品推荐

