spdlog学习记录

news2024/12/31 4:59:02

spdlog

请添加图片描述

  • Loggers:是 Spdlog 最基本的组件,负责记录日志消息。在 Spdlog 中,一个 Logger 对象代表着一个日志记录器,应用程序可以使用 Logger 对象记录不同级别的日志消息
  • Sinks:决定了日志消息的输出位置。在 Spdlog 中,一个 Sink 对象代表着一个输出位置,例如控制台、文件、网络等。应用程序可以将不同的日志消息发送到不同的 Sink 中
  • Formatters:负责将日志消息转换为特定格式。在 Spdlog 中,一个 Formatter 对象代表着一个消息格式器,应用程序可以使用不同的 Formatter 对象将日志消息转换为不同的格式
  • Async Logger:是 Spdlog 的异步记录器,它负责将日志消息异步地写入到目标 Sink 中。当应用程序调用 Logger 对象记录一个日志消息时,该消息会被加入到一个队列中,然后异步地写入目标 Sink 中。这样可以避免多个线程同时访问 Sink,从而确保线程安全性
  • Registry:用于管理 Spdlog 的所有组件。在 Spdlog 中,所有的 Loggers、Sinks、Formatters 和 Async Logger 都在一个全局注册表中注册,Registry 用于管理这些组件

spdlog 记录日志的流程

当应用程序调用spdlog 记录日志时,spdlog 的调用流程如下:

  1. 获取一个 Logger 对象
  2. 使用该 Logger 对象记录一个日志消息,该消息包括日志级别、时间戳、线程 ID、文件名和行号等信息
  3. 将日志消息传递给 Formatter,将消息转换为特定格式
  4. 将格式化后的消息传递给 Async Logger
  5. Async Logger 将消息写入目标 Sink,完成日志记录。

源码结构

.
├── CMakeLists.txt
├── bench
├── cmake
├── example
│   ├── CMakeLists.txt
│   └── example.cpp
├── include
│   └── spdlog
│       ├── async.h       // *.h 异步模式,日志库接口等实现
│       ├── async_logger-inl.h
│       ├── async_logger.h
│       ├── cfg
│       ├── common-inl.h
│       ├── common.h
│       ├── details         // 功能函数目录
│       │   ├── registry-inl.h
│       │   ├── registry.h  // 管理所有logger的获取、创建、销毁等
│       │   ├── mpmc_blocking_q.h //  多生产多消费者队列
│       │   ├── circular_q.h  // 循环队列
│       │   ├── thread_pool-inl.h 
│       │   ├── thread_pool.h // 线程池
│       │   └── synchronous_factory.h
│       ├── fmt             // {fmt} 库目录
│       ├── formatter.h
│       ├── fwd.h
│       ├── logger-inl.h
│       ├── logger.h
│       ├── mdc.h
│       ├── pattern_formatter-inl.h
│       ├── pattern_formatter.h
│       ├── sinks         // 落地文件格式实现
│       │   ├── android_sink.h
│       │   ├── ansicolor_sink-inl.h
│       │   ├── ansicolor_sink.h
│       │   ├── base_sink-inl.h
│       │   ├── base_sink.h
│       │   ├── basic_file_sink-inl.h
│       │   ├── basic_file_sink.h
│       │   ├── callback_sink.h
│       │   ├── daily_file_sink.h
│       │   ├── dist_sink.h
│       │   ├── dup_filter_sink.h
│       │   ├── hourly_file_sink.h
│       │   ├── kafka_sink.h
│       │   ├── mongo_sink.h
│       │   ├── msvc_sink.h
│       │   ├── null_sink.h
│       │   ├── ostream_sink.h
│       │   ├── qt_sinks.h
│       │   ├── ringbuffer_sink.h
│       │   ├── rotating_file_sink-inl.h
│       │   ├── rotating_file_sink.h
│       │   ├── sink-inl.h
│       │   ├── sink.h      // 所有sink的基类
│       │   ├── stdout_color_sinks-inl.h
│       │   ├── stdout_color_sinks.h
│       │   ├── stdout_sinks-inl.h
│       │   ├── stdout_sinks.h
│       │   ├── syslog_sink.h
│       │   ├── systemd_sink.h
│       │   ├── tcp_sink.h
│       │   ├── udp_sink.h
│       │   ├── win_eventlog_sink.h
│       │   ├── wincolor_sink-inl.h
│       │   └── wincolor_sink.h
│       ├── spdlog-inl.h
│       ├── spdlog.h
│       ├── stopwatch.h
│       ├── tweakme.h      
│       └── version.h
├── src
│   ├── async.cpp         // .cpp 文件,组成编译模块生成静态库使用
│   ├── bundled_fmtlib_format.cpp
│   ├── cfg.cpp
│   ├── color_sinks.cpp
│   ├── file_sinks.cpp
│   ├── spdlog.cpp
│   └── stdout_sinks.cpp
└── tests             // 测试代码

