Spdlog 是一個快速、非同步的 C++ 日誌庫,被廣泛應用於 C++ 項目中。在這篇文章中,我們將探討 Spdlog 日誌庫的實現原理。 Spdlog 的結構 Spdlog 由五個主要組件構成:Loggers、Sinks、Formatters、Async Logger 和 Registry。每個組 ...
Spdlog 是一個快速、非同步的 C++ 日誌庫,被廣泛應用於 C++ 項目中。在這篇文章中,我們將探討 Spdlog 日誌庫的實現原理。
Spdlog 的結構
Spdlog 由五個主要組件構成:Loggers、Sinks、Formatters、Async Logger 和 Registry。每個組件都扮演著不同的角色,共同協作記錄並輸出日誌消息。
- Loggers :是 Spdlog 最基本的組件,負責記錄日誌消息。在 Spdlog 中,一個 Logger 對象代表著一個日誌記錄器,應用程式可以使用 Logger 對象記錄不同級別的日誌消息。
- Sinks :決定了日誌消息的輸出位置。在 Spdlog 中,一個 Sink 對象代表著一個輸出位置,例如控制台、文件、網路等。應用程式可以將不同的日誌消息發送到不同的 Sink 中。
- Formatters :負責將日誌消息轉換為特定格式。在 Spdlog 中,一個 Formatter 對象代表著一個消息格式器,應用程式可以使用不同的 Formatter 對象將日誌消息轉換為不同的格式。
- Async Logger :是 Spdlog 的非同步記錄器,它負責將日誌消息非同步地寫入到目標 Sink 中。當應用程式調用 Logger 對象記錄一個日誌消息時,該消息會被加入到一個隊列中,然後非同步地寫入目標 Sink 中。這樣可以避免多個線程同時訪問 Sink,從而確保線程安全性。
- Registry :用於管理 Spdlog 的所有組件。在 Spdlog 中,所有的 Loggers、Sinks、Formatters 和 Async Logger 都在一個全局註冊表中註冊,Registry 用於管理這些組件。
Spdlog 記錄日誌的流程
當應用程式調用 Spdlog 記錄日誌時,Spdlog 的流程如下:
- 獲取一個 Logger 對象。
- 使用該 Logger 對象記錄一個日誌消息,該消息包括日誌級別、時間戳、線程 ID、文件名和行號等信息。
- 將日誌消息傳遞給 Formatter,將消息轉換為特定格式。
- 將格式化後的消息傳遞給 Async Logger。
- Async Logger 將消息寫入目標 Sink,完成日誌記錄。
Spdlog 的流程非常簡單,但是每個組件都扮演著重要的角色。Loggers 負責記錄日誌消息,Sinks 決定了日誌消息的輸出位置,Formatters 負責將日誌消息轉換為特定格式,Async Logger 非同步地將日誌消息寫入到目標 Sink 中,Registry 用於管理這些組件。
Spdlog 的線程安全性
spdlog 允許我們自由創建線程安全和非線程安全(單線程)的日誌,其設置在基類base_skin
中,
template<typename Mutex>
class SPDLOG_API base_sink : public sink
{
public:
void log(const details::log_msg &msg) final;
protected:
Mutex mutex_;
}
template<typename Mutex>
void SPDLOG_INLINE spdlog::sinks::base_sink<Mutex>::log(const details::log_msg &msg)
{
std::lock_guard<Mutex> lock(mutex_);
sink_it_(msg);
}
每個sink都會繼承 base_sink,通過模板參數 Mutex
傳入鎖。可以看到寫日誌函數 log
調用了 std::lock_guard
來使用鎖。
Mutex
可以自定義,需要提供下麵兩個介面:
void lock();
void unlock();
在實際使用中如果想要線程安全,可以傳入c++的 mutex(c++11開始支持),也可以自定義。如下是一個聲明線程安全例子:
using kafka_sink_mt = kafka_sink<std::mutex>;
當然spdlog 也為我們提供了單線程的 mutex:
struct null_mutex
{
void lock() const {}
void unlock() const {}
};
using kafka_sink_st = kafka_sink<spdlog::details::null_mutex>;
Spdlog 的同步和非同步模式
同步模式
在同步模式下,Spdlog 將日誌消息直接寫入目標 Sink,不使用記憶體隊列。這種模式下,應用程式在記錄日誌消息時,必須等待消息寫入目標 Sink 後才能繼續執行。同步模式可以保證日誌消息的實時性,但是可能會影響程式的性能,特別是在大量記錄日誌消息時。如果應用程式不需要實時記錄日誌消息,可以使用非同步模式來提高性能。
非同步模式
在非同步模式下,日誌消息被加入到一個記憶體隊列中,然後非同步地寫入目標 Sink。非同步模式可以提高日誌記錄的性能,尤其是在多線程環境下,因為它可以避免多個線程同時訪問 Sink,從而提高線程安全性。
在 Spdlog 中,非同步模式由 Async Logger 實現。Async Logger 在後臺運行一個線程,負責從記憶體隊列中獲取日誌消息,並將其寫入目標 Sink 中。Async Logger 可以配置多個 Sink,每個 Sink 都會有一個獨立的記憶體隊列。
Spdlog 提供了兩種記憶體隊列實現:unbounded 和 bounded。unbounded 記憶體隊列沒有大小限制,可以一直增長,直到記憶體耗盡。bounded 記憶體隊列有一個固定的大小,超過大小限制後,新的消息將被丟棄。
在使用非同步模式時,需要註意以下事項:
- 處理記憶體隊列時可能會出現記憶體分配問題和鎖競爭問題,需要謹慎設計和測試。
- 如果記憶體隊列大小有限制,需要根據應用程式的需求和硬體資源進行適當的調整。
- 在應用程式退出時,需要等待所有日誌消息寫入完成,否則可能會丟失一些日誌消息。
非同步模式可以大大提高日誌記錄的性能,但是也需要謹慎使用和測試。如果記憶體隊列大小限制不當或處理不當,可能會導致記憶體占用過高或日誌消息丟失等問題。
Spdlog 的性能
Spdlog 是一個高性能的日誌庫,它的性能優於其他許多日誌庫。Spdlog 的非同步記錄器和多線程支持使得它能夠快速地記錄大量的日誌消息。
下麵是spdlog性能:
同步模式:
[info] **************************************************************
[info] Single thread, 1,000,000 iterations
[info] **************************************************************
[info] basic_st Elapsed: 0.17 secs 5,777,626/sec
[info] rotating_st Elapsed: 0.18 secs 5,475,894/sec
[info] daily_st Elapsed: 0.20 secs 5,062,659/sec
[info] empty_logger Elapsed: 0.07 secs 14,127,300/sec
[info] **************************************************************
[info] C-string (400 bytes). Single thread, 1,000,000 iterations
[info] **************************************************************
[info] basic_st Elapsed: 0.41 secs 2,412,483/sec
[info] rotating_st Elapsed: 0.72 secs 1,389,196/sec
[info] daily_st Elapsed: 0.42 secs 2,393,298/sec
[info] null_st Elapsed: 0.04 secs 27,446,957/sec
[info] **************************************************************
[info] 10 threads, competing over the same logger object, 1,000,000 iterations
[info] **************************************************************
[info] basic_mt Elapsed: 0.60 secs 1,659,613/sec
[info] rotating_mt Elapsed: 0.62 secs 1,612,493/sec
[info] daily_mt Elapsed: 0.61 secs 1,638,305/sec
[info] null_mt Elapsed: 0.16 secs 6,272,758/sec
非同步模式:
[info] -------------------------------------------------
[info] Messages : 1,000,000
[info] Threads : 10
[info] Queue : 8,192 slots
[info] Queue memory : 8,192 x 272 = 2,176 KB
[info] -------------------------------------------------
[info]
[info] *********************************
[info] Queue Overflow Policy: block
[info] *********************************
[info] Elapsed: 1.70784 secs 585,535/sec
[info] Elapsed: 1.69805 secs 588,910/sec
[info] Elapsed: 1.7026 secs 587,337/sec
[info]
[info] *********************************
[info] Queue Overflow Policy: overrun
[info] *********************************
[info] Elapsed: 0.372816 secs 2,682,285/sec
[info] Elapsed: 0.379758 secs 2,633,255/sec
[info] Elapsed: 0.373532 secs 2,677,147/sec