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

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