spdlog 源码解析
##记日志两种模式:
-
同步: 对于basic_file_sink, 日志每次调用fwrite写入到文件缓存,即使同步模式,也需要
flush_every
来定时flush,否则crash 时有丢失日志风险 -
异步:
- log写日志就是把日志入一个循环queue。
- 初始时会开启一个线程池,里面有n个线程,每个线程里做的工作就是从循环队列中取日志,写入到指定输出设备。当队列为空时,取不到数据时,会wait 10s时间,然后继续循环取数据。
- 也需要
flush_every
来定时flush 数据
sink
sink 类实现多态,方便扩展.
对于继承的类,都是模板类
template<typename Mutex>
class base_sink : public sink
{
}
Mutex 可以空实现,但是这样无法保证日志的连贯有序性,打日志的场景感觉实用性不大。但是是个很棒的设计方法。可以在其他场景运用这种设计方法。
可变模板参数
创建logger的代码
- 外面创建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 可变模板参数
- 一个例子
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.