spdlog 代碼分析

spdlog 源碼解析

##記日誌兩種模式:

  1. 同步: 對於basic_file_sink, 日誌每次調用fwrite寫入到文件緩存,即使同步模式,也需要flush_every來定時flush,否則crash 時有丟失日誌風險

  2. 異步:

  • log寫日誌就是把日誌入一個循環queue。
  • 初始時會開啓一個線程池,裏面有n個線程,每個線程裏做的工作就是從循環隊列中取日誌,寫入到指定輸出設備。當隊列爲空時,取不到數據時,會wait 10s時間,然後繼續循環取數據。
  • 也需要flush_every來定時flush 數據

sink

sink 類實現多態,方便擴展.
對於繼承的類,都是模板類

template<typename Mutex>
class base_sink : public sink
{
}

Mutex 可以空實現,但是這樣無法保證日誌的連貫有序性,打日誌的場景感覺實用性不大。但是是個很棒的設計方法。可以在其他場景運用這種設計方法。

可變模板參數

創建logger的代碼

  1. 外面創建logger的api,是個模板函數
auto my_logger = spdlog::basic_logger_mt<spdlog::default_factory>("file_logger1", "logs/basic-log.txt");
auto my_logger = spdlog::basic_logger_mt("file_logger1", "logs/basic-log.txt");//使用了默認的模板default_factory

創建logger的函數模板, 模板有默認值default_factory

template<typename Factory = default_factory>
inline std::shared_ptr<logger> basic_logger_mt(const std::string &logger_name, const filename_t &filename, bool truncate = false)
{
    return Factory::template create<sinks::basic_file_sink_mt>(logger_name, filename, truncate);
}

其實可以寫成
return Factory::create<sinks::basic_file_sink_mt>(logger_name, filename, truncate);
寫成 Factory::template 這種形式還沒太明白這是什麼語法

第2步調用的create方法,此方法爲可變模板參數。

using default_factory = synchronous_factory;
struct synchronous_factory
{
    template<typename Sink, typename... SinkArgs>
    static std::shared_ptr<spdlog::logger> create(std::string logger_name, SinkArgs &&... args)
    {
        auto sink = std::make_shared<Sink>(std::forward<SinkArgs>(args)...);
        auto new_logger = std::make_shared<logger>(std::move(logger_name), std::move(sink));
        details::registry::instance().initialize_logger(new_logger);
        return new_logger;
    }
};

其中步驟2中 create<sinks::basic_file_sink_mt>(logger_name, filename, truncate) 中 sinks::basic_file_sink_mt 對應詞函數模板中的模板參數Sink,入參logger_name對應此函數中的入參 std::string logger_name; 入參filename, truncate 對應此函數中的SinkArgs 可變模板參數

  1. 一個例子
void print()
{
    std::cout << "empty" << std::endl;
}

//展開函數
template <class T, class ...Args>
void print(T head, Args... rest)
{
    std::cout << "parameter " << head << std::endl;
    print(rest...);
}

print(1, 2.2, 3, 4); 輸出 1 2.2 3 4 empty
print(1, 2.2, 3, 4); 同上,參數T自動推導

如果把print中調用修改爲print(rest…) 結果是什麼樣呢?這樣就類型不會自動推導,所有入參 類型爲int,輸出2.2就會變爲2

所以可變模板參數需要格外注意參數類型。

##periodic_worker

periodic_worker(const std::function<void()> &callback_fun, std::chrono::seconds interval)
   {
       active_ = (interval > std::chrono::seconds::zero());
       if (!active_)
       {
           return;
       }

       worker_thread_ = std::thread([this, callback_fun, interval]() {
           for (;;)
           {
               std::unique_lock<std::mutex> lock(this->mutex_);
               if (this->cv_.wait_for(lock, interval, [this] { return !this->active_; }))
               {
                   return; // active_ == false, so exit this thread
               }
               callback_fun();
           }
       });
   }

   ~periodic_worker()
   {
       if (worker_thread_.joinable())
       {
           {
               std::lock_guard<std::mutex> lock(mutex_);
               active_ = false;
           }
           cv_.notify_one();
           worker_thread_.join();
       }
   }

通過條件量實現timmer. 析構時重置標記位active_,然後notify.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章