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: level_t level_{level::trace}; };
|
sink
是最基础的抽象类,提供了 level_
日志等级设置。并且提供了下面四个抽象方法:
- log:用于接收
spdlog::details::log_msg
类型的日志消息,实现将消息输出到指定目标的功能。
- flush:用于将缓冲区中的日志消息输出到目标。
- set_pattern:用于设置日志格式。该方法接收一个字符串参数,包含格式说明符和文本内容。
- set_formatter:用于设置日志的格式化器。该方法接收一个
std::unique_ptr<spdlog::formatter>
类型的参数,表示要使用的格式化器的指针。
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: 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) { }
~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