第四部分:Spdlog日誌庫的核心元件分析-logger

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

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());
        }
    }
}

相關文章