spdlog自定义功能

实现新功能:打印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

测试:

main.cc

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;

输出

参考:

spdlog日志库--基础介绍-CSDN博客

一个简单的spdlog使用示例 - 二次元攻城狮 - 博客园