其中:

  • spdlog/spdlog.h 为日志库接口,提供日志宏的属性控制函数
  • spdlog/logger.h 为日志管理器,为前后端连接的枢纽
  • spdlog/async.h 为异步模式接口
  • spdlog/sinks/base_sink.h 为日志文件格式基类,后面所有的日志文件格式都是继承该类来实现不同功能
  • spdlog/sinks/registry.h 用于注册所有的logger,及一些默认的属性,如日志格式、日志写入等级

spdlog编译方式

spdlog的源码被分在xxx.hxxx-inl.h文件中,其中xxx.h只有函数和类的声明,而实现都以inline的方式写在了xxx-inl.h中。spdlog提供了header-only versioncompiled version2种方式, 通过SPDLOG_HEADER_ONLY宏定义,可以调节.h文件中是否包含了其实现的代码,如果包含了那就是header-only version。如果不包含,那它就是compiled version中普通的头文件。如果是compiled version形式,则其源码中包含xxx-inl.h实现就可以了。SPDLOG_COMPILED_LIBSPDLOG_HEADER_ONLY宏定义:

// spdlog/common.h
#ifdef SPDLOG_COMPILED_LIB
    #undef SPDLOG_HEADER_ONLY
    #define SPDLOG_INLINE
#else  // !defined(SPDLOG_COMPILED_LIB)
    #define SPDLOG_API
    #define SPDLOG_HEADER_ONLY
    #define SPDLOG_INLINE inline
#endif  // #ifdef SPDLOG_COMPILED_LIB
// async.cpp
#ifndef SPDLOG_COMPILED_LIB
    #error Please define SPDLOG_COMPILED_LIB to compile this file.
#endif

#include <spdlog/async.h> // 正常async头文件,包含类和接口声明
// xxx-inl.h 具体实现
#include <spdlog/async_logger-inl.h>
#include <spdlog/details/periodic_worker-inl.h>
#include <spdlog/details/thread_pool-inl.h>

// cfg.cpp
#ifndef SPDLOG_COMPILED_LIB
    #error Please define SPDLOG_COMPILED_LIB to compile this file.
#endif
#include <spdlog/cfg/helpers-inl.h>

spdlog 接口

  • spdlog::debug(), 默认的日志对象,使用默认的日志信息格式,输出至 stdout
  • logger->debug(), 指定日志对象进行日志记录,输出至该日志对象对应的文件中
  • SPDLOG_LOGGER_DEBUG(logger), SPDLOG_DEBUG(), 使用宏对以上两种接口进行包装,产生的日志格式包含 文件、函数、行
    • 支持的文件类型:
      • 标准输出
      • 带颜色的标准输出(默认)
      • 基本文件
      • 可设定时间的滚动文件
      • 可设定大小的滚动文件
      • 过滤重复的日志
      • syslog 日志

spdlog 默认使用同步模式,也可以设置异步模式,异步模式会创建一个线程池,线程池大小可以自行设置,默认为1,该线程池所有者为 details::registry::instance(). 后台的大小可以设置的 多生产者多消费者队列 默认为阻塞模式,也可以设置为非阻塞。

sync-logger 的调用过程

// 调用例如spdlog::info("Welcome to spdlog!"); 
// 或者spdlog::info(num);
template <typename T> void info(const T &msg)
{ log(level::info, msg); }   // 1. 确定log等级为info

// 调用例如spdlog::info("Support for floats {:03.2f}", 1.23456);
// 或者spdlog::info("Positional args are {1} {0}..", "too", "supported");
template <typename... Args> void info(format_string_t<Args...> fmt, Args &&...args)
{ log(level::info, fmt, std::forward<Args>(args)...); }  // 1. 确定log等级为info

template <typename T> void log(level::level_enum lvl, const T &msg)
{ log(source_loc{}, lvl, msg); }  // 2. 确定日志调用的位置(文件、函数名、行号)

template <typename... Args>
void log(level::level_enum lvl, format_string_t<Args...> fmt, Args &&...args)
{ log(source_loc{}, lvl, fmt, std::forward<Args>(args)...); }  // 2. 确定日志调用的位置(文件、函数名、行号)

template <typename T> void log(source_loc loc, level::level_enum lvl, const T &msg)
{ log(loc, lvl, "{}", msg); } // spdlog::info(num);可以等价为spdlog::info("{}", num);这里加了一个“{}”

template <typename... Args>
void log(source_loc loc, level::level_enum lvl, format_string_t<Args...> fmt, Args &&...args)
{ log_(loc, lvl, details::to_string_view(fmt), std::forward<Args>(args)...); }  // 3. log_日志输出

// 4. 
template <typename... Args>
void log_(source_loc loc, level::level_enum lvl, string_view_t fmt, Args &&...args) {
    bool log_enabled = should_log(lvl); // 是否需要记录日志
    bool traceback_enabled = tracer_.enabled(); // 是否需要traceback
    if (!log_enabled && !traceback_enabled) { return; } 

    memory_buf_t buf;
    // 使用第三方的fmt库做格式化,例如将("num {:.2f}", 1.23456) 格式化成 "num 1.23"
    fmt::vformat_to(fmt::appender(buf), fmt, fmt::make_format_args(args...));
    details::log_msg log_msg(loc, name_, lvl, string_view_t(buf.data(), buf.size()));
    // 
    log_it_(log_msg, log_enabled, traceback_enabled);
}

