当前位置: 首页 > news >正文

spdlog学习记录

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等级为infotemplate <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(); // 是否需要tracebackif (!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 leftvoid 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. 构造loggerdetails::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 levelauto 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源码解读(三)

相关文章:

spdlog学习记录

spdlog Loggers&#xff1a;是 Spdlog 最基本的组件&#xff0c;负责记录日志消息。在 Spdlog 中&#xff0c;一个 Logger 对象代表着一个日志记录器&#xff0c;应用程序可以使用 Logger 对象记录不同级别的日志消息Sinks&#xff1a;决定了日志消息的输出位置。在 Spdlog 中&…...

linux替换某个文件的某段内容命令

假设文件是a.sql 里面的库是abc&#xff0c;我想把这个abc给替换掉&#xff0c;改成hahaha cat a.sql |grep abc|sed -i s/abc/hahaha/g a.sql 如果想写个脚本指定整个文件夹中的内容替换 #!/bin/bash # 检查是否提供了文件夹路径 if [ -z "\$1" ]; then echo &…...

什么是SQL注入攻击?如何防止呢?

目录 一、什么是SQL注入&#xff1f; 二、如何防止&#xff1f; 2.1 使用预编译语句 2.2 使用 ORM 框架 2.3 用户输入校验 一、什么是SQL注入&#xff1f; SQL 注入是一种常见的网络安全漏洞&#xff0c;攻击者通过在应用程序的用户输入中插入恶意的 SQL 代码&#xff…...

consumer 角度讲一下i2c外设

往期内容 I2C子系统专栏&#xff1a; I2C&#xff08;IIC&#xff09;协议讲解-CSDN博客SMBus 协议详解-CSDN博客I2C相关结构体讲解:i2c_adapter、i2c_algorithm、i2c_msg-CSDN博客内核提供的通用I2C设备驱动I2c-dev.c分析&#xff1a;注册篇内核提供的通用I2C设备驱动I2C-dev.…...

面试经典150题刷题记录

数组部分 1. 合并两个有序的子数组 —— 倒序双指针避免覆盖 88. 合并两个有序数组 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2&#xff0c;另有两个整数 m 和 n &#xff0c;分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 到 nums1 中&#xff0c;使…...

【HarmonyOS NEXT】实现保存base64图片到图库

上篇文章介绍了HarmonyOS NEXT如何保存base64文件到download目录下&#xff0c;本次介绍如何保存base64图片到图库&#xff0c;网络图片保存方式大同小异&#xff0c;先下载图片&#xff0c;然后再保存 phAccessHelper.showAssetsCreationDialog参考官方文档’ ohos.file.pho…...

开题答辩最怕被问什么?教你用ChatGPT轻松准备,稳拿高分!

AIPaperGPT&#xff0c;论文写作神器~ https://www.aipapergpt.com/ 开题答辩是学位论文写作过程中的重要环节&#xff0c;能帮助导师评估你的研究计划是否可行&#xff0c;并对后续写作起到指导作用。很多同学在面对导师提问时会感到紧张&#xff0c;因此提前准备好常见问题的…...

Unity3D功耗和发热分析与优化详解

前言 Unity3D作为广泛使用的游戏开发引擎&#xff0c;在游戏开发过程中&#xff0c;功耗和发热问题一直是开发者需要重点关注的问题。功耗和发热不仅影响用户体验&#xff0c;还可能对设备的硬件寿命造成一定影响。本文将从技术角度详细分析Unity3D游戏在移动设备上的功耗和发…...

深度学习中的掩码介绍

在深度学习中,**掩码(Mask)**通常用于屏蔽掉某些特定部分的数据,以确保模型在训练或推理时不处理无效或无关的输入。掩码的使用场景主要包括处理变长序列、生成模型中的自回归任务、处理填充数据等。掩码可以是二值矩阵(1 表示有效数据,0 表示屏蔽数据),在注意力机制中…...

rust高级进阶总结

文章目录 前言1. Rust生命周期进阶一、不太聪明的生命周期检查&#xff08;一&#xff09;例子1&#xff08;二&#xff09;例子2 二、无界生命周期三、生命周期约束&#xff08;HRTB&#xff09;&#xff08;一&#xff09;语法及含义&#xff08;二&#xff09;综合例子 四、…...

整理—计算机网络

目录 网络OSI模型和TCP/IP模型 应用层有哪些协议 HTTP报文有哪些部分 HTTP常用的状态码 Http 502和 504 的区别 HTTP层请求的类型有哪些&#xff1f; GET和POST的使用场景&#xff0c;有哪些区别&#xff1f; HTTP的长连接 HTTP默认的端口是什么&#xff1f; HTTP1.1怎…...

分布式数据库环境(HBase分布式数据库)的搭建与配置

分布式数据库环境&#xff08;HBase分布式数据库&#xff09;的搭建与配置 1. VMWare安装CentOS7.9.20091.1 下载 CentOS7.9.2009 映像文件1.2启动 VMware WorkstationPro&#xff0c;点击“创建新的虚拟机”1.3在新建虚拟机向导界面选择“典型&#xff08;推荐&#xff09;”1…...

