spdlog源码阅读(四):Sink系列类的实现

Sink是一系列类,功能为负责日志输出的具体实现,其通过继承的方式实现拓展。基类Sink的类定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class SPDLOG_API 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类的实现

Sink类是一个纯虚类,无法被实例化。在类的声明前包含了SPDLOG_API宏,这是用于动态库符号导出的宏。

在win平台中,生成动态库时需要使用__declspec(dllexport)显式声明使得这些符号可以被其他程序使用,这是必须选项。在其他程序使用动态库时,使用__declspec(dllimport)声明导入了该符号,虽然这不是必须选项,但是有助于理解和编译器优化。

而在其他Linux平台中,编译动态库时默认导出所有符号,可以使用-fvisibility=hidden编译选项使默认符号不导。为了更详细的控制符号的导出,可以使用__attribute__((visibility("default")))来设置使符号导出,使用__attribute__ ((visibility("hidden")))设置使符号不导出。

它一共实现了4个纯虚函数,分别是:

  • log:接收传递的日志信息,并输出到具体位置,子类通过重载这个函数来实现各种日志输出功能。
  • flush:日志刷新函数,可以调用该函数实现日志的刷新。
  • set_pattern:设置日志消息格式
  • set_formatter:设置日志消息格式化器

成员变量只有level_,它是std::atomic<int>类型,可以以比较低的开销实现线程安全性。

base_sink类的实现

Sink类负责定义接口和基本的读写方法,而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
template <typename Mutex>
class SPDLOG_API 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 override;
void flush() final override;
void set_pattern(const std::string &pattern) final override;
void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) final override;

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对象的复制和移动,以确保对象具有独占的所有权或避免资源管理问题。

base_sink类中对重载的函数使用了finaloverride关键字,用于显式声明函数是重载以及不能被再次重载。

base_sink 是一个模板类,模板参数为 Mutex,通过传递不同类型的 Mutex,可以实现不同的线程控制功能。传递 std::mutex 时,base_sink 具备线程安全性。如果需要更高的性能,可以传递 spdlog 定义的 null_mutexnull_mutex 实现了 lockunlock 方法,但它们不执行任何实际操作,因此不会引入锁机制的开销,这个方法通过一套代码实现了线程安全和非线程安全的代码。

1
2
3
4
struct null_mutex {
void lock() const {}
void unlock() const {}
};

如果看base_sink 会发现该类只是实现了一个简单的封装,使用互斥锁来保证线程安全性,具体的实现由新定义的sink_it_flush_set_pattern_set_formatter_方法实现。

basic_file_sink类的实现

basic_file_sink才是sink的最完整的实现,其关键成员属性details::file_helper file_helper_ 负责文件的打开,写入和刷新

1
2
3
4
5
6
7
8
9
10
11
template <typename Mutex>
SPDLOG_INLINE void basic_file_sink<Mutex>::sink_it_(const details::log_msg &msg) {
memory_buf_t formatted;
base_sink<Mutex>::formatter_->format(msg, formatted);
file_helper_.write(formatted);
}

template <typename Mutex>
SPDLOG_INLINE void basic_file_sink<Mutex>::flush_() {
file_helper_.flush();
}

memory_buf_t是spdlog管理字符缓存的自定义类型,当使用std库时,其为std::string,如果使用fmt库,则使用的是fmt::basic_memory_buffer<char, 250>fmt::basic_memory_buffer 通过预分配栈缓冲区和减少动态分配,能够在高效格式化操作中表现更佳,可以根据项目使用的日志长度,调整预分配的大小。

除了实现之外,还封装了使用接口,使用basic_file_sink_mtbasic_file_sink_st来区分多线程版和单线程版。

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
using basic_file_sink_mt = basic_file_sink<std::mutex>;
using basic_file_sink_st = basic_file_sink<details::null_mutex>;

} // namespace sinks

//
// factory functions
//
template <typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> basic_logger_mt(const std::string &logger_name,
const filename_t &filename,
bool truncate = false,
const file_event_handlers &event_handlers = {}) {
return Factory::template create<sinks::basic_file_sink_mt>(logger_name, filename, truncate,
event_handlers);
}

template <typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> basic_logger_st(const std::string &logger_name,
const filename_t &filename,
bool truncate = false,
const file_event_handlers &event_handlers = {}) {
return Factory::template create<sinks::basic_file_sink_st>(logger_name, filename, truncate,
event_handlers);
}

通过工厂方法方便创建logger实例,而不需要单独创建logger对象和sink对象。

作者

echo

发布于

2024-10-14

更新于

2024-10-14

许可协议

评论