Spdlog是一個快速且可延伸的C++紀錄檔庫,它支援多執行緒和非同步紀錄檔記錄。在本文中,我們將分析Spdlog紀錄檔庫的核心程式碼,探究其實現原理和程式碼結構。
上一篇文章介紹了spdlog的五個主要元件,其中最重要是Logger、Sink和Formatter其中,Logger負責紀錄檔的記錄和管理,Sink負責將紀錄檔輸出到不同的目標(比如控制檯、檔案、網路等),Formatter負責將紀錄檔格式化為字串。我們會在下面詳細的介紹下它們。
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());
}
}
}