// 5. 
void logger::log_it_(const spdlog::details::log_msg &log_msg,
                     bool log_enabled,
                     bool traceback_enabled) {
  if (log_enabled) { 
    sink_it_(log_msg); 
  }

  if (traceback_enabled) { 
    tracer_.push_back(log_msg); 
  }
}
// 6. 
void logger::sink_it_(const details::log_msg &msg) {
  // 遍历sinks_中的所有sink并把msg交给每个sink去处理
  for (auto &sink : sinks_) {
    if (sink->should_log(msg.level)) { 
      sink->log(msg); 
    }
  }
  // should_flush_: 通过判断msg的等级和flush_level_的关系来决定是不是立即将msg写到文件或终端
  if (should_flush_(msg)) { 
    flush_(); 
  }
}
// 7. 让所有sink都进行一次flush操作
void logger::flush_() {
  for (auto &sink : sinks_) { 
    sink->flush(); // 将缓冲区的内容进一步写入到文件或者控制台等最终目的地
  }
}

async-logger 的调用过程

async-logger的代码在asyn_logger.h和async_looger-inl.h中,对应async_logger类。async_logger继承自logger,前面关于接受日志内容整理log_msg对象中的工作照常做,将对sink的调用(包括sink->log(msg)和sink->flush())都交由线程池去执行了,由此便实现了异步。代码如下:

void spdlog::async_logger::sink_it_(const details::log_msg &msg)
{
  if (auto pool_ptr = thread_pool_.lock()) {
      pool_ptr->post_log(shared_from_this(), msg, overflow_policy_);
  }
  else {
      throw_spdlog_ex("async log: thread pool doesn't exist anymore");
  }
}
// thread_pool_ 的声明
std::weak_ptr<details::thread_pool> thread_pool_;

线程池里面要有一个多生产多消费的线程安全队列,用来存放日志内容。可以有多个async_logger(即生产者)向里面生产日志,又同时又多个线程(即消费者)从里面消费日志。这个队列的容量应该是有限的,当队列满了之后向里面生产日志可以有不同的策略,spdlog提供了三种策略:阻塞丢弃新日志丢弃旧日志。为方便实现这个需求,用循环队列来实现

循环队列

循环队列的代码在circular_q.h中实现:

  • circular_q应设计成类模板,使其能够支持各种数据类型;
  • circular_q中实际存数据的std::vector<T> vec_的大小应该比circular_q能存的数据大小多一个,这样才能队列是满的还是空的

多生产多消费的线程安全队列

template <typename T>
class mpmc_blocking_queue {
  ...
  // try to enqueue and block if no room left
  void enqueue(T &&item) {
    std::unique_lock<std::mutex> lock(queue_mutex_);
    pop_cv_.wait(lock, [this] { return !this->q_.full(); }); // 阻塞式等待,直到q_非满状态
    q_.push_back(std::move(item));
    push_cv_.notify_one();
  }

  // blocking dequeue without a timeout.
  void dequeue(T &popped_item) {
    std::unique_lock<std::mutex> lock(queue_mutex_);
    push_cv_.wait(lock, [this] { return !this->q_.empty(); }); // 阻塞式等待
    popped_item = std::move(q_.front());
    q_.pop_front();
    pop_cv_.notify_one();
  }

private:
  std::mutex queue_mutex_;
  std::condition_variable push_cv_;
  std::condition_variable pop_cv_;
  spdlog::details::circular_q<T> q_;
  std::atomic<size_t> discard_counter_{0};
}

spdlog线程池

thread_pool使用了mpmc_blocking_queue(多生产者-多消费者阻塞队列)来缓存日志消息。这个队列允许多个前端线程(生产者)同时向队列中添加日志消息,也允许多个后端线程(消费者)同时从队列中取出消息。前端线程是指用户调用日志记录功能的线程。当用户调用异步日志记录方法时,日志消息会被封装成 async_msg 对象,并放入 mpmc_blocking_queue 队列中。thread_pool 内部维护了一组后端线程,这些线程从 mpmc_blocking_queue 队列中取出日志消息并进行处理。实际上是调用 async_logger::backend_sink_it_ 方法,将日志消息写入到预先注册的 sink(日志输出目标,如文件、控制台等)