100个JavaWeb(JDBC, Servlet, JSP)毕业设计选题

100个JavaWeb(JDBC, Servlet, JSP)毕业设计选题 教育行业 学生信息管理系统在线考试系统课程管理与选课系统教师评价管理系统图书馆管理系统学生成绩查询系统校园论坛作业提交与批改系统学生考勤管理系统教学资源共享平台 企业管理 员工管理系统考勤打卡系统办公用品申请管…...

05 go语言(golang) - 常量和条件语句

常量 在Go语言中&#xff0c;常量是使用 const 关键字定义的&#xff0c;并且一旦被赋值后&#xff0c;它们的值在程序运行期间不能改变。常量可以是字符、字符串、布尔或数值类型。 基本特性 不可修改&#xff1a;一旦一个常量被定义&#xff0c;它的值就不能被更新。编译时…...

【设计模式】深入理解Python中的适配器模式(Adapter Pattern)

深入理解Python中的适配器模式&#xff08;Adapter Pattern&#xff09; 在软件开发中&#xff0c;常常会遇到需要让不兼容的类或接口协同工作的问题。适配器模式&#xff08;Adapter Pattern&#xff09;是一种结构型设计模式&#xff0c;通过提供一个包装器对象&#xff0c;…...

RuoYi-Vue若依框架-后端设置不登陆访问(白名单)

找到SecurityConfig类 确认自己的需求 /*** anyRequest | 匹配所有请求路径* access | SpringEl表达式结果为true时可以访问* anonymous | 匿名可以访问* denyAll | 用户不能访问* fullyAuthenticated | 用户完全认证可…...

C语言初阶小练习2(三子棋小游戏的实现代码)

这是C语言小游戏三子棋的代码实现 test.c文件是用来测试的部分 game.h文件是用来声明我们说写出的函数 game.c文件是用来编写我们的功能实现函数部分 1.test.c #define _CRT_SECURE_NO_WARNINGS 1 #include"game.h" void menu() {printf("***************…...

金融行业合同管理如何利用AI技术进行风险预警?

2024年以来&#xff0c;金融行业的发展主线被锚定&#xff0c;强调了防风险的基调&#xff0c;尤其是系统性风险的防范。金融工作的重点在于实现六个强大&#xff1a;强大的货币、强大的中央银行、强大的金融机构、强大的国际金融中心、强大的金融监管、强大的金融人才队伍。这…...

世界数字农业盛宴与技术探索,25年3月聚焦世界灌溉科技大会

由中国农业节水和农村供水技术协会、中国农垦节水农业产业技术联盟、北京物联网智能技术应用协会、振威国际会展集团主办的“世界灌溉科技大会”、“第11届北京国际数字农业与灌溉技术博览会”&#xff0c;定于2025年3月31日至4月2日在北京国家会议中心举办。 作为世界三大灌溉…...

二百六十九、Kettle——ClickHouse清洗ODS层原始数据增量导入到DWD层表中

一、目的 清洗ClickHouse的ODS层原始数据&#xff0c;增量导入到DWD层表中 二、实施步骤 2.1 newtime select( select create_time from hurys_jw.dwd_statistics order by create_time desc limit 1) as create_time 2.2 替换NULL值 2.3 clickhouse输入 2.4 字段选择 2.5 …...

浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)

✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义&#xff08;Task Definition&…...

可靠性+灵活性:电力载波技术在楼宇自控中的核心价值

可靠性灵活性&#xff1a;电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中&#xff0c;电力载波技术&#xff08;PLC&#xff09;凭借其独特的优势&#xff0c;正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据&#xff0c;无需额外布…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略

本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装&#xff1b;只需暴露 19530&#xff08;gRPC&#xff09;与 9091&#xff08;HTTP/WebUI&#xff09;两个端口&#xff0c;即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...

Cinnamon修改面板小工具图标

Cinnamon开始菜单-CSDN博客 设置模块都是做好的&#xff0c;比GNOME简单得多&#xff01; 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...

跨链模式:多链互操作架构与性能扩展方案

跨链模式&#xff1a;多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈&#xff1a;模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展&#xff08;H2Cross架构&#xff09;&#xff1a; 适配层&#xf…...

Qt Http Server模块功能及架构

Qt Http Server 是 Qt 6.0 中引入的一个新模块&#xff0c;它提供了一个轻量级的 HTTP 服务器实现&#xff0c;主要用于构建基于 HTTP 的应用程序和服务。 功能介绍&#xff1a; 主要功能 HTTP服务器功能&#xff1a; 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...

Nginx server_name 配置说明

Nginx 是一个高性能的反向代理和负载均衡服务器&#xff0c;其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机&#xff08;Virtual Host&#xff09;。 1. 简介 Nginx 使用 server_name 指令来确定…...

【JavaSE】绘图与事件入门学习笔记

-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角&#xff0c;以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向&#xff0c;距离坐标原点x个像素;第二个是y坐标&#xff0c;表示当前位置为垂直方向&#xff0c;距离坐标原点y个像素。 坐标体系-像素 …...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容

目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法&#xff0c;当前调用一个医疗行业的AI识别算法后返回…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...