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 的调用流程如下:
- 获取一个 Logger 对象
- 使用该 Logger 对象记录一个日志消息,该消息包括日志级别、时间戳、线程 ID、文件名和行号等信息
- 将日志消息传递给 Formatter,将消息转换为特定格式
- 将格式化后的消息传递给 Async Logger
- 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.h
和xxx-inl.h
文件中,其中xxx.h只有函数和类的声明,而实现都以inline的方式写在了xxx-inl.h中。spdlog提供了header-only version
和compiled version
2种方式, 通过SPDLOG_HEADER_ONLY宏定义,可以调节.h文件中是否包含了其实现的代码,如果包含了那就是header-only version。如果不包含,那它就是compiled version
中普通的头文件。如果是compiled version
形式,则其源码中包含xxx-inl.h
实现就可以了。SPDLOG_COMPILED_LIB
和SPDLOG_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()
, 默认的日志对象,使用默认的日志信息格式,输出至 stdoutlogger->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(日志输出目标,如文件、控制台等)
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 类图
源码剖析
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 ®istry::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:是 Spdlog 最基本的组件,负责记录日志消息。在 Spdlog 中,一个 Logger 对象代表着一个日志记录器,应用程序可以使用 Logger 对象记录不同级别的日志消息Sinks:决定了日志消息的输出位置。在 Spdlog 中&…...

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

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

consumer 角度讲一下i2c外设
往期内容 I2C子系统专栏: I2C(IIC)协议讲解-CSDN博客SMBus 协议详解-CSDN博客I2C相关结构体讲解:i2c_adapter、i2c_algorithm、i2c_msg-CSDN博客内核提供的通用I2C设备驱动I2c-dev.c分析:注册篇内核提供的通用I2C设备驱动I2C-dev.…...

面试经典150题刷题记录
数组部分 1. 合并两个有序的子数组 —— 倒序双指针避免覆盖 88. 合并两个有序数组 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 到 nums1 中,使…...

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

开题答辩最怕被问什么?教你用ChatGPT轻松准备,稳拿高分!
AIPaperGPT,论文写作神器~ https://www.aipapergpt.com/ 开题答辩是学位论文写作过程中的重要环节,能帮助导师评估你的研究计划是否可行,并对后续写作起到指导作用。很多同学在面对导师提问时会感到紧张,因此提前准备好常见问题的…...

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

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

rust高级进阶总结
文章目录 前言1. Rust生命周期进阶一、不太聪明的生命周期检查(一)例子1(二)例子2 二、无界生命周期三、生命周期约束(HRTB)(一)语法及含义(二)综合例子 四、…...

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

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

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

05 go语言(golang) - 常量和条件语句
常量 在Go语言中,常量是使用 const 关键字定义的,并且一旦被赋值后,它们的值在程序运行期间不能改变。常量可以是字符、字符串、布尔或数值类型。 基本特性 不可修改:一旦一个常量被定义,它的值就不能被更新。编译时…...

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

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年以来,金融行业的发展主线被锚定,强调了防风险的基调,尤其是系统性风险的防范。金融工作的重点在于实现六个强大:强大的货币、强大的中央银行、强大的金融机构、强大的国际金融中心、强大的金融监管、强大的金融人才队伍。这…...

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

二百六十九、Kettle——ClickHouse清洗ODS层原始数据增量导入到DWD层表中
一、目的 清洗ClickHouse的ODS层原始数据,增量导入到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 …...

Maya---骨骼绑定
调节骨骼大小 回车键确认骨骼 FK子集跟父集走 IK子集不跟父集走 前视图中按shift键添加骨骼 清零、删除历史记录,创建新的物体...

携手并进,智驭教育!和鲸科技与智谱 AI 签署“101 数智领航计划”战略合作协议
近日,上海和今信息科技有限公司(以下简称“和鲸科技”)与北京智谱华章科技有限公司(以下简称“智谱 AI”)签署“101 数智领航计划”战略合作协议。双方将携手营造智能化学科教育与科研环境,提供多种大模型工…...

牛客周赛63
https://ac.nowcoder.com/acm/contest/91592 好数 简单的判断两位数,且十位等于个位 #include <bits/stdc.h> #define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); #define int long long using namespace std; using ll long long; using pii …...

git restore恢复删除文件
新版本 在 Git 2.23 版本之后,Git 引入了一个新的命令 git restore,用于简化文件恢复操作。可以用 git restore 来恢复误删除的文件。下面是详细的使用方法: 1. 恢复工作区中删除的文件(未提交) 如果文件已被删除&a…...

MacOS13虚拟机VMware Workstation Pro 16安装
资源 安装unlocker 安装虚拟机 低版本的还没有MacOS13选项,这也是我安装低版本虚拟机踩过的坑 找个教程安装就可以了 省略…自己去找找教程… 过程中我使用桥接是不行的,没有网络,后面重新下一步一步的选择默认的网络重装后就好了&am…...

docker 数据管理,数据持久化详解 一
docker镜像是分层设计的,镜像出只读,通过镜像启动的容器添加一层可读写的文件系统,用户写入的数据表都保存在这层中。 容器的数据分层目录 LowerDir:image 镜像层,即镜像本身,制度 UpperDir:容…...

【ios】使用TestFlight将app分发给测试人员(超详细)
我的环境: macos系统是Ventura 13.0 xcode是14.2(后面发现至少需要15版本的xcode才能上传app) 证书生成 可以通过xcode生成Distribution类型的证书,如果你已经有的话那就忽略,这个证书也是备案时所需的。 我是已…...

证件照小程序源码,前后端稳定运行
演示:证寸照制作 运行环境: Linux Nginx PHP >5.6 MySQL>5.6 安装步骤: 1.下载源码上传至你的服务器宝塔面板 2.直接添加站点选择源码目录,新建数据库 3.设置代码执行目录为/web 4.在浏览器中输入你的域名,会提示安装,填写…...

java白嫖同事的从身份证里面提取省市区地址详细信息的工具类代码
/*** author sunpeiyang* date 2024/10/21 16:35*/ Slf4j public class MiTaAddressExtractor {/*** 获取详细地址** param fullAddress 身份证完整地址*/public static String getDetailedAddress(String fullAddress) {String[] addressArrays spliceDetailedAddress(fullAd…...

计算机网络基本架构示例2
一、企业内部网络架构 在一个中型企业中,通常会有以下的网络架构: - 核心层:由高性能的核心交换机组成,负责快速转发大量数据。例如采用具有高带宽和冗余功能的三层交换机,确保整个网络的稳定运行。它连接着各个部门的…...