log_msg
+string_view_t logger_name
+level::level_enum level
+log_clock::time_point time
+size_t thread_id
+source_loc source;
+string_view_t payload;
+mutable size_t color_range_start
+mutable size_t color_range_end
+log_msg(log_clock::time_point log_time, source_loc loc,string_view_t logger_name,level::level_enum lvl, string_view_t msg)
+log_msg(source_loc loc, string_view_t logger_name, level::level_enum lvl, string_view_t msg)
+log_msg(string_view_t logger_name, level::level_enum lvl, string_view_t msg)
log_msg_buffer
-memory_buf_t buffer
+log_msg_buffer()
+explicit log_msg_buffer(const log_msg &orig_msg)
+log_msg_buffer(const log_msg_buffer &other)
+log_msg_buffer(log_msg_buffer &&other)
+log_msg_buffer &operator=(const log_msg_buffer &other)
+log_msg_buffer &operator=(log_msg_buffer &&other)
-void update_string_views()
async_msg
+async_msg_type msg_type
+async_logger_ptr worker_ptr
+std::promise flush_promise
+async_msg()
+~async_msg()
+async_msg(const async_msg &)
+async_msg(async_logger_ptr &&worker, async_msg_type the_type, const details::log_msg &m)
+async_msg(async_logger_ptr &&worker, async_msg_type the_type)
+async_msg(async_logger_ptr &&worker, async_msg_type the_type, std::promise &&promise)
+explicit async_msg(async_msg_type the_type)
thread_pool
-q_type q_
-std::vector threads_
+thread_pool(size_t q_max_items, size_t threads_n, std::function on_thread_start, std::function on_thread_stop)
+thread_pool(size_t q_max_items, size_t threads_n, std::function on_thread_start)
+thread_pool(size_t q_max_items, size_t threads_n)
+void post_log(async_logger_ptr &&worker_ptr, const details::log_msg &msg, async_overflow_policy overflow_policy)
+std::future post_flush(async_logger_ptr &&worker_ptr, async_overflow_policy overflow_policy)
+size_t overrun_counter()
+void reset_overrun_counter()
+size_t discard_counter()
+void reset_discard_counter()
+size_t queue_size()
-void post_async_msg_(async_msg &&new_msg, async_overflow_policy overflow_policy)
-void worker_loop_()
-bool process_next_msg_()
SPDLOG_INLINE thread_pool::thread_pool(size_t q_max_items,
                                       size_t threads_n,
                                       std::function<void()> on_thread_start,
                                       std::function<void()> on_thread_stop)
    : q_(q_max_items) {
    if (threads_n == 0 || threads_n > 1000) {
        throw_spdlog_ex("spdlog::thread_pool(): invalid threads_n param (valid range is 1-1000)");
    }
    for (size_t i = 0; i < threads_n; i++) {
        threads_.emplace_back([this, on_thread_start, on_thread_stop] {
            on_thread_start();
            this->thread_pool::worker_loop_();
            on_thread_stop();
        });
    }
}
SPDLOG_INLINE thread_pool::thread_pool(size_t q_max_items,
                                       size_t threads_n,
                                       std::function<void()> on_thread_start)
    : thread_pool(q_max_items, threads_n, on_thread_start, [] {}) {
    
}
SPDLOG_INLINE thread_pool::thread_pool(size_t q_max_items, size_t threads_n)
    : thread_pool(q_max_items, threads_n, [] {}, [] {}) {
          
}

void SPDLOG_INLINE thread_pool::worker_loop_() {
    while (process_next_msg_()) {
    }
}

// process next message in the queue, return true if this thread should 
// still be active (while no terminate msg was received)
bool SPDLOG_INLINE thread_pool::process_next_msg_() {
  async_msg incoming_async_msg;
  q_.dequeue(incoming_async_msg);
  switch (incoming_async_msg.msg_type) {
    case async_msg_type::log: {
      incoming_async_msg.worker_ptr->backend_sink_it_(incoming_async_msg);
      return true;
    }
    case async_msg_type::flush: {
      incoming_async_msg.worker_ptr->backend_flush_();
      incoming_async_msg.flush_promise.set_value();
      return true;
    }
    case async_msg_type::terminate: {
      return false;
    }
    default: {
      assert(false);
    }
  }
  return true;
}

std::future<void> SPDLOG_INLINE thread_pool::post_flush(async_logger_ptr &&worker_ptr,
                                                        async_overflow_policy overflow_policy) {
    std::promise<void> promise;
    std::future<void> future = promise.get_future();
    post_async_msg_(async_msg(std::move(worker_ptr), async_msg_type::flush, std::move(promise)), 
                   overflow_policy);
    return future;
}
void SPDLOG_INLINE thread_pool::post_async_msg_(async_msg &&new_msg, async_overflow_policy overflow_policy) {
    if (overflow_policy == async_overflow_policy::block) {
        q_.enqueue(std::move(new_msg));
    } else if (overflow_policy == async_overflow_policy::overrun_oldest) {
        q_.enqueue_nowait(std::move(new_msg));
    } else {
        assert(overflow_policy == async_overflow_policy::discard_new);
        q_.enqueue_if_have_room(std::move(new_msg));
    }
}

spdlog sink

sink接收log_msg对象,并通过formatter将对象中所含有的信息转换成字符串,最后将字符串输出到指定的地方,例如控制台、文件等,甚至通过tcp/udp将字符串发送到指定的地方

