Sink
负责将日志输出到不同的目标。Spdlog
提供了多种Sink
,包括stdout_sink
(输出到控制台)、basic_file_sink
(输出到文件)、syslog_sink
(输出到syslog
)等。
Sink
组件的实现采用继承抽象类的方法,提供了灵活的扩展能力。
最基础的sink
基类定义如下:
|---------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class sink { public: virtual ~sink() = default; virtual void log(const details::log_msg &msg) = 0; virtual void flush() = 0; virtual void set_pattern(const std::string &pattern) = 0; virtual void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) = 0; void set_level(level::level_enum log_level); level::level_enum level() const; bool should_log(level::level_enum msg_level) const; protected: // sink log level - default is all level_t level_{level::trace}; };
|
sink
是最基础的抽象类,提供了 level_
日志等级设置。并且提供了下面四个抽象方法:
- log:用于接收
spdlog::details::log_msg
类型的日志消息,实现将消息输出到指定目标的功能。 - flush:用于将缓冲区中的日志消息输出到目标。
- set_pattern:用于设置日志格式。该方法接收一个字符串参数,包含格式说明符和文本内容。
- set_formatter:用于设置日志的格式化器。该方法接收一个
std::unique_ptr<spdlog::formatter>
类型的参数,表示要使用的格式化器的指针。
base_sink {#base-sink}
|------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class base_sink : public sink { public: base_sink(); explicit base_sink(std::unique_ptr<spdlog::formatter> formatter); ~base_sink() override = default; base_sink(const base_sink &) = delete; base_sink(base_sink &&) = delete; base_sink &operator=(const base_sink &) = delete; base_sink &operator=(base_sink &&) = delete; void log(const details::log_msg &msg) final; void flush() final; void set_pattern(const std::string &pattern) final; void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) final; protected: // sink formatter std::unique_ptr<spdlog::formatter> formatter_; Mutex mutex_; virtual void sink_it_(const details::log_msg &msg) = 0; virtual void flush_() = 0; virtual void set_pattern_(const std::string &pattern); virtual void set_formatter_(std::unique_ptr<spdlog::formatter> sink_formatter); };
|
base_sink
继承自 sink
,在次基础上提供了锁和格式化器,其中 mutex_
用于保证多线程环境下的数据安全,formatter_
表示该 sink
所使用的 formatter
。sink_it_()
方法用于实现将日志输出到目标的具体逻辑,flush_()
用于实现将缓冲区中的日志消息输出到目标,set_pattern_()
用于设置日志格式,set_formatter_()
用于设置日志的格式化器。由于 base_sink
是一个抽象类,所以这些方法都是纯虚函数,需要在子类中实现。
自定义锁 {#自定义锁}
mutex_
的类型通过模板传递,也就是说我们可以自定义锁。那么自定义锁需要满足哪些条件呢?在 log
写日志函数里可以找到调用:
|---------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6
| template<typename Mutex> void SPDLOG_INLINE spdlog::sinks::base_sink<Mutex>::log(const details::log_msg &msg) { std::lock_guard<Mutex> lock(mutex_); sink_it_(msg); }
|
std::lock_guard
使用了自定义的锁,查看 lock_guard
的实现,是个模板函数,会调用到 Mutex
的 lock
和 unlock
方法。
|---------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| template<typename _Mutex> class lock_guard { public: typedef _Mutex mutex_type; explicit lock_guard(mutex_type& __m) : _M_device(__m) { _M_device.lock(); } lock_guard(mutex_type& __m, adopt_lock_t) noexcept : _M_device(__m) { } // calling thread owns mutex ~lock_guard() { _M_device.unlock(); } lock_guard(const lock_guard&) = delete; lock_guard& operator=(const lock_guard&) = delete; private: mutex_type& _M_device; };
|
也就是说我们自定义的 Mutex
只要实现 lock
与 unlock
方法就可以了。spdlog
也给我们提供了默认的无锁Mutex
,来提供单线程不加锁的日志实现。
|-------------------|----------------------------------------------------------------------------|
| 1 2 3 4 5
| struct null_mutex { void lock() const {} void unlock() const {} };
|
实现自定义日志输出 {#实现自定义日志输出}
继承 base_sink
后我们可以定义自己的输出实现,只需要实现下面两个接口:
|-------------|--------------------------------------------------------------------------------------------|
| 1 2
| virtual void sink_it_(const details::log_msg &msg) = 0; virtual void flush_() = 0;
|
-
sink_it_:用于写日志
-
flush_:用于刷新缓冲区到输出
目前 spdlog 已经提供了多种 Sink 的实现,包括: -
stdout_sink(输出到控制台)
-
basic_file_sink(输出到文件)
-
daily_file_sink(每天输出到不同的日志文件)
-
rotating_file_sink(按文件大小或时间自动切分日志文件)
-
null_sink(无输出)
-
syslog_sink(输出到系统日志)
-
udp_sink(输出到udp)
-
tcp_sink(输出到tcp)
-
kafka_sink(输出到kafka)
-
...
如果需要自定义输出实现,只需要继承base_sink
并实现sink_it_
和flush_
两个接口即可。同时,也可以自定义锁,只需要实现lock
和unlock
两个方法,并在继承base_sink
时将锁作为模板参数传入即可。
参考链接:https://zhuanlan.zhihu.com/p/617954521