第三部分:Spdlog 日誌庫的實現原理

聆湖聽風發表於2023-03-27

! https://zhuanlan.zhihu.com/p/617432495

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 用於管理這些元件。

Image

Spdlog 記錄日誌的流程

當應用程式呼叫 Spdlog 記錄日誌時,Spdlog 的流程如下:

  1. 獲取一個 Logger 物件。
  2. 使用該 Logger 物件記錄一個日誌訊息,該訊息包括日誌級別、時間戳、執行緒 ID、檔名和行號等資訊。
  3. 將日誌訊息傳遞給 Formatter,將訊息轉換為特定格式。
  4. 將格式化後的訊息傳遞給 Async Logger。
  5. 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

相關文章