sink 类图

sink
#level_t level_ = level::trace;
+virtual void log(const details::log_msg &msg)
+virtual void flush()
+virtual void set_pattern(const std::string &pattern)
+virtual void set_formatter(std::unique_ptr sink_formatter)
+void set_level(level::level_enum log_level)
+level::level_enum level()
+bool should_log(level::level_enum msg_level)
base_sink<Mutex>
#std::unique_ptr formatter_
#Mutex mutex_
+void log(const details::log_msg &msg)
+void flush()
+void set_pattern(const std::string &pattern)
+void set_formatter(std::unique_ptr sink_formatter)
#virtual void sink_it_(const details::log_msg &msg)
#virtual void flush_()
#virtual void set_pattern_(const std::string &pattern)
#virtual void set_formatter_(std::unique_ptr sink_formatter)
basic_file_sink<Mutex>
-details::file_helper file_helper_
#void sink_it_(const details::log_msg &msg)
#void flush_()
daily_file_sink<Mutex、FileNameCalc>
-filename_t base_filename_;
-int rotation_h_;
-int rotation_m_;
-log_clock::time_point rotation_tp_;
-details::file_helper file_helper_;
-bool truncate_;
-uint16_t max_files_;
-details::circular_q filenames_q_;
+daily_file_sink(filename_t base_filename, int rotation_hour, int rotation_minute, bool truncate = false, uint16_t max_files = 0, const file_event_handlers &event_handlers)
+filename_t filename()
#void sink_it_(const details::log_msg &msg)
#void flush_()
-void init_filenames_q_()
-tm now_tm(log_clock::time_point tp)
-log_clock::time_point next_rotation_tp_()
-void delete_old_()
hourly_file_sink<Mutex、FileNameCalc>
-filename_t base_filename_;
-log_clock::time_point rotation_tp_;
-details::file_helper file_helper_;
-bool truncate_;
-uint16_t max_files_;
-details::circular_q filenames_q_;
-bool remove_init_file_;
+hourly_file_sink(filename_t base_filename, bool truncate = false, uint16_t max_files = 0, const file_event_handlers &event_handlers)
+filename_t filename()
#void sink_it_(const details::log_msg &msg)
#void flush_()
-void init_filenames_q_()
-tm now_tm(log_clock::time_point tp)
-log_clock::time_point next_rotation_tp_()
-void delete_old_()
rotating_file_sink<Mutex>
-filename_t base_filename_;
-std::size_t max_size_;
-std::size_t max_files_;
-std::size_t current_size_;
-details::file_helper file_helper_;
+rotating_file_sink(filename_t base_filename, std::size_t max_size,std::size_t max_files,bool rotate_on_open = false,const +file_event_handlers &event_handlers)
+filename_t calc_filename(const filename_t &filename, std::size_t index)
+filename_t filename()
#void sink_it_(const details::log_msg &msg)
#void flush_()
-void rotate_()
-bool rename_file_(const filename_t &src_filename, const filename_t &target_filename)
dist_sink<Mutex>
#std::vector> sinks_
+explicit dist_sink(std::vector> sinks)
+void add_sink(std::shared_ptr sub_sink)
+void remove_sink(std::shared_ptr sub_sink)
+void set_sinks(std::vector> sinks)
+std::vector> &sinks()
#void sink_it_(const details::log_msg &msg)
#void flush_()
#void set_pattern_(const std::string &pattern)
#void set_formatter_(std::unique_ptr sink_formatter)
dup_filter_sink<Mutex>
#std::chrono::microseconds max_skip_duration_;
#log_clock::time_point last_msg_time_;
#std::string last_msg_payload_;
#size_t skip_counter_ = 0;
#level::level_enum log_level_;
+explicit dup_filter_sink~Rep,Period~(std::chrono::duration max_skip_duration, level::level_enum notification_level = level::info)
#void sink_it_(const details::log_msg &msg)
#bool filter_(const details::log_msg &msg)
stdout_sink_base<ConsoleMutex>
#mutex_t &mutex_;
#FILE *file_;
#std::unique_ptr formatter_;
+explicit stdout_sink_base(FILE *file)
+void log(const details::log_msg &msg)
+void flush()
+void set_pattern(const std::string &pattern)
+void set_formatter(std::unique_ptr sink_formatter)
stdout_sink<ConsoleMutex>
+stdout_sink()
stderr_sink<ConsoleMutex>
+stderr_sink()

源码剖析

logger factory

// synchronous_factory.h 
// Default logger factory-  creates synchronous loggers
struct synchronous_factory {
    template <typename Sink, typename... SinkArgs>
    static std::shared_ptr<spdlog::logger> create(std::string logger_name, SinkArgs &&...args) {
        auto sink = std::make_shared<Sink>(std::forward<SinkArgs>(args)...);
        auto new_logger = std::make_shared<spdlog::logger>(std::move(logger_name), std::move(sink));
        details::registry::instance().initialize_logger(new_logger);
        return new_logger;
    }
};

