概述
在 spdlog 日志库中,sinks 并不是一个单独的类,而是一系列类的集合,这些类以基类-派生类的形式组织,每一个 sink 派生类代表了一种输出日志消息的方式。输出目标可以是普通文件、标准输出 (stdout)、标准错误输出 (stderr)、系统日志 (syslog) 等等。其文件位于include/spdlog/sinks中
sink类
类sink是所有sinks系列类的基类,也是一个接口类,提供接口和共有数据,但不负责实例化。
/// 一个sink对象对应一个输出目标, 即文件, 负责将log消息写到指定目标上,
/// 可能是普通文件, syslog, 终端, 或者socket(tcp/udp), etc.
class SPDLOG_API sink
{
public:
virtual ~sink() = default;
// 接收log消息
virtual void log(const details::log_msg &msg) = 0;
// 冲刷log消息
virtual void flush() = 0;
// 设置模式
virtual void set_pattern(const std::string &pattern) = 0;
// 设置formatter(格式)
virtual void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) = 0;
// 设置log等级阈值
void set_level(level::level_enum log_level);
// 获取log等级阈值
level::level_enum level() const;
// 判断是否应当写log消息,msg_level是log消息的log等级
bool should_log(level::level_enum msg_level) const;
protected:
// sink log level - default is all
level_t level_{level::trace};
};
- log() 接收用户log消息并写入目标文件,通常是由logger类传入
- flush() 冲刷用户log消息,将缓存中数据尽快写入目标文件
- set_pattern() 由现有的模式标志,定制输出的log消息格式
- set_formatter() 实现自定义formatter,定制输出的log消息格式
sink有一个比较特殊的变量level_,是指sink的日志等级,相当于一个log等级阈值。只有当log消息本身log等级 >= level_时,才写log到目标。should_log函数中会判断是否写:
SPDLOG_INLINE bool spdlog::sinks::sink::should_log(spdlog::level::level_enum msg_level) const {
return msg_level >= level_.load(std::memory_order_relaxed);
}
sink提供了level_的get、set方法。注意这里并没有直接对leve_使用"="进行赋值,而是使用了适用于内存布局的方法(松散的内存顺序)。
SPDLOG_INLINE void spdlog::sinks::sink::set_level(level::level_enum log_level) {
level_.store(log_level, std::memory_order_relaxed);
}
SPDLOG_INLINE spdlog::level::level_enum spdlog::sinks::sink::level() const {
return static_cast<spdlog::level::level_enum>(level_.load(std::memory_order_relaxed));
}
sink子类
base_sink类模板
这是sink最核心的一个子类,是一个抽象类类模板,无法实例化,为其他更多的sink提供公共的标准接口。
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; // 接收用户log消息
void flush() final; // 冲刷用户log消息(到目标文件)
void set_pattern(const std::string &pattern) final; // 用模式串设置模式, 使用默认的formatter=pattern_formatter
void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) final; // 设置formatter指定格式, 支持自定义formatter
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基础上做了一些额外工作,主要是:
- 添加接受formatter为参数的构造器;
- 删除拷贝构造、移动构造函数;
- 删除拷贝赋值、移动赋值运算符;
- 将方法log、flush、set_pattern、set_formatter声明为final,禁止派生类重写,但又增添了virtual版本的protected方法sink_it_、flush_、set_pattern_、set_formatter_,这实际上是模板方法(设计模式)的应用;
- 提供默认的formatter(即pattern_formatter),或自定义的formatter支持;
- 以模板参数Mutex为锁类型,便于用同一套代码实现有锁、无锁两套方案;
例如,要创建一个有锁的文件 sink,可以使用 std::mutex 作为模板参数:
#include <spdlog/sinks/basic_file_sink.h>
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("logs.txt", true);
// basic_file_sink_mt 是 basic_file_sink<std::mutex> 的类型定义
要创建一个无锁的文件 sink,可以使用 null_mutex 作为模板参数:
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/details/null_mutex.h>
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink<spdlog::details::null_mutex>>("logs.txt", true);
这个spdlog::details::null_mutex是结构体,实现了lock和unlock,不过是空函数。
struct null_mutex {
void lock() const {}
void unlock() const {}
};
//在std::lock_guard<Mutex> lock(mutex_)中会调用这两个函数
base_sink的public接口是线程安全的,只在public接口加锁,并未在protected方法加锁。例如,base_sink::log()接收log消息:
template<typename Mutex>
void SPDLOG_INLINE spdlog::sinks::base_sink<Mutex>::log(const details::log_msg &msg)
{
std::lock_guard<Mutex> lock(mutex_); // 获得锁,确保base_sink<Mutex>数据成员的线程安全
sink_it_(msg); // sink_it_是纯虚函数,实际工作转发给sink_it_
}
log()把工作转发给了virtual函数sink_it_,实际调用的是子类的实现。例如,其中一个子类basic_file_sink::sink_it_将msg进行格式化(format)后转换为二进制数据,然后通过工具类file_helper的write()写入目标文件,其实现如下:
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); // 将格式化后的二进制数据写入目标文件
}
除了构造器,有数据访问的public接口都加锁了,而非public接口并未加锁。
null_sink类模板
用户想用logger对象,就必须提供sink对象。但是如果用户不想做任何写文件操作,例如测试代码框架是否能跑通,此时可以使用null_sink,是一个空类,所有接口皆为空。
template <typename Mutex>
class null_sink : public base_sink<Mutex> {
protected:
void sink_it_(const details::log_msg &) override {}
void flush_() override {}
};
using null_sink_mt = null_sink<details::null_mutex>;
using null_sink_st = null_sink<details::null_mutex>;
有了具体的null_sink类型(null_sink_mt/null_sink_st),可以用工厂方法装配出logger对象。
template <typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> null_logger_mt(const std::string &logger_name) {
auto null_logger = Factory::template create<sinks::null_sink_mt>(logger_name);
null_logger->set_level(level::off);
return null_logger;
}
template <typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> null_logger_st(const std::string &logger_name) {
auto null_logger = Factory::template create<sinks::null_sink_st>(logger_name);
null_logger->set_level(level::off);
return null_logger;
}
basic_file_sink类模板
basic_file_sink是basic_sink的派生类(类模板),提供文件操作,写log消息到指定文件的基本操作。如果只是想拥有简单的写log消息到文件的功能,那么可使用该sink子类。
basic_file_sink会根据构造器提供的文件名来创建一个log文件,文件支持截断功能。文件截断指的是在文件写入操作时,当文件已经存在时是否清空其内容,从文件头开始写入新的数据。
template<typename Mutex>
class basic_file_sink final : public base_sink<Mutex>
{
public:
explicit basic_file_sink(const filename_t &filename, bool truncate = false, const file_event_handlers &event_handlers = {});
const filename_t &filename() const;
protected:
// 实现了2个base_sink声明的pure virtual函数
void sink_it_(const details::log_msg &msg) override;
void flush_() override;
private:
details::file_helper file_helper_; // 文件操作帮助类, 是一个工具类
};
私有变量file_helper是文件工具类,封装了一些基本文件操作,专门用于日志文件操作。
在构造函数中:filename 类型通过别名filename_t进行包装,本质上一个字符串(std::string),因为在Windows可能需要支持宽字符。
truncate 指定是否使用文件截断功能,在打开文件时决定,通常以write + append或truncate方式打开。
event_handlers 通过一个结构体file_event_handlers包装了文件操作前后的事件,用户可以通过这种回调函数机制,指定在对应文件事件发生时要进行的动作。支持4类文件事件:打开文件前(before_open)、打开文件后(after_open)、关闭文件前(before_close)、关闭文件后(after_close)。file_event_handlers定义:
struct file_event_handlers
{
file_event_handlers()
: before_open(nullptr)
, after_open(nullptr)
, before_close(nullptr)
, after_close(nullptr)
{}
std::function<void(const filename_t &filename)> before_open;
std::function<void(const filename_t &filename, std::FILE *file_stream)> after_open;
std::function<void(const filename_t &filename, std::FILE *file_stream)> before_close;
std::function<void(const filename_t &filename)> after_close;
};
basic_file_sink实现了2个基类base_sink声明的纯虚函数:sink_it_,flush_。
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();
}
本质就是利用基类的formatter_,对log消息msg进行格式化(format),转换为二进制数据存放到memory_buf_t缓存中,然后通过工具函数file_helper_.write写到指定文件中。
flush_则是直接调用工具函数file_helper_.flush冲刷缓存到文件。
daily_file_sink类模板
daily_file_sink类可以按照一定的时间间隔将日志写入到不同的文件中,通常用于按日期分割日志文件。例如,下面调用spdlog::daily_logger_mt的例子,就能每天都创建一个daily_logger对应的log文件:
#include "spdlog/sinks/daily_file_sink.h"
...
// 每天的14:55, 在logs目录下, 创建新的日志文件, 如"daily_logger_2022-11-03"
auto daily_logger = spdlog::daily_logger_mt("daily_logger", "logs/daily", 14, 55);
daily_logger_mt是提供给用户创建logger的接口,实际调用了同步工厂方法synchronous_factory::create,创建logger对象:
template<typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> daily_logger_mt(const std::string &logger_name, const filename_t &filename, int hour = 0, int minute = 0,
bool truncate = false, uint16_t max_files = 0, const file_event_handlers &event_handlers = {})
{
// daily_file_sink_mt为工厂方法指定要装配的sink类型, 后面的函数参数用于构造sink对象
// 工厂方法会自动将新建的sin对象装配给新建的logger对象, 并用shared_ptr包裹返回给调用者
return Factory::template create<sinks::daily_file_sink_mt>(logger_name, filename, hour, minute, truncate, max_files, event_handlers);
}
dist_sink类模板
dist_sink基础自base_sink,是一个sink复用器,包含一组sinks,当log调用时,可分发给所有sink。
template<typename Mutex>
class dist_sink : public base_sink<Mutex>
{
public:
dist_sink() = default;
explicit dist_sink(std::vector<std::shared_ptr<sink>> sinks)
: sinks_(sinks)
{}
// 因为对应类类底层文件资源, 因此禁止拷贝
dist_sink(const dist_sink &) = delete;
dist_sink &operator=(const dist_sink &) = delete;
...
protected:
...
std::vector<std::shared_ptr<sink>> sinks_;
};
// 便捷类型
using dist_sink_mt = dist_sink<std::mutex>; // 线程安全版本
using dist_sink_st = dist_sink<details::null_mutex>; // 非线程安全版本
sink_it_ 将log消息写到目标文件。dist_sink的实现则是将log消息转交给每个sink对象来处理。
flush_ 将log消息从缓存冲刷到目标文件。dist_sink的实现也是交给每个sink对象来处理。
protected:
void sink_it_(const details::log_msg &msg) override{
for (auto &sink : sinks_){
if (sink->should_log(msg.level)){
sink->log(msg);
}
}
}
void flush_() override{
for (auto &sink : sinks_){
sink->flush();
}
}
对sink数组进行增删改查,属于public接口,需要加锁以确保线程安全。
public:
// 增
void add_sink(std::shared_ptr<sink> sink){
std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_);
sinks_.push_back(sink);
}
// 删
void remove_sink(std::shared_ptr<sink> sink){
std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_);
sinks_.erase(std::remove(sinks_.begin(), sinks_.end(), sink), sinks_.end());
}
// 改
void set_sinks(std::vector<std::shared_ptr<sink>> sinks){
std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_);
sinks_ = std::move(sinks);
}
// 查
std::vector<std::shared_ptr<sink>> &sinks(){
return sinks_;
}
dup_filter_sink 类模板
dup_filter_sink 用于过滤一定时间内相同的log消息,只会写一条,不会都写到log文件。
例如,下面这段代码利用dup_filter_sink过滤相同的log消息。
template<typename Mutex>
class dup_filter_sink : public dist_sink<Mutex>
{
public:
template<class Rep, class Period>
explicit dup_filter_sink(std::chrono::duration<Rep, Period> max_skip_duration)
: max_skip_duration_{max_skip_duration}
{}
protected:
std::chrono::microseconds max_skip_duration_; // 过滤时间,单位:微秒
log_clock::time_point last_msg_time_; // 上一次log消息时间点
std::string last_msg_payload_; // log消息载荷,即用户写的文本内容
size_t skip_counter_ = 0; // 过滤次数
void sink_it_(const details::log_msg &msg) override; // 父类dist_sink定义的virtual函数
...
};
using dup_filter_sink_mt = dup_filter_sink<std::mutex>;
using dup_filter_sink_st = dup_filter_sink<details::null_mutex>;
sink_it_ 是向目标文件写log消息。dup_filter_sink的做法是,先判断与规定时间内的上一次log消息是否相同,如果相同就过滤掉;如果就先写之前的过滤信息,然后。
过滤重复log消息,并不是悄无声息的,而是会写一个"Skipped n duplicate messages…"的提示信息。
protected:
void sink_it_(const details::log_msg &msg) override
{
bool filtered = filter_(msg); // false表示应该过滤掉, true表示不应该
if (!filtered)
{
skip_counter_ += 1;
return;
}
// 过滤了重复log消息, 但应产生对应的过滤信息
// log the "skipped.." message
if (skip_counter_ > 0)
{
char buf[64];
auto msg_size = ::snprintf(buf, sizeof(buf), "Skipped %u duplicate messages..", static_cast<unsigned>(skip_counter_));
if (msg_size > 0 && static_cast<size_t>(msg_size) < sizeof(buf))
{
// 调用父类sink_it_ 将log消息写入sink对象对应的目标文件, 因为是virtual函数, 所以需要显式调用
details::log_msg skipped_msg{msg.logger_name, level::info, string_view_t{buf, static_cast<size_t>(msg_size)}};
dist_sink<Mutex>::sink_it_(skipped_msg);
}
}
// 通过父类sink_it_ 将log消息写入sink对象
// log current message
dist_sink<Mutex>::sink_it_(msg);
// 更新上一次消息状态
last_msg_time_ = msg.time;
skip_counter_ = 0;
last_msg_payload_.assign(msg.payload.data(), msg.payload.data() + msg.payload.size());
}
ringbuffer_sink类模板
通常,sink的目标是一个文件,而ringbuffer_sink的目标是一个环形缓冲区,即内存。如果想把log消息写到内存中缓存起来,那么可以使用ringbuffer_sink。
template<typename Mutex>
class ringbuffer_sink final : public base_sink<Mutex>
{
public:
// 构造者指定环形缓冲区大小
explicit ringbuffer_sink(size_t n_items)
: q_{n_items}
{}
std::vector<details::log_msg_buffer> last_raw(size_t lim = 0);
std::vector<std::string> last_formatted(size_t lim = 0);
...
private:
details::circular_q<details::log_msg_buffer> q_; // sink的目标, 即一个环形缓冲区
};
using ringbuffer_sink_mt = ringbuffer_sink<std::mutex>;
using ringbuffer_sink_st = ringbuffer_sink<details::null_mutex>;
sink_it_ 实现是简单的将log消息插入到环形缓冲区末尾,flush_ 则实现为一个空函数,因为没有内容需要写到文件。
protected:
void sink_it_(const details::log_msg &msg) override
{
q_.push_back(details::log_msg_buffer{msg}); // 调用的是vector<>::push_back(log_msg_buffer &&)版本
}
void flush_() override {}
syslog_sink类模板
其他sink将log消息写到目标文件或内存,而syslog_sink则将log消息交给一个系统服务进程syslog(POSIX,Windows不支持),进而写到系统日志文件(由syslog完成)。syslog_sink采用RAII方式管理syslog资源,构造即调用openlog打开syslog,析构即调用closelog关闭syslog。
template<typename Mutex>
class syslog_sink : public base_sink<Mutex>
{
public:
syslog_sink(std::string ident, int syslog_option, int syslog_facility, bool enable_formatting)
: enable_formatting_{enable_formatting}
, syslog_levels_{{/* spdlog::level::trace */ LOG_DEBUG,
/* spdlog::level::debug */ LOG_DEBUG,
/* spdlog::level::info */ LOG_INFO,
/* spdlog::level::warn */ LOG_WARNING,
/* spdlog::level::err */ LOG_ERR,
/* spdlog::level::critical */ LOG_CRIT,
/* spdlog::level::off */ LOG_INFO}}
, ident_{std::move(ident)}
{
// set ident to be program name if empty
::openlog(ident_.empty() ? nullptr : ident_.c_str(), syslog_option, syslog_facility);
}
~syslog_sink() override
{
::closelog();
}
// 因为对应了底层系统资源, 因此禁用拷贝
syslog_sink(const syslog_sink &) = delete;
syslog_sink &operator=(const syslog_sink &) = delete;
protected:
void sink_it_(const details::log_msg &msg) override;
void flush_() override;
bool enable_formatting_ = false;
private:
using levels_array = std::array<int, 7>;
levels_array syslog_levels_; // syslog日志等级数组
const std::string ident_;
int syslog_prio_from_level(const details::log_msg &msg) const;
};
// 便捷类型
using syslog_sink_mt = syslog_sink<std::mutex>; // 线程安全版本
using syslog_sink_st = syslog_sink<details::null_mutex>; // 非现线程安全版本
sink_it_ 实现为对log消息格式化(根据需要),然后将内容通过syslog()接口转交给syslog服务。
flush_ 实现为空,因为不涉及本进程下的文件操作。
protected:
void sink_it_(const details::log_msg &msg) override
{
string_view_t payload;
memory_buf_t formatted;
if (enable_formatting_)
{
base_sink<Mutex>::formatter_->format(msg, formatted);
payload = string_view_t(formatted.data(), formatted.size()); // 格式化log消息
}
else
{
payload = msg.payload; // 直接赋值为原始的用户消息(而非格式化的log消息)
}
size_t length = payload.size();
// limit to max int
if (length > static_cast<size_t>(std::numeric_limits<int>::max()))
{
length = static_cast<size_t>(std::numeric_limits<int>::max());
}
// 将log消息转交给syslog,注意需要将日志等级进行转换
::syslog(syslog_prio_from_level(msg), "%.*s", static_cast<int>(length), payload.data());
}
void flush_() override {}
用户写log消息时,使用的是spdlog日志等级,而syslog需要的是自身的日志等级,因此需要转换。syslog_prio_from_level负责将日志等级从spdlog日志等级转换为syslog日志等级。两种日志等级对应关系,在syslog_levels_构造的注释中,已经详细说明:
int syslog_prio_from_level(const details::log_msg &msg) const
{
return syslog_levels_.at(static_cast<levels_array::size_type>(msg.level));
}
stdout_sink_base类模板
stdout_sink_base 是一个用于向控制台(标准输出或标准错误)输出日志消息的派生类模板。它继承自 sink 类,而不是 base_sink 类,并且专门处理向控制台输出日志的任务。
template<typename ConsoleMutex>
class stdout_sink_base : public sink
{
public:
using mutex_t = typename ConsoleMutex::mutex_t;
explicit stdout_sink_base(FILE *file);
~stdout_sink_base() override = default;
// 禁用拷贝和移动操作
stdout_sink_base(const stdout_sink_base &other) = delete;
stdout_sink_base(stdout_sink_base &&other) = delete;
stdout_sink_base &operator=(const stdout_sink_base &other) = delete;
stdout_sink_base &operator=(stdout_sink_base &&other) = delete;
// 重写 sink 类的纯虚函数
void log(const details::log_msg &msg) override;
void flush() override;
void set_pattern(const std::string &pattern) override;
void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) override;
protected:
mutex_t &mutex_; // 引用类型的互斥锁
FILE *file_;
std::unique_ptr<spdlog::formatter> formatter_;
#ifdef _WIN32
HANDLE handle_;
#endif
};
对于非 Windows 平台,需要传入 stdout 或 stderr 作为控制台文件指针,而在 Windows 平台,需要获取文件句柄进行文件 IO 操作。
template<typename ConsoleMutex>
SPDLOG_INLINE stdout_sink_base<ConsoleMutex>::stdout_sink_base(FILE *file)
: mutex_(ConsoleMutex::mutex()) // 从模板参数 ConsoleMutex 中获取 mutex 对象,绑定到引用 mutex_
, file_(file)
, formatter_(details::make_unique<spdlog::pattern_formatter>()) // 默认的 formatter
{
#ifdef _WIN32 // Windows 平台
// 从 FILE* 对象获取 Windows 句柄
handle_ = reinterpret_cast<HANDLE>(::_get_osfhandle(::_fileno(file_))); // 获取文件对应句柄
// 判断文件指针和句柄
if (handle_ == INVALID_HANDLE_VALUE && file != stdout && file != stderr)
{
throw_spdlog_ex("spdlog::stdout_sink_base: _get_osfhandle() 失败", errno);
}
#endif
}
stdout_sink_base 并非 base_sink 的派生类,因此无需实现 sink_it_ 和 flush_,但需要实现 sink 类的纯虚函数:log、flush、set_pattern 和 set_formatter。
template<typename ConsoleMutex>
SPDLOG_INLINE void stdout_sink_base<ConsoleMutex>::log(const details::log_msg &msg)
{
#ifdef _WIN32 // Windows 平台
if (handle_ == INVALID_HANDLE_VALUE)
{
return;
}
std::lock_guard<mutex_t> lock(mutex_); // 获取锁
memory_buf_t formatted;
formatter_->format(msg, formatted); // 格式化日志消息
::fflush(file_); // 刷新文件缓冲区
auto size = static_cast<DWORD>(formatted.size());
DWORD bytes_written = 0;
bool ok = ::WriteFile(handle_, formatted.data(), size, &bytes_written, nullptr) != 0;
if (!ok)
{
throw_spdlog_ex("stdout_sink_base: WriteFile() 失败. GetLastError(): " + std::to_string(::GetLastError()));
}
#else // 非 Windows 平台
std::lock_guard<mutex_t> lock(mutex_);
memory_buf_t formatted;
formatter_->format(msg, formatted);
::fwrite(formatted.data(), sizeof(char), formatted.size(), file_);
::fflush(file_); // 刷新每行到终端
#endif
}
flush 函数直接调用系统的 fflush 函数,set_pattern 函数通过模式字符串构造一个 pattern_formatter,
template<typename ConsoleMutex>
SPDLOG_INLINE void stdout_sink_base<ConsoleMutex>::flush()
{
std::lock_guard<mutex_t> lock(mutex_);
fflush(file_);
}
template<typename ConsoleMutex>
SPDLOG_INLINE void stdout_sink_base<ConsoleMutex>::set_pattern(const std::string &pattern)
{
std::lock_guard<mutex_t> lock(mutex_);
formatter_ = std::unique_ptr<spdlog::formatter>(new pattern_formatter(pattern));
}
template<typename ConsoleMutex>
stdout_sink_base 通过 mutex_ 实现线程安全。在每个需要保护数据的公共接口中,都会对 mutex_ 加锁。mutex_ 的类型是 mutex_t &(即 ConsoleMutex::mutex_t),从模板参数 ConsoleMutex 获取的。ConsoleMutex::mutex_t 可以是线程安全的互斥锁(如 console_mutex),也可以是非线程安全的空锁(如 console_nullmutex)。
控制台不同于普通文件,一个进程通常只有一个全局的控制台用于输出,因此所有的 stdout_sink_base 及其派生类共用一个控制台,也就需要共用一个互斥锁。
struct console_mutex
{
using mutex_t = std::mutex; // 标准库互斥锁
static mutex_t &mutex()
{
static mutex_t s_mutex; // 确保全局共享一个锁
return s_mutex;
}
};
struct console_nullmutex
{
using mutex_t = null_mutex; // 自定义空锁
static mutex_t &mutex()
{
static mutex_t s_mutex; // 确保全局共享一个锁
return s_mutex;
}
};
标签:std,log,level,源码,sink,base,msg,spdlog
From: https://blog.csdn.net/weixin_45605341/article/details/139376677