高性能C++ 日志實(shí)戰(zhàn):spdlog 核心架構(gòu)解析與最佳實(shí)踐指南
一、spdlog 介紹
spdlog 是一個(gè)高性能、超快速、零配置的 C++ 日志庫(kù),它旨在提供簡(jiǎn)潔的 API 和豐富的功能,同時(shí)保持高性能的日志記錄。它支持多種輸出目標(biāo)、格式化選項(xiàng)、線程安全以及異步日志記錄。
特點(diǎn):
- 高性能: spdlog 專為速度而設(shè)計(jì),即使在高負(fù)載情況下也能保持良好的性能。
- 零配置: 無(wú)需復(fù)雜的配置,只需包含頭文件即可在項(xiàng)目中使用。
- 異步日志: 支持異步日志記錄,減少對(duì)主線程的影響。
- 格式化: 支持自定義日志消息的格式化,包括時(shí)間戳、線程 ID、日志級(jí)別等。
- 多平臺(tái): 跨平臺(tái)兼容,支持 Windows、Linux、macOS 等操作系統(tǒng)。
- 豐富的 API: 提供豐富的日志級(jí)別和操作符重載,方便記錄各種類型的日志。
二、spdlog 安裝
1. 直接命令安裝(適用于 Ubuntu/Debian 系統(tǒng))
(1)步驟:
sudo apt-get update # 更新軟件源 sudo apt-get install libspdlog-dev # 安裝開發(fā)庫(kù)
(2)特點(diǎn):
- 簡(jiǎn)單快捷: 適合快速部署,無(wú)需手動(dòng)編譯。
- 版本受限: 依賴系統(tǒng)倉(cāng)庫(kù)的版本,可能非最新(如 Ubuntu 20.04 默認(rèn)版本較舊)。
(3)驗(yàn)證安裝:
- 檢查頭文件: ls /usr/include/spdlog/
- 檢查庫(kù)文件: ls /usr/lib/x86_64-linux-gnu/libspdlog*

