51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

spdlog日志库的核心组件分析-sink

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 所使用的 formattersink_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的实现,是个模板函数,会调用到 Mutexlockunlock 方法。

|---------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 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 只要实现 lockunlock 方法就可以了。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_ 两个接口即可。同时,也可以自定义锁,只需要实现 lockunlock 两个方法,并在继承 base_sink 时将锁作为模板参数传入即可。

参考链接:https://zhuanlan.zhihu.com/p/617954521


赞(2)
未经允许不得转载:工具盒子 » spdlog日志库的核心组件分析-sink