spdlog日志库源码:输出通道sink

2024-06-01 20:20

本文主要是介绍spdlog日志库源码:输出通道sink,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

概述

在 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 alllevel_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_formattervoid set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) final; // 设置formatter指定格式, 支持自定义formatterprotected:// sink formatterstd::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.." messageif (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 messagedist_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 intif (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 _WIN32HANDLE 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;}
};

这篇关于spdlog日志库源码:输出通道sink的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1022021

相关文章

C++中实现调试日志输出

《C++中实现调试日志输出》在C++编程中,调试日志对于定位问题和优化代码至关重要,本文将介绍几种常用的调试日志输出方法,并教你如何在日志中添加时间戳,希望对大家有所帮助... 目录1. 使用 #ifdef _DEBUG 宏2. 加入时间戳:精确到毫秒3.Windows 和 MFC 中的调试日志方法MFC

SpringBoot如何使用TraceId日志链路追踪

《SpringBoot如何使用TraceId日志链路追踪》文章介绍了如何使用TraceId进行日志链路追踪,通过在日志中添加TraceId关键字,可以将同一次业务调用链上的日志串起来,本文通过实例代码... 目录项目场景:实现步骤1、pom.XML 依赖2、整合logback,打印日志,logback-sp

Python使用Colorama库美化终端输出的操作示例

《Python使用Colorama库美化终端输出的操作示例》在开发命令行工具或调试程序时,我们可能会希望通过颜色来区分重要信息,比如警告、错误、提示等,而Colorama是一个简单易用的Python库... 目录python Colorama 库详解:终端输出美化的神器1. Colorama 是什么?2.

Java汇编源码如何查看环境搭建

《Java汇编源码如何查看环境搭建》:本文主要介绍如何在IntelliJIDEA开发环境中搭建字节码和汇编环境,以便更好地进行代码调优和JVM学习,首先,介绍了如何配置IntelliJIDEA以方... 目录一、简介二、在IDEA开发环境中搭建汇编环境2.1 在IDEA中搭建字节码查看环境2.1.1 搭建步

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

MOLE 2.5 分析分子通道和孔隙

软件介绍 生物大分子通道和孔隙在生物学中发挥着重要作用,例如在分子识别和酶底物特异性方面。 我们介绍了一种名为 MOLE 2.5 的高级软件工具,该工具旨在分析分子通道和孔隙。 与其他可用软件工具的基准测试表明,MOLE 2.5 相比更快、更强大、功能更丰富。作为一项新功能,MOLE 2.5 可以估算已识别通道的物理化学性质。 软件下载 https://pan.quark.cn/s/57

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

顺序表之创建,判满,插入,输出

文章目录 🍊自我介绍🍊创建一个空的顺序表,为结构体在堆区分配空间🍊插入数据🍊输出数据🍊判断顺序表是否满了,满了返回值1,否则返回0🍊main函数 你的点赞评论就是对博主最大的鼓励 当然喜欢的小伙伴可以:点赞+关注+评论+收藏(一键四连)哦~ 🍊自我介绍   Hello,大家好,我是小珑也要变强(也是小珑),我是易编程·终身成长社群的一名“创始团队·嘉宾”