首页 > 编程语言 >spdlog日志库源码:输出通道sink

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

时间:2024-06-01 18:32:15浏览次数:22  
标签:std log level 源码 sink base msg spdlog

概述

在 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

相关文章

  • 【技术突破】优秘数字人独立部署源码功能进化
    随着数字人直播系统的兴起,越来越多的人对数字人直播系统源码(源码:ai6ai69)搭建感兴趣。数字人直播系统源码在一些市场上是可以找到的,但是鱼目混杂、五花八门想要找一个合适自己的就需要一定技巧,要选择一个技术专业的AI数字人直播系统源码服务商是很关键的,关系到成败。优秘数字......
  • 轻松学AI:数字人系统源码部署简易教程“
    随着短视频领域的迅猛发展,数字化概念已经成为我们生活中不可或缺的一部分。在数字化的大潮中,数字人源码部署(源码部署:ai6ai69)成为了一个热门的商业风口项目。很多企业和个人创业者开始关注并探索如何选择适合自己的数字人源码部署方案。在选择数字人系统源码厂家时,我们需要明......
  • 解决源码部署难题:如何定位数字人系统的源头厂商“
    解决源码部署难题:如何定位数字人系统的源头厂商随着短视频领域的爆发,AI人工智能也开始慢慢向这个行业渗透,在这个大背景下,数字人源码部署成为了创业者中热门的话题。面对市场上众多的数字人源码厂家,如何选择一个值得信赖、技术实力过硬的企业成为了很多人的难题。今天,小编就咨......
  • html中轮播图的做法及源码
    <!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width,initial-scale=1.0"><title>Document</title>......
  • 全开源源码---小红书卡片-跳转微信-自动回复跳转卡片-商品卡片-发私信-发群聊-安全导
    做小红书的人都知道小红书的用户商业价值非常高,消费能力很强,很多做高客单产品的都想从小红书平台上引流到私域成交,但是都会遇到账号违规、被封的问题,因为小红书的平台是所有平台里对引流导流最严的。不允许留公众号、手机号、微信号等联系方式,一旦被发现就会面临封禁等处罚。......
  • 开源源码---小红书卡片-跳转微信-自动回复跳转卡片-商品卡片-发私信-发群聊-安全导流
     做小红书的人都知道小红书的用户商业价值非常高,消费能力很强,很多做高客单产品的都想从小红书平台上引流到私域成交,但是都会遇到账号违规、被封的问题,因为小红书的平台是所有平台里对引流导流最严的。不允许留公众号、手机号、微信号等联系方式,一旦被发现就会面临封禁等处罚。......
  • 基于SpringBoot+Vue的在线答疑管理系统设计与实现毕设(文档+源码)
            目录一、项目介绍二、开发环境三、功能介绍四、核心代码五、效果图六、源码获取:        大家好呀,我是一个混迹在java圈的码农。今天要和大家分享的是一款基于SpringBoot+Vue的在线答疑管理系统,项目源码请点击文章末尾联系我哦~目前有各类成......
  • 基于SpringBoot+Vue的在线BLOG网管理系统设计与实现毕设(文档+源码)
            目录一、项目介绍二、开发环境三、功能介绍四、核心代码五、效果图六、源码获取:        大家好呀,我是一个混迹在java圈的码农。今天要和大家分享的是一款基于SpringBoot+Vue的在线BLOG网管理系统,项目源码请点击文章末尾联系我哦~目前有各类......
  • 基于n-gram语言模型实现输入单词推荐功能(附源码及语料库)
    一、开发环境1.语言:python2.开源工具:nltk3.语料库:维基百科英文语料库二、环境配置关于pyhton项目对nltk的部署,我看了以下文章。NLTK库安装教程在安装nltk库的过程中,我又遇到了pip更新的问题,看了以下文章。PIP更新​​​​三、实验要求利用n-gram语言模型完成,输入文字......
  • 基于SpringBoot+Vue的校园博客管理系统设计与实现毕设(文档+源码)
          目录一、项目介绍二、开发环境三、功能介绍四、核心代码五、效果图六、源码获取:        大家好呀,我是一个混迹在java圈的码农。今天要和大家分享的是一款基于SpringBoot+Vue的校园博客管理系统,项目源码请点击文章末尾联系我哦~目前有各类成品毕......