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裡,程式碼如下, registry
的 loggers_
欄位透過名字記錄了所有的 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());
}
}
}