2. 源碼編譯安裝
# 下載源碼 git clone https://github.com/gabime/spdlog.git # 切換目錄 cd spdlog/ # 創(chuàng)建并進(jìn)入構(gòu)建目錄 mkdir build cd build/ # 執(zhí)行構(gòu)建命令,生成 Makefile cmake .. # 編譯代碼 make # 安裝到系統(tǒng)目錄(默認(rèn) /usr/local) sudo make install
三、spdlog 的重要組成結(jié)構(gòu)
1. 日志輸出等級(jí)枚舉
- 日志輸出等級(jí)枚舉:
namespace spdlog {
namespace level {
enum level_enum : int
{
trace = SPDLOG_LEVEL_TRACE,
debug = SPDLOG_LEVEL_DEBUG,
info = SPDLOG_LEVEL_INFO,
warn = SPDLOG_LEVEL_WARN,
error = SPDLOG_LEVEL_ERROR,
critical = SPDLOG_LEVEL_CRITICAL,
off = SPDLOG_LEVEL_OFF,
n_levels
};
}
}在 spdlog/include/spdlog/common.h 頭文件中,其宏定義如下:
#define SPDLOG_LEVEL_TRACE 0
#define SPDLOG_LEVEL_DEBUG 1
#define SPDLOG_LEVEL_INFO 2
#define SPDLOG_LEVEL_WARN 3
#define SPDLOG_LEVEL_ERROR 4
#define SPDLOG_LEVEL_CRITICAL 5
#define SPDLOG_LEVEL_OFF 6
2. (同步)日志記錄器類(含 自定義日志輸出格式 接口的使用介紹 )
- (同步)日志記錄器類:
namespace spdlog {
class logger
{
// 構(gòu)造函數(shù)
logger(std::string name);
logger(std::string name, sink_ptr single_sink);
logger(std::string name, sinks_init_list sinks);
// 設(shè)置輸出等級(jí)(只輸出 高于或等于 此等級(jí)的日志)
void set_level(level::level_enum log_level);
// 自定義 日志輸出格式
void set_pattern(const std::string& pattern);
// 以不同等級(jí) 輸出日志信息的接口
template<typename... Args>
void trace(fmt::format_string<Args...> fmt, Args &&...args)
template<typename... Args>
void debug(fmt::format_string<Args...> fmt, Args &&...args)
template<typename... Args>
void info(fmt::format_string<Args...> fmt, Args &&...args)
template<typename... Args>
void warn(fmt::format_string<Args...> fmt, Args &&...args)
template<typename... Args>
void error(fmt::format_string<Args...> fmt, Args &&...args)
template<typename... Args>
void critical(fmt::format_string<Args...> fmt, Args &&...args)
// 立刻刷新日志信息
void flush();
// 設(shè)置刷新策略(指定一個(gè)日志等級(jí),一旦有 高于或等于指定等級(jí)的日志,立刻刷新日志信息)
void flush_on(level::level_enum log_level);
};
}- logger類內(nèi),set_pattern接口的使用:
假設(shè)Log 是logger類對(duì)象的智能指針
Log->set_pattern("%Y-%m-%d %H:%M:%S [%t] [%-8l] %v");
%t - 線程 ID(Thread ID)
%n - 日志器名稱(Logger name)
%l - 日志級(jí)別名稱(Level name),如 INFO, DEBUG, ERROR 等
%v - 日志內(nèi)容(message)
%Y - 年(Year)
%m - 月(Month)
%d - 日(Day)
%H - 小時(shí)(24-hour format)
%M - 分鐘(Minute)
%S - 秒(Second)3. 線程池類(含 單例模式管理全局線程池實(shí)例 的介紹 )
- spdlog中只有一種線程池實(shí)現(xiàn),即spdlog::details::thread_pool類
namespace spdlog {
namespace details {
class thread_pool
{
thread_pool(size_t q_max_items, size_t threads_n, std::function<void()> on_thread_start,
std::function<void()> on_thread_stop);
thread_pool(size_t q_max_items, size_t threads_n, std::function<void()> on_thread_start);
thread_pool(size_t q_max_items, size_t threads_n);
};
}
}- 單例模式的實(shí)現(xiàn)
通過registry類(單例)持有 全局線程池實(shí)例:
// registry 類(單例)管理全局線程池
namespace spdlog {
namespace details {
class registry
{
std::shared_ptr<thread_pool> _tp; // 全局線程池實(shí)例
std::mutex _mutex; // 線程安全鎖
public:
// 獲取單例實(shí)例(線程安全)
static registry &instance()
{
static registry s_instance; // 靜態(tài)實(shí)例(C++11保證線程安全)
return s_instance;
}
// 獲取全局線程池(延遲初始化)
std::shared_ptr<thread_pool> get_tp()
{
std::lock_guard<std::mutex> lock(_mutex);
if (!_tp)
{
// 首次調(diào)用時(shí)創(chuàng)建默認(rèn)線程池(8192隊(duì)列 + 1線程)
_tp = std::make_shared<thread_pool>(8192, 1);
}
return _tp;
}
// 重置全局線程池(線程安全)
void set_tp(std::shared_ptr<thread_pool> new_tp)
{
std::lock_guard<std::mutex> lock(_mutex);
// 先停止舊線程池的所有任務(wù)
if (_tp)
{
_tp->shutdown(); // 內(nèi)部停止所有工作線程
}
_tp = std::move(new_tp); // 替換為新線程池
}
private:
registry() = default; // 禁用外部構(gòu)造
registry(const registry&) = delete; // 禁用拷貝
registry& operator=(const registry&) = delete; // 禁用賦值
};
}
}- 通過spdlog庫(kù)中的 spdlog::init_thread_pool() 和 spdlog::thread_pool() 函數(shù) 來使用 registry類(單例)持有的 全局線程池實(shí)例
namespace spdlog {
// 獲取全局線程池的共享指針
std::shared_ptr<details::thread_pool> thread_pool()
{
return details::registry::instance().get_tp();
}
// 重置全局線程池(指定隊(duì)列大小和線程數(shù))
void init_thread_pool(size_t q_size, size_t thread_count)
{
auto new_tp = std::make_shared<details::thread_pool>(q_size, thread_count);
details::registry::instance().set_tp(new_tp);
}
}- 注意: spdlog::init_thread_pool() 重置全局線程池時(shí),如果 static registry s_instance(registry類單例,靜態(tài)對(duì)象)的 _tp已經(jīng)指向了一個(gè)線程池實(shí)例,_tp會(huì)銷毀指向的線程池實(shí)例,然后指向新創(chuàng)建的線程池實(shí)例

4. 異步日志記錄器類(與 日志記錄器類 對(duì)比)
- async_logger 是異步日志的核心實(shí)現(xiàn)類,繼承自 logger 基類。它通過線程池和任務(wù)隊(duì)列實(shí)現(xiàn)異步日志記錄:
namespace spdlog {
class async_logger final : public logger
{
async_logger(std::string logger_name, sinks_init_list sinks_list, std::weak_ptr<details::thread_pool> tp,
async_overflow_policy overflow_policy = async_overflow_policy::block);
async_logger(std::string logger_name, sink_ptr single_sink, std::weak_ptr<details::thread_pool> tp,
async_overflow_policy overflow_policy = async_overflow_policy::block);
}
}| 類型 | 日志寫入流程 | 線程模型 |
|---|---|---|
| (同步)日志記錄器 | 主線程直接調(diào)用I/O操作(如文件寫入、控制臺(tái)輸出) • 日志生成 → 立即執(zhí)行磁盤/網(wǎng)絡(luò)I/O → 主線程阻塞等待完成 | 單線程模型:日志I/O占用主線程時(shí)間片 |
| 異步日志記錄器 | 主線程將日志存入內(nèi)存隊(duì)列(任務(wù)隊(duì)列) • 日志生成 → 存入隊(duì)列 → 立即返回主線程 • 后臺(tái)線程池消費(fèi)隊(duì)列并執(zhí)行I/O | 生產(chǎn)者-消費(fèi)者模型:主線程與I/O線程分離 |
關(guān)鍵區(qū)別:異步日志通過內(nèi)存隊(duì)列緩沖和線程池異步刷盤,避免主線程因I/O等待被阻塞
5. 日志記錄器工廠類
using async_factory = async_factory_impl<async_overflow_policy::block>;
//創(chuàng)建一個(gè)彩色輸出到標(biāo)準(zhǔn)輸出的日志記錄器,默認(rèn)工廠 創(chuàng)建同步日志記錄器
template<typename Factory = spdlog::synchronous_factory>
std::shared_ptr<logger> stdout_color_mt(const std::string &logger_name,
color_mode mode = color_mode::automatic);
//標(biāo)準(zhǔn)錯(cuò)誤
template<typename Factory = spdlog::synchronous_factory>
std::shared_ptr<logger> stderr_color_mt(const std::string &logger_name,
color_mode mode = color_mode::automatic);
// 指定文件
template<typename Factory = spdlog::synchronous_factory>
std::shared_ptr<logger> basic_logger_mt(const std::string &logger_name, const filename_t &filename,
bool truncate = false,
const file_event_handlers &event_handlers = {})
//循環(huán)文件
template<typename Factory = spdlog::synchronous_factory>
std::shared_ptr<logger> rotating_logger_mt(const std::string &logger_name, const filename_t &filename,
size_t max_file_size, size_t max_files,
bool rotate_on_open = false)日志記錄器工廠類:封裝了 日志記錄器類對(duì)象 的構(gòu)建 和 配置過程
有了日志記錄器工廠類,我們不需要關(guān)心 日志記錄器類對(duì)象 的構(gòu)建 和 配置過程,可以通過API接口一鍵構(gòu)造 日志記錄器類對(duì)象
spdlog::basic_logger_mt()接口 的 第三個(gè)參數(shù) truncate 的功能:
| 值 | 行為 | 適用場(chǎng)景 |
|---|---|---|
| true | 若日志文件已存在,清空文件內(nèi)容,從頭開始寫入新日志。 | 需要覆蓋舊日志的場(chǎng)景(如臨時(shí)調(diào)試) |
| false (默認(rèn)) | 若日志文件已存在,追加新日志到文件末尾;若文件不存在則創(chuàng)建新文件。 | 長(zhǎng)期運(yùn)行的程序需保留歷史日志的場(chǎng)景 |
- 創(chuàng)建 同步日志記錄器對(duì)象
采用默認(rèn)工廠,創(chuàng)建的都是 同步日志記錄器對(duì)象
(1)創(chuàng)建 向標(biāo)準(zhǔn)輸出寫入日志 的同步日志記錄器對(duì)象
// 使用stdout_color_mt接口,只需指定 要?jiǎng)?chuàng)建的日志記錄器對(duì)象的名字,
// 它就會(huì)創(chuàng)建 指定名字的日志記錄器對(duì)象,并返回指向該對(duì)象的智能指針
auto log = spdlog::stdout_color_mt("sync_logger");
(2)創(chuàng)建 向指定文件寫入日志 的同步日志記錄器對(duì)象
// 使用basic_logger_mt接口,需指定 要?jiǎng)?chuàng)建的日志記錄器對(duì)象的名字 和 文件,
// 它就會(huì)創(chuàng)建 向指定文件寫入日志的 指定名字的日志記錄器對(duì)象,并返回指向該對(duì)象的智能指針
auto file_log = spdlog::basic_logger_mt("file_sync_logger", "log.txt");
- 創(chuàng)建 異步日志記錄器對(duì)象
要?jiǎng)?chuàng)建異步日志記錄器對(duì)象,需要顯式指定 工廠類型(spdlog::async_factory)
(1)創(chuàng)建 向標(biāo)準(zhǔn)輸出寫入日志 的異步日志記錄器對(duì)象
auto log = spdlog::stdout_color_mt<spdlog::async_factory>("sync_logger");
(2)創(chuàng)建 向指定文件寫入日志 的異步日志記錄器對(duì)象
auto file_log = spdlog::basic_logger_mt<spdlog::async_factory>("file_sync_logger", "log.txt");
6. 全局接口
namespace spdlog {
// 日志刷新策略-每隔 N 秒刷新一次
void flush_every(std::chrono::seconds interval);
// 設(shè)置輸出等級(jí)(只輸出 高于或等于 此等級(jí)的日志)
void set_level(level::level_enum log_level);
// 設(shè)置刷新策略(指定一個(gè)日志等級(jí),一旦有 高于或等于指定等級(jí)的日志,立刻刷新日志信息)
void flush_on(level::level_enum log_level);
// 自定義 日志輸出格式
void set_pattern(const std::string& pattern);
}注:全局配置的 優(yōu)先級(jí)低于 日志記錄器對(duì)象的專屬配置!
spdlog 采用作用域逐級(jí)覆蓋的配置策略:
- 全局配置(spdlog::set_level()、spdlog::flush_on()、spdlog::set_pattern())
影響所有未單獨(dú)配置的日志記錄器,作為默認(rèn)值存在。
- 日志記錄器專屬配置 (logger->set_level()、logger->flush_on()、logger->set_pattern())
每個(gè)日志器對(duì)象獨(dú)立配置,優(yōu)先級(jí)高于全局配置。
四、spdlog 的使用
1. 通過全局接口配置,創(chuàng)建同步日志器 (向標(biāo)準(zhǔn)輸出寫日志)
要使用spdlog庫(kù),在你的 C++ 源文件中必須包含 spdlog 的頭文件:
#include <spdlog/spdlog.h>
- 通過全局接口配置,創(chuàng)建同步日志器 (向標(biāo)準(zhǔn)輸出寫日志)
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h> // 包含 spdlog::stdout_color_mt() 的實(shí)現(xiàn)
int main()
{
// 1. 全局配置
spdlog::flush_on(spdlog::level::level_enum::info);
// 設(shè)置輸出等級(jí)(只輸出 高于或等于 info等級(jí)的日志)
spdlog::set_level(spdlog::level::level_enum::info);
// 自定義 日志輸出格式
spdlog::set_pattern("%Y-%m-%d %H:%M:%S [%t] [%-8l] %v");
// 日志刷新策略-每隔 1 秒刷新一次
spdlog::flush_every(std::chrono::seconds(1));
// 2. 創(chuàng)建同步日志器(向標(biāo)準(zhǔn)輸出寫日志)
auto log = spdlog::stdout_color_mt("sync_logger");
// 3. 使用同步日志器 向標(biāo)準(zhǔn)輸出寫日志
log->trace("你好!{}", "中國(guó)"); // {}是占位符,不區(qū)分?jǐn)?shù)據(jù)類型
log->debug("你好!{}","中國(guó)");
log->info("你好!{}","中國(guó)");
log->warn("你好!{}","中國(guó)");
log->error("你好!{}","中國(guó)");
log->critical("你好!{}","中國(guó)");
return 0;
}注:spdlog庫(kù)依賴 fmt庫(kù),所以鏈接時(shí),還需要顯式指定fmt庫(kù)


2. 創(chuàng)建同步日志器 (向指定文件寫日志),并使用同步日志器的專用配置接口
#include <spdlog/spdlog.h>
#include <spdlog/sinks/basic_file_sink.h> // 包含 spdlog::basic_logger_mt() 的實(shí)現(xiàn)
int main()
{
// 1. 全局配置
// 日志刷新策略-每隔 1 秒刷新一次
spdlog::flush_every(std::chrono::seconds(1));
// 2. 創(chuàng)建同步日志器(向當(dāng)前目錄下的log.txt文件 寫日志)
auto file_log = spdlog::basic_logger_mt("sync_logger", "log.txt");
// 使用同步日志器的專用配置接口
file_log->flush_on(spdlog::level::level_enum::info);
file_log->set_level(spdlog::level::level_enum::info);
file_log->set_pattern("%Y-%m-%d %H:%M:%S [%t] [%-8l] %v");
// 3. 使用同步日志器 向指定文件寫日志
file_log->trace("你好!{}", "中國(guó)"); // {}是占位符,不區(qū)分?jǐn)?shù)據(jù)類型
file_log->debug("你好!{}","中國(guó)");
file_log->info("你好!{}","中國(guó)");
file_log->warn("你好!{}","中國(guó)");
file_log->error("你好!{}","中國(guó)");
file_log->critical("你好!{}","中國(guó)");
return 0;
}

3. 創(chuàng)建異步日志器 (向指定文件寫日志)
#include <spdlog/spdlog.h>
#include <spdlog/sinks/basic_file_sink.h> // 包含 spdlog::basic_logger_mt() 的實(shí)現(xiàn)
#include <spdlog/async.h> // 包含 spdlog::async_factory工廠類的實(shí)現(xiàn)
int main()
{
// 1. 全局配置
// 日志刷新策略-每隔 1 秒刷新一次
spdlog::flush_every(std::chrono::seconds(1));
// 初始化全局線程池 的任務(wù)隊(duì)列數(shù) 和 線程數(shù)量
spdlog::init_thread_pool(4096, 2);
// 2. 顯式指定工廠類, 創(chuàng)建異步日志器(向當(dāng)前目錄下的log.txt文件 寫日志)
// 創(chuàng)建異步日志器對(duì)象時(shí),需要指定 線程池實(shí)例,
// basic_logger_mt接口 創(chuàng)建異步日志器對(duì)象時(shí),會(huì)指定 全局線程池實(shí)例,
// 如果之前未初始化全局線程池的配置,全局線程池實(shí)例采用默認(rèn)配置:8192隊(duì)列 + 1線程
auto file_log = spdlog::basic_logger_mt<spdlog::async_factory>("sync_logger", "log.txt");
// 使用異步日志器的專用配置接口
file_log->flush_on(spdlog::level::level_enum::info);
file_log->set_level(spdlog::level::level_enum::info);
file_log->set_pattern("%Y-%m-%d %H:%M:%S [%t] [%-8l] %v");
// 3. 使用異步日志器 向指定文件寫日志
file_log->trace("你好!{}", "中國(guó)"); // {}是占位符,不區(qū)分?jǐn)?shù)據(jù)類型
file_log->debug("你好!{}","中國(guó)");
file_log->info("你好!{}","中國(guó)");
file_log->warn("你好!{}","中國(guó)");
file_log->error("你好!{}","中國(guó)");
file_log->critical("你好!{}","中國(guó)");
return 0;
}
到此這篇關(guān)于高性能C++ 日志實(shí)戰(zhàn):spdlog 核心架構(gòu)解析與最佳實(shí)踐指南的文章就介紹到這了,更多相關(guān)C++ 日志 spdlog內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++模擬實(shí)現(xiàn)stack和Queue的操作示例
這篇文章主要介紹了C++模擬實(shí)現(xiàn)stack和Queue的操作示例,文中通過代碼示例給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-06-06
深入淺出理解C語(yǔ)言初識(shí)結(jié)構(gòu)體
C?數(shù)組允許定義可存儲(chǔ)相同類型數(shù)據(jù)項(xiàng)的變量,結(jié)構(gòu)是?C?編程中另一種用戶自定義的可用的數(shù)據(jù)類型,它允許你存儲(chǔ)不同類型的數(shù)據(jù)項(xiàng),本篇讓我們來了解C?的結(jié)構(gòu)體2022-02-02
VS2019和VS2022項(xiàng)目兼容性問題的解決
在VS2019中打開被VS2022打開過的項(xiàng)目時(shí)出現(xiàn)兼容性問題,可以通過修改項(xiàng)目解決方案文件(.sln)和項(xiàng)目文件(.vcxproj)中的版本號(hào)來解決2024-12-12
,下面就來介紹一下,感興趣的可以了解一下
嵌入式C實(shí)戰(zhàn)項(xiàng)目開發(fā)技巧:對(duì)一個(gè)有規(guī)律的數(shù)組表進(jìn)行位移操作的方法
今天小編就為大家分享一篇關(guān)于嵌入式C實(shí)戰(zhàn)項(xiàng)目開發(fā)技巧:對(duì)一個(gè)有規(guī)律的數(shù)組表進(jìn)行位移操作的方法,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2018-12-12
C語(yǔ)言關(guān)鍵字之a(chǎn)uto register詳解
這篇文章主要為大家介紹了C語(yǔ)言關(guān)鍵字之a(chǎn)uto register,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-01-01

