
实现新功能:打印log并自动保存到数据库
前置工作:拷贝spdlog库和sqlites库的头文件到项目
新加文件spdlog/details/db_client.h
cpp
#pragma once
#include <stdio.h>
#include "sqlite3.h"
namespace spdlog {
namespace details {
class db_client {
public:
db_client() : db_(nullptr) {}
~db_client() { close(); }
// Close the database connection
void close() {
if (db_) {
sqlite3_close(db_);
db_ = nullptr;
}
}
// Check if connected
bool is_connected() const { return db_ != nullptr; }
// Connect to the database or throw if failed
void connect(const std::string &db_name) {
if (sqlite3_open(db_name.c_str(), &db_) != SQLITE_OK) {
throw std::runtime_error("Failed to connect to database");
}
}
// connection fails, throw an exception
void throw_spdlog_ex(const std::string &msg, int err_code) {
throw std::runtime_error(msg + ": " + sqlite3_errmsg(db_));
}
// create table if not exists
void create_table(const std::string &table_name) {
if (!is_connected()) {
throw_spdlog_ex("Database connection is not valid", 0);
}
const std::string sql =
"CREATE TABLE IF NOT EXISTS " + table_name +
" (Id INTEGER PRIMARY KEY AUTOINCREMENT, Log TEXT NOT NULL);";
char *err_msg = nullptr;
if (sqlite3_exec(db_, sql.c_str(), nullptr, nullptr, &err_msg) !=
SQLITE_OK) {
std::string error_msg = "SQL error: ";
error_msg += err_msg ? err_msg : "Unknown error";
sqlite3_free(err_msg);
throw_spdlog_ex(error_msg, 0);
}
}
// Send data to the database
void send(const char *data, size_t n_bytes, std::string &table_name) {
if (!is_connected()) {
throw_spdlog_ex("Database connection is not valid", 0);
}
if (n_bytes == 0) {
return; // No data to send
}
// Execute the SQL command
std::string log_message(data, n_bytes);
std::string sql =
"INSERT INTO " + table_name + " (Log) VALUES ('" + log_message + "');";
char *err_msg = nullptr;
if (sqlite3_exec(db_, sql.c_str(), nullptr, nullptr, &err_msg) !=
SQLITE_OK) {
std::string error_msg = "SQL error: ";
error_msg += err_msg ? err_msg : "Unknown error";
sqlite3_free(err_msg);
throw_spdlog_ex(error_msg, 0);
}
}
private:
sqlite3 *db_; // Database connection handle
};
} // namespace details
} // namespace spdlog
新加文件spdlog/sinks/db_sink.h
cpp
#pragma once
#include <spdlog/common.h>
#include <spdlog/details/db_client.h>
#include <spdlog/details/null_mutex.h>
#include <spdlog/sinks/base_sink.h>
#include <chrono>
#include <functional>
#include <mutex>
#include <string>
#pragma once
namespace spdlog {
namespace sinks {
struct db_sink_config {
std::string db_name;
std::string table_name;
db_sink_config(std::string db_name, std::string table_name)
: db_name{std::move(db_name)}, table_name{std::move(table_name)} {}
};
template <typename Mutex>
class db_sink : public spdlog::sinks::base_sink<Mutex> {
public:
explicit db_sink(db_sink_config sink_config)
: config_{std::move(sink_config)} {
if (!client_.is_connected()) {
client_.connect(config_.db_name);
}
client_.create_table(config_.table_name);
}
~db_sink() override = default;
protected:
void sink_it_(const spdlog::details::log_msg &msg) override {
spdlog::memory_buf_t formatted;
this->formatter_->format(msg, formatted);
client_.send(formatted.data(), formatted.size(), config_.table_name);
}
void flush_() override {}
db_sink_config config_;
details::db_client client_;
};
using db_sink_mt = db_sink<std::mutex>;
using db_sink_st = db_sink<spdlog::details::null_mutex>;
} // namespace sinks
// factory functions
template <typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> db_logger_mt(const std::string &logger_name,
const std::string &db_name,
const std::string &table_name) {
sinks::db_sink_config config(db_name, table_name);
return Factory::template create<sinks::db_sink_mt>(logger_name, config);
}
template <typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> db_logger_st(const std::string &logger_name,
const std::string &db_name,
const std::string &table_name) {
return Factory::template create<sinks::db_sink_st>(logger_name, db_name,
table_name);
}
} // namespace spdlog
测试:
cpp
#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_INFO
#include "spdlog/spdlog.h"
#include "spdlog/sinks/db_sink.h"
void f() {
auto logger = spdlog::get("db_logger");
logger->info("Welcome to spdlog!");
}
int main() {
auto my_logger = spdlog::db_logger_mt("db_logger", "test.db", "LOG_TABLE");
spdlog::set_default_logger(my_logger);
my_logger->set_pattern("[%Y-%m-%d %H:%M:%S.%e] %^[%l]%$ [%t] [%s %!:%#] %v");
my_logger->set_level(spdlog::level::info);
my_logger->info("Hello, {}!", "World");
// 带文件名与行号的日志输出
SPDLOG_LOGGER_INFO(my_logger, "Support for floats {:03.2f}", 1.23456);
SPDLOG_LOGGER_WARN(my_logger, "Easy padding in numbers like {:08d}", 12);
// 输出到默认日志中
spdlog::critical(
"Support for int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42);
spdlog::error("Some error message with arg: {}", 1);
spdlog::warn("Easy padding in numbers like {:08d}", 12);
spdlog::info("Support for floats {:03.2f}", 1.23456);
f();
return 0;
}
CMakeLists.txt
bash
cmake_minimum_required(VERSION 2.8)
project(spdlog_test)
set(CMAKE_CXX_STANDARD 17)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
include_directories(${CMAKE_CURRENT_BINARY_DIR}) # build dir
include_directories( "${PROJECT_SOURCE_DIR}/spdlog")
include_directories( "${PROJECT_SOURCE_DIR}/sqlite3")
add_executable (spdlog_test main.cc sqlite3/sqlite3.c)
target_link_libraries (spdlog_test -lpthread dl)
运行:
bash
mkdir build && cd build
cmake .. && make spdlog_test
../bin/spdlog_test
sqlite3 test.db
select * from LOG_TABLE;
输出

参考: