第四部分:Spdlog紀錄檔庫的核心元件分析-logger

2023-03-28 18:00:58

Spdlog是一個快速且可延伸的C++紀錄檔庫,它支援多執行緒和非同步紀錄檔記錄。在本文中,我們將分析Spdlog紀錄檔庫的核心程式碼,探究其實現原理和程式碼結構。

Spdlog的基本架構

上一篇文章介紹了spdlog的五個主要元件,其中最重要是Logger、Sink和Formatter其中,Logger負責紀錄檔的記錄和管理,Sink負責將紀錄檔輸出到不同的目標(比如控制檯、檔案、網路等),Formatter負責將紀錄檔格式化為字串。我們會在下面詳細的介紹下它們。

Logger

Logger是Spdlog紀錄檔庫的核心元件,它負責記錄和管理紀錄檔。Logger的定義如下:

class logger {
public:
    explicit logger(std::string logger_name, sinks_init_list sinks);

    template<typename T>
    void log(level::level_enum lvl, const T &msg);
    
		template<typename... Args>
    void log(source_loc loc, level::level_enum lvl, format_string_t<Args...> fmt, Args &&... args);
    
    template<typename... Args>
    void log(level::level_enum lvl, format_string_t<Args...> fmt, Args &&... args);
    
    // ...
    template<typename... Args>
    void trace(format_string_t<Args...> fmt, Args &&... args);
    
    template<typename... Args>
    void debug(format_string_t<Args...> fmt, Args &&... args);

    template<typename... Args>
    void info(format_string_t<Args...> fmt, Args &&... args);

    // ....

private:
    std::string name_;
    std::vector<sink_ptr> sinks_;
    // ...
};

Logger主要包含一個名稱和一組Sink,它還提供了log()方法用於記錄紀錄檔。當呼叫log()方法時,Logger會將紀錄檔訊息傳遞給每個Sink,並由Sink將紀錄檔輸出到目標。Logger還提供了其他一些方法,比如設定紀錄檔級別、新增和刪除Sink等。

Logger 是個紀錄檔包裝器,包含了紀錄檔名稱和一組Sink,它提供了輸出不同級別紀錄檔的方法,通過不同Sink的組合可以輸出到一個或多個不同輸出路徑(檔案,控制檯,網路等)。

紀錄檔名全域性唯一

每個 logger 都有一個名稱,並且是全域性唯一的,通過上一篇提到的 register 元件註冊到全域性的 map裡,程式碼如下, registryloggers_ 欄位通過名字記錄了所有的 logger 範例。

class registry
{
public:
    // ..... 
    // 註冊紀錄檔
	  void register_logger(std::shared_ptr<logger> new_logger);

 private:
  // ....
	std::unordered_map<std::string, std::shared_ptr<logger>> loggers_;
}

registry 提供 register_logger 介面註冊紀錄檔。這裡值得注意點是:註冊時候如果發現已經存在則會拋異常, throw_if_exists_ 會檢查是否已經存在同名紀錄檔範例,存在則通過 throw_spdlog_ex 丟擲異常。

SPDLOG_INLINE void registry::throw_if_exists_(const std::string &logger_name)
{
    if (loggers_.find(logger_name) != loggers_.end())
    {
        throw_spdlog_ex("logger with name '" + logger_name + "' already exists");
    }
}

SPDLOG_INLINE void registry::register_logger_(std::shared_ptr<logger> new_logger)
{
    auto logger_name = new_logger->name();
    throw_if_exists_(logger_name);
    loggers_[logger_name] = std::move(new_logger);
}

紀錄檔輸出控制

  • 提供不同級別紀錄檔的輸出介面

logger 類中,Spdlog 提供了不同級別紀錄檔的輸出介面,包括 trace()debug()info()warn()error()critical() 等。下面是 logger 類中提供的不同級別紀錄檔輸出介面的程式碼範例:

template<typename... Args>
void trace(format_string_t<Args...> fmt, Args &&... args);

template<typename... Args>
void debug(format_string_t<Args...> fmt, Args &&... args);

template<typename... Args>
void info(format_string_t<Args...> fmt, Args &&... args);

template<typename... Args>
void warn(format_string_t<Args...> fmt, Args &&... args);

template<typename... Args>
void error(format_string_t<Args...> fmt, Args &&... args);

template<typename... Args>
void critical(format_string_t<Args...> fmt, Args &&... args);

使用這些介面,可以根據不同的紀錄檔級別輸出不同的紀錄檔資訊,比如 logger.info("This is an info message."); 將輸出一條資訊級別為 info 的紀錄檔。

  • 紀錄檔輸出級別控制

logger 提供了 set_level 介面來設定紀錄檔級別,這個級別可以是列舉型別 level 中的任何一個,比如 spdlog::set_level(spdlog::level::trace); 將設定紀錄檔級別為 trace,這樣所有級別的紀錄檔都會被記錄下來。如果想要只記錄 info 級別及以上的紀錄檔,則可以使用 spdlog::set_level(spdlog::level::info);

如果想要在執行時動態地設定紀錄檔級別,可以使用 set_level() 方法,例如 logger->set_level(spdlog::level::trace); 將設定當前 logger 的紀錄檔級別為 trace

注意:如果想要關閉紀錄檔,則可以將紀錄檔級別設定為 off,例如 spdlog::set_level(spdlog::level::off);

enum class level
{
    trace,
    debug,
    info,
    warn,
    err,
    critical,
    off
};
  • 紀錄檔重新整理控制

logger提供了一些控制紀錄檔重新整理的方法,最重要的方法是flush()。當呼叫flush()方法時,Logger會將所有掛起的紀錄檔訊息重新整理到Sink中。Logger還提供了set_pattern()方法,用於設定紀錄檔格式化模式。這個方法可以用於修改紀錄檔的輸出格式,例如新增時間戳、執行緒ID等資訊。

void flush();
  • 紀錄檔格式設定

logger 提供了設定紀錄檔格式的方法 set_pattern 通過此方法可以設定包含的 skin的紀錄檔格式,具體的格式資訊可以參考上一篇。

// 紀錄檔格式設定
void set_pattern(std::string pattern, pattern_time_type time_type = pattern_time_type::local);

通過格式字串 pattern,會生成 formatter 範例,呼叫 skin的set_formatter 介面設定紀錄檔格式。

SPDLOG_INLINE void logger::set_pattern(std::string pattern, pattern_time_type time_type)
{
    auto new_formatter = details::make_unique<pattern_formatter>(std::move(pattern), time_type);
    set_formatter(std::move(new_formatter));
}

// set formatting for the sinks in this logger.
// each sink will get a separate instance of the formatter object.
SPDLOG_INLINE void logger::set_formatter(std::unique_ptr<formatter> f)
{
    for (auto it = sinks_.begin(); it != sinks_.end(); ++it)
    {
        if (std::next(it) == sinks_.end())
        {
            // last element - we can be move it.
            (*it)->set_formatter(std::move(f));
            break; // to prevent clang-tidy warning
        }
        else
        {
            (*it)->set_formatter(f->clone());
        }
    }
}