// stdout_sinks factory
template <typename Factory = spdlog::synchronous_factory>
std::shared_ptr<logger> stdout_logger_mt(const std::string &logger_name);

template <typename Factory = spdlog::synchronous_factory>
std::shared_ptr<logger> stdout_logger_st(const std::string &logger_name);

template <typename Factory = spdlog::synchronous_factory>
std::shared_ptr<logger> stderr_logger_mt(const std::string &logger_name);

template <typename Factory = spdlog::synchronous_factory>
std::shared_ptr<logger> stderr_logger_st(const std::string &logger_name);


// stdout_color_sinks
// stdout_color_mt: 多线程(mult thread)   stdout_color_st: 单线程(single thread)
template <typename Factory>
SPDLOG_INLINE std::shared_ptr<logger> stdout_color_mt(const std::string &logger_name, color_mode mode) {
    return Factory::template create<sinks::stdout_color_sink_mt>(logger_name, mode);
}

template <typename Factory>
SPDLOG_INLINE std::shared_ptr<logger> stdout_color_st(const std::string &logger_name, color_mode mode) {
    return Factory::template create<sinks::stdout_color_sink_st>(logger_name, mode);
}

template <typename Factory>
SPDLOG_INLINE std::shared_ptr<logger> stderr_color_mt(const std::string &logger_name, color_mode mode) {
    return Factory::template create<sinks::stderr_color_sink_mt>(logger_name, mode);
}

template <typename Factory>
SPDLOG_INLINE std::shared_ptr<logger> stderr_color_st(const std::string &logger_name, color_mode mode) {
    return Factory::template create<sinks::stderr_color_sink_st>(logger_name, mode);
}

// basic_file_sink factory
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);
}

// daily_file_sink factory
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 = {}) {
    return Factory::template create<sinks::daily_file_sink_mt>(logger_name, filename, hour, minute, truncate, max_files, event_handlers);
}

template <typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> daily_logger_format_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 = {}) {
    return Factory::template create<sinks::daily_file_format_sink_mt>(logger_name, filename, hour, minute, truncate, max_files, event_handlers);
}

template <typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> daily_logger_st(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 = {}) {
    return Factory::template create<sinks::daily_file_sink_st>(logger_name, filename, hour, minute, truncate, max_files, event_handlers);
}

template <typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> daily_logger_format_st(
    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 = {}) {
    return Factory::template create<sinks::daily_file_format_sink_st>(logger_name, filename, hour, minute, truncate, max_files, event_handlers);
}

// hourly_file_sink
template <typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> hourly_logger_mt(const std::string &logger_name,
                                                const filename_t &filename,
                                                bool truncate = false,
                                                uint16_t max_files = 0,
                                                const file_event_handlers &event_handlers = {}) {
    return Factory::template create<sinks::hourly_file_sink_mt>(logger_name, filename, truncate, max_files, event_handlers);
}

template <typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> hourly_logger_st(const std::string &logger_name,
                                                const filename_t &filename,
                                                bool truncate = false,
                                                uint16_t max_files = 0,
                                                const file_event_handlers &event_handlers = {}) {
    return Factory::template create<sinks::hourly_file_sink_st>(logger_name, filename, truncate, max_files, event_handlers);
}
// rotating_file_sink
template <typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> rotating_logger_mt(const std::string &logger_name,
                                                  const filename_t &filename,
                                                  size_t max_file_size,
                                                  size_t max_files,
                                                  bool rotate_on_open = false,
                                                  const file_event_handlers &event_handlers = {}) {
    return Factory::template create<sinks::rotating_file_sink_mt>(logger_name, filename, max_file_size, max_files, rotate_on_open, event_handlers);
}

template <typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> rotating_logger_st(const std::string &logger_name,
                                                  const filename_t &filename,
                                                  size_t max_file_size,
                                                  size_t max_files,
                                                  bool rotate_on_open = false,
                                                  const file_event_handlers &event_handlers = {}) {
    return Factory::template create<sinks::rotating_file_sink_st>(logger_name, filename, max_file_size, max_files, rotate_on_open, event_handlers);
}
以stdout_logger为例看整个logger创建过程
using stdout_sink_mt = stdout_sink<details::console_mutex>;     // 有锁对应多线程版本
using stdout_sink_st = stdout_sink<details::console_nullmutex>; // 无锁对应单线程版本

// 1. 调用工厂方法
template <typename Factory>
SPDLOG_INLINE std::shared_ptr<logger> stdout_logger_mt(const std::string &logger_name) {
    return Factory::template create<sinks::stdout_sink_mt>(logger_name);
}
// 2. 创建logger之后将其注册进registry并返回logger
struct synchronous_factory {
    template <typename Sink, typename... SinkArgs>
    static std::shared_ptr<spdlog::logger> create(std::string logger_name, SinkArgs &&...args) {
        auto sink = std::make_shared<Sink>(std::forward<SinkArgs>(args)...);
        auto new_logger = std::make_shared<spdlog::logger>(std::move(logger_name), std::move(sink)); // 1. 构造logger
        details::registry::instance().initialize_logger(new_logger);  // 2. 调用registry的initialize_logger方法中
        return new_logger;
    }
};
// 3.
SPDLOG_INLINE void registry::initialize_logger(std::shared_ptr<logger> new_logger) {
    std::lock_guard<std::mutex> lock(logger_map_mutex_);
    new_logger->set_formatter(formatter_->clone());
    if (err_handler_) {
        new_logger->set_error_handler(err_handler_);
    }
    // set new level according to previously configured level or default level
    auto it = log_levels_.find(new_logger->name());
    auto new_level = it != log_levels_.end() ? it->second : global_log_level_;
    new_logger->set_level(new_level);
    new_logger->flush_on(flush_level_);
    if (backtrace_n_messages_ > 0) {
        new_logger->enable_backtrace(backtrace_n_messages_);
    }
    if (automatic_registration_) {
        register_logger_(std::move(new_logger));
    }
}

registry

Loggers registry of unique name->logger pointer. An attempt to create a logger with an already existing name will result
with spdlog_ex exception.If user requests a non existing logger, nullptr will be returned. This class is thread safe

// std::unordered_map<std::string, std::shared_ptr<logger>> loggers_;

// create default logger (ansicolor_stdout_sink_mt)
registry::registry() : formatter_(new pattern_formatter()) {
  auto color_sink = std::make_shared<sinks::ansicolor_stdout_sink_mt>();
  const char *default_logger_name = "";
  default_logger_ = std::make_shared<spdlog::logger>(default_logger_name, std::move(color_sink));
  loggers_[default_logger_name] = default_logger_;
spdlog::info(“Welcome to spdlog!”)调用分析
// spdlog/spdlog.h
template <typename... Args>
inline void info(format_string_t<Args...> fmt, Args &&...args) {
    default_logger_raw()->info(fmt, std::forward<Args>(args)...);
}

spdlog::logger *default_logger_raw() {
    return details::registry::instance().get_default_raw();
}

// spdlog/details/register-inl.h
registry &registry::instance() {
    static registry s_instance;
    return s_instance;
}

// spdlog/details/register-inl.h
logger *registry::get_default_raw() { 
  return default_logger_.get();  // default_logger_ 由registry模块初始化
}

附件

超详细!spdlog源码解析(上)
spdlog源码解读(三)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2220527.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深入拆解TomcatJetty(三)

深入拆解Tomcat&Jetty&#xff08;三&#xff09; 专栏地址&#xff1a;https://time.geekbang.org/column/intro/100027701 1 Tomcat组件生命周期 Tomcat如何如何实现一键式启停 Tomcat 架构图和请求处理流程如图所示&#xff1a; 对组件之间的关系进行分析&#xff0c;…

MySQL(python开发)——(3)表数据的基本操作,增删改查

MySQL&#xff08;python开发)——&#xff08;1&#xff09;数据库概述及其MySQL介绍 MySQL&#xff08;python开发)——&#xff08;2&#xff09;数据库基本操作及数据类型 MySQL—— 表数据基本操作 一、表中插入(insert)数据——增 insert into 表名 values (值1&#…

人工智能正在扼杀云计算的可持续性

可持续性曾是公共云计算中备受推崇的优势。企业和云提供商大肆宣扬他们的绿色计划&#xff0c;推广采用可再生能源的数据中心&#xff0c;以减少碳足迹。 近几个月来&#xff0c;这个话题已悄然淡出人们的视线。罪魁祸首是什么&#xff1f;对人工智能功能的无限需求正在推动云…

大数据-180 Elasticsearch - 原理剖析 索引写入与近实时搜索

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

Django配置路由后,为什么输入http://127.0.0.1:8000/ 网址后报错了?

问题探讨&#xff1a;为什么输入http://127.0.0.1:8000/ 网址后报错了&#xff1f; 翻译一下&#xff1a; 屏蔽一下新加的路由 发现界面正常了 现在翻译一下&#xff1a; 是不是比较理解了&#xff0c;admin 属于默认配置的URL,所以urlpatterns为空列表或仅配置admin路由时&…

【算法篇】贪心类(1)(笔记)

目录 一、理论基础 1. 大纲 2. 求解步骤 二、Leetcode 题目 1. 分发饼干 2. 摆动序列 3. 最大子序和 4. 买卖股票的最佳时机 II 5. 跳跃游戏 6. 跳跃游戏 II 7. K 次取反后最大化的数组和 8. 加油站 9. 分发糖果 一、理论基础 1. 大纲 2. 求解步骤 将问题分解为…

人工智能:塑造未来生活与工作的力量

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《热点时事》 期待您的关注 引言 随着人工智能技术的不断发展&#xff0c;我们已经看到了它在各行业带来的巨大变革。 在医疗行业中…

【wpf】08 xml文件的存取操作

在使用wpf编程过程中&#xff0c;会用到xml的配置文件&#xff0c;实现对其读取和存储的操作是必须的。 1 xml说明 可扩展标记语言 (Extensible Markup Language, XML) &#xff0c;标准通用标记语言的子集&#xff0c;可以用来标记数据、定义数据类型&#xff0c;是一种允许…

git clone报错fatal: pack has bad object at offset 186137397: inflate returned 1

逐步拷贝 https://stackoverflow.com/questions/27653116/git-fatal-pack-has-bad-object-at-offset-x-inflate-returned-5 https://www.cnblogs.com/Lenbrother/p/17726195.html https://cloud.tencent.com/developer/ask/sof/107092182 git clone --depth 1 <repository…

外包干了30年,人都快要废了。。。。。

先说一下自己的情况&#xff0c;本科生&#xff0c;19年通过校招进入南京某软件公司&#xff0c;干了接近2年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了2年的功能测试&…

如何做软件系统的维护成本估算?

一、人员成本 维护工程师 确定维护工程师的数量和技能级别。例如&#xff0c;可能需要 2 名中级维护工程师&#xff0c;月薪 10000 元左右。计算每月的人员成本为 2 10000 20000 元。 技术支持人员 技术支持人员负责解答用户的问题和处理紧急情况。假设需要 1 名技术支持人员…

django5入门【03】新建一个hello界面

注意 ⭐前提&#xff1a;将上节的项目导入到pycharm中操作步骤总结&#xff1a; 1、HelloDjango/HelloDjango目录下&#xff0c;新建一个views.py 2、HelloDjango/HelloDjango/urls.py 文件中&#xff0c;配置url路由信息 3、新建终端&#xff0c;执行运行命令python manag…

Noteexpress在已有作者名字时怎么只标注年份

如图 需要除掉重复的人名 达到如下只出现年份的效果 方法&#xff1a; 打开 编辑引文 然后&#xff0c;选中文献&#xff0c;并勾选“不显示作者名” 按确定即可。

js.杨辉三角和分发饼干

1&#xff0c;链接&#xff1a;118. 杨辉三角 - 力扣&#xff08;LeetCode&#xff09; 题目&#xff1a; 给定一个非负整数 numRows&#xff0c;生成「杨辉三角」的前 numRows 行。 在「杨辉三角」中&#xff0c;每个数是它左上方和右上方的数的和。 示例 1: 输入: numRows …

PostgreSQL的学习心得和知识总结(一百五十五)|[performance]优化期间将 WHERE 子句中的 IN VALUES 替换为 ANY

目录结构 注&#xff1a;提前言明 本文借鉴了以下博主、书籍或网站的内容&#xff0c;其列表如下&#xff1a; 1、参考书籍&#xff1a;《PostgreSQL数据库内核分析》 2、参考书籍&#xff1a;《数据库事务处理的艺术&#xff1a;事务管理与并发控制》 3、PostgreSQL数据库仓库…

二叉树遍历(前序、中序、后续)

目录 什么是二叉树二叉树遍历以递归创建树的角度看前、中、后序遍历前序遍历中序遍历后序遍历 栈来实现前、中、后序遍历栈的实现栈操作进行前序、中序遍历代码实现中序遍历和先序遍历栈操作进行后序遍历 什么是二叉树 树&#xff1a;树的根节点没有前驱&#xff0c;除根节点以…

Spring声明式事务管理:深入探索XML配置方式

前言 Spring的事务管理&#xff0c;无论是基于xml还是注解实现&#xff0c;本质上还是实现数据库的事务管理机制&#xff0c;因此要注意发送SQL的连接是否为同一个&#xff0c;这是实现声明式事务的关键。 以下案例和实现基于SSM整合框架完成&#xff0c;不知道如何整合SSM&…

CTFHUB技能树之文件上传——无验证

开启靶场&#xff0c;打开链接&#xff1a; 直接上传一句话木马&#xff1a; <?php eval($_POST[pass]);?> 成功提交并显示了上传的文件的路径 访问一下该文件触发一句话木马&#xff1a; 看到一片空白是正常的&#xff0c;因为没有写什么函数&#xff0c;比如&#x…

FineReport 计算同比增长

1、数据库查询 SELECTt1.年,t1.月,t1.总金额 AS 同期金额,t1.仓库名称,t2.总金额 AS 上期金额 FROMtest t1LEFT JOIN test t2 ON ( t1.年 t2.年 1 ) AND t1.月 t2.月 AND t1.仓库名称 t2.仓库名称2、配置字段 月份字段加后缀 月 数据列加后缀 计算同比增长率 if(LEN(B3)0 …

移动零---双指针法

目录 一&#xff1a;题目 二:算法原理讲解 三&#xff1a;代码编写 一&#xff1a;题目 题目链接&#xff1a;https://leetcode.cn/problems/move-zeroes/description/ 二:算法原理讲解 三&#xff1a;代码编写 void moveZeroes2(vector<int>& nums) {for (int d…