spdlog源码学习

前言

spdlog是一个跨平台c++ 的开源日志库 ,可以head only 使用,包含部分modern c++ 语法,
更是兼容了c++20 format,支持异步和格式化输出,通俗易懂,适合阅读。

源码下载

here

用法

直接贴上了 example.cpp 的代码

//
// Copyright(c) 2015 Gabi Melman.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)// spdlog usage example
#include <cstdio>
#include <chrono>
void load_levels_example();
void stdout_logger_example();
void basic_example();
void rotating_example();
void daily_example();
void callback_example();
void async_example();
void binary_example();
void vector_example();
void stopwatch_example();
void trace_example();
void multi_sink_example();
void user_defined_example();
void err_handler_example();
void syslog_example();
void udp_example();
void custom_flags_example();
void file_events_example();
void replace_default_logger_example();
#include "spdlog/spdlog.h"
#include "spdlog/cfg/env.h"   // support for loading levels from the environment variable
#include "spdlog/fmt/ostr.h"  // support for user defined types
int main(int, char *[]) {// Log levels can be loaded from argv/env using "SPDLOG_LEVEL"load_levels_example();   //可以从环境变量获取日志等级//不同日志等级的输出   可以了解一下format的用法spdlog::info("Welcome to spdlog version {}.{}.{}  !", SPDLOG_VER_MAJOR, SPDLOG_VER_MINOR,SPDLOG_VER_PATCH);spdlog::warn("Easy padding in numbers like {:08d}", 12);spdlog::critical("Support for int: {0:d};  hex: {0:x};  oct: {0:o}; bin: {0:b}", 42);spdlog::info("Support for floats {:03.2f}", 1.23456);spdlog::info("Positional args are {1} {0}..", "too", "supported");spdlog::info("{:>8} aligned, {:<8} aligned", "right", "left");// Runtime log levelsspdlog::set_level(spdlog::level::info);  // Set global log level to infospdlog::debug("This message should not be displayed!");spdlog::set_level(spdlog::level::trace);  // Set specific logger's log levelspdlog::debug("This message should be displayed..");// Customize msg format for all loggersspdlog::set_pattern("[%H:%M:%S %z] [%^%L%$] [thread %t] %v");spdlog::info("This an info message with custom format");spdlog::set_pattern("%+");  // back to default formatspdlog::set_level(spdlog::level::info);// Backtrace support// Loggers can store in a ring buffer all messages (including debug/trace) for later inspection.// When needed, call dump_backtrace() to see what happened:spdlog::enable_backtrace(10);  // create ring buffer with capacity of 10  messagesfor (int i = 0; i < 100; i++) {spdlog::debug("Backtrace message {}", i);  // not logged..}// e.g. if some error happened:spdlog::dump_backtrace();  // log them now!try {stdout_logger_example();basic_example();rotating_example();daily_example();callback_example();async_example();binary_example();vector_example();multi_sink_example();user_defined_example();err_handler_example();trace_example();stopwatch_example();udp_example();custom_flags_example();file_events_example();replace_default_logger_example();// Flush all *registered* loggers using a worker thread every 3 seconds.// note: registered loggers *must* be thread safe for this to work correctly!spdlog::flush_every(std::chrono::seconds(3));// Apply some function on all registered loggersspdlog::apply_all([&](std::shared_ptr<spdlog::logger> l) { l->info("End of example."); });// Release all spdlog resources, and drop all loggers in the registry.// This is optional (only mandatory if using windows + async log).spdlog::shutdown();}// Exceptions will only be thrown upon failed logger or sink construction (not during logging).catch (const spdlog::spdlog_ex &ex) {std::printf("Log initialization failed: %s\n", ex.what());return 1;}
}#include "spdlog/sinks/stdout_color_sinks.h"
// or #include "spdlog/sinks/stdout_sinks.h" if no colors needed.
void stdout_logger_example() {// Create color multi threaded logger.auto console = spdlog::stdout_color_mt("console");// or for stderr:// auto console = spdlog::stderr_color_mt("error-logger");
}#include "spdlog/sinks/basic_file_sink.h"
void basic_example() {// Create basic file logger (not rotated).auto my_logger = spdlog::basic_logger_mt("file_logger", "logs/basic-log.txt", true);
}#include "spdlog/sinks/rotating_file_sink.h"
void rotating_example() {// Create a file rotating logger with 5mb size max and 3 rotated files.auto rotating_logger =spdlog::rotating_logger_mt("some_logger_name", "logs/rotating.txt", 1048576 * 5, 3);
}#include "spdlog/sinks/daily_file_sink.h"
void daily_example() {// Create a daily logger - a new file is created every day on 2:30am.auto daily_logger = spdlog::daily_logger_mt("daily_logger", "logs/daily.txt", 2, 30);
}#include "spdlog/sinks/callback_sink.h"
void callback_example() {// Create the loggerauto logger = spdlog::callback_logger_mt("custom_callback_logger",[](const spdlog::details::log_msg & /*msg*/) {// do what you need to do with msg});
}#include "spdlog/cfg/env.h"
void load_levels_example() {// Set the log level to "info" and mylogger to "trace":// SPDLOG_LEVEL=info,mylogger=trace && ./examplespdlog::cfg::load_env_levels();// or from command line:// ./example SPDLOG_LEVEL=info,mylogger=trace// #include "spdlog/cfg/argv.h" // for loading levels from argv// spdlog::cfg::load_argv_levels(args, argv);
}#include "spdlog/async.h"
void async_example() {// Default thread pool settings can be modified *before* creating the async logger:// spdlog::init_thread_pool(32768, 1); // queue with max 32k items 1 backing thread.auto async_file =spdlog::basic_logger_mt<spdlog::async_factory>("async_file_logger", "logs/async_log.txt");// alternatively:// auto async_file =// spdlog::create_async<spdlog::sinks::basic_file_sink_mt>("async_file_logger",// "logs/async_log.txt");for (int i = 1; i < 101; ++i) {async_file->info("Async message #{}", i);}
}// Log binary data as hex.
// Many types of std::container<char> types can be used.
// Iterator ranges are supported too.
// Format flags:
// {:X} - print in uppercase.
// {:s} - don't separate each byte with space.
// {:p} - don't print the position on each line start.
// {:n} - don't split the output to lines.#if !defined SPDLOG_USE_STD_FORMAT || defined(_MSC_VER)#include "spdlog/fmt/bin_to_hex.h"
void binary_example() {std::vector<char> buf;for (int i = 0; i < 80; i++) {buf.push_back(static_cast<char>(i & 0xff));}spdlog::info("Binary example: {}", spdlog::to_hex(buf));spdlog::info("Another binary example:{:n}",spdlog::to_hex(std::begin(buf), std::begin(buf) + 10));// more examples:// logger->info("uppercase: {:X}", spdlog::to_hex(buf));// logger->info("uppercase, no delimiters: {:Xs}", spdlog::to_hex(buf));// logger->info("uppercase, no delimiters, no position info: {:Xsp}", spdlog::to_hex(buf));// logger->info("hexdump style: {:a}", spdlog::to_hex(buf));// logger->info("hexdump style, 20 chars per line {:a}", spdlog::to_hex(buf, 20));
}
#else
void binary_example() {// not supported with std::format yet
}
#endif// Log a vector of numbers
#ifndef SPDLOG_USE_STD_FORMAT#include "spdlog/fmt/ranges.h"
void vector_example() {std::vector<int> vec = {1, 2, 3};spdlog::info("Vector example: {}", vec);
}#else
void vector_example() {}
#endif// ! DSPDLOG_USE_STD_FORMAT// Compile time log levels.
// define SPDLOG_ACTIVE_LEVEL to required level (e.g. SPDLOG_LEVEL_TRACE)
void trace_example() {// trace from default loggerSPDLOG_TRACE("Some trace message.. {} ,{}", 1, 3.23);// debug from default loggerSPDLOG_DEBUG("Some debug message.. {} ,{}", 1, 3.23);// trace from logger objectauto logger = spdlog::get("file_logger");SPDLOG_LOGGER_TRACE(logger, "another trace message");
}// stopwatch example
#include "spdlog/stopwatch.h"
#include <thread>
void stopwatch_example() {spdlog::stopwatch sw;std::this_thread::sleep_for(std::chrono::milliseconds(123));spdlog::info("Stopwatch: {} seconds", sw);
}#include "spdlog/sinks/udp_sink.h"
void udp_example() {spdlog::sinks::udp_sink_config cfg("127.0.0.1", 11091);auto my_logger = spdlog::udp_logger_mt("udplog", cfg);my_logger->set_level(spdlog::level::debug);my_logger->info("hello world");
}// A logger with multiple sinks (stdout and file) - each with a different format and log level.
void multi_sink_example() {auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();console_sink->set_level(spdlog::level::warn);console_sink->set_pattern("[multi_sink_example] [%^%l%$] %v");auto file_sink =std::make_shared<spdlog::sinks::basic_file_sink_mt>("logs/multisink.txt", true);file_sink->set_level(spdlog::level::trace);spdlog::logger logger("multi_sink", {console_sink, file_sink});logger.set_level(spdlog::level::debug);logger.warn("this should appear in both console and file");logger.info("this message should not appear in the console, only in the file");
}// User defined types logging
struct my_type {int i = 0;explicit my_type(int i): i(i){};
};#ifndef SPDLOG_USE_STD_FORMAT  // when using fmtlib
template <>
struct fmt::formatter<my_type> : fmt::formatter<std::string> {auto format(my_type my, format_context &ctx) -> decltype(ctx.out()) {return fmt::format_to(ctx.out(), "[my_type i={}]", my.i);}
};#else  // when using std::format
template <>
struct std::formatter<my_type> : std::formatter<std::string> {auto format(my_type my, format_context &ctx) const -> decltype(ctx.out()) {return format_to(ctx.out(), "[my_type i={}]", my.i);}
};
#endifvoid user_defined_example() { spdlog::info("user defined type: {}", my_type(14)); }// Custom error handler. Will be triggered on log failure.
void err_handler_example() {// can be set globally or per logger(logger->set_error_handler(..))spdlog::set_error_handler([](const std::string &msg) {printf("*** Custom log error handler: %s ***\n", msg.c_str());});
}// syslog example (linux/osx/freebsd)
#ifndef _WIN32#include "spdlog/sinks/syslog_sink.h"
void syslog_example() {std::string ident = "spdlog-example";auto syslog_logger = spdlog::syslog_logger_mt("syslog", ident, LOG_PID);syslog_logger->warn("This is warning that will end up in syslog.");
}
#endif// Android example.
#if defined(__ANDROID__)#include "spdlog/sinks/android_sink.h"
void android_example() {std::string tag = "spdlog-android";auto android_logger = spdlog::android_logger_mt("android", tag);android_logger->critical("Use \"adb shell logcat\" to view this message.");
}
#endif// Log patterns can contain custom flags.
// this will add custom flag '%*' which will be bound to a <my_formatter_flag> instance
#include "spdlog/pattern_formatter.h"
class my_formatter_flag : public spdlog::custom_flag_formatter {
public:void format(const spdlog::details::log_msg &,const std::tm &,spdlog::memory_buf_t &dest) override {std::string some_txt = "custom-flag";dest.append(some_txt.data(), some_txt.data() + some_txt.size());}std::unique_ptr<custom_flag_formatter> clone() const override {return spdlog::details::make_unique<my_formatter_flag>();}
};void custom_flags_example() {using spdlog::details::make_unique;  // for pre c++14auto formatter = make_unique<spdlog::pattern_formatter>();formatter->add_flag<my_formatter_flag>('*').set_pattern("[%n] [%*] [%^%l%$] %v");// set the new formatter using spdlog::set_formatter(formatter) or// logger->set_formatter(formatter) spdlog::set_formatter(std::move(formatter));
}void file_events_example() {// pass the spdlog::file_event_handlers to file sinks for open/close log file notificationsspdlog::file_event_handlers handlers;handlers.before_open = [](spdlog::filename_t filename) {spdlog::info("Before opening {}", filename);};handlers.after_open = [](spdlog::filename_t filename, std::FILE *fstream) {spdlog::info("After opening {}", filename);fputs("After opening\n", fstream);};handlers.before_close = [](spdlog::filename_t filename, std::FILE *fstream) {spdlog::info("Before closing {}", filename);fputs("Before closing\n", fstream);};handlers.after_close = [](spdlog::filename_t filename) {spdlog::info("After closing {}", filename);};auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("logs/events-sample.txt",true, handlers);spdlog::logger my_logger("some_logger", file_sink);my_logger.info("Some log line");
}void replace_default_logger_example() {// store the old logger so we don't break other examples.auto old_logger = spdlog::default_logger();auto new_logger =spdlog::basic_logger_mt("new_default_logger", "logs/new-default-log.txt", true);spdlog::set_default_logger(new_logger);spdlog::set_level(spdlog::level::info);spdlog::debug("This message should not be displayed!");spdlog::set_level(spdlog::level::trace);spdlog::debug("This message should be displayed..");spdlog::set_default_logger(old_logger);
}

details

 ·  整体代码也是尽量使用 inline· 我在想是不是 可以尽量使用constexpr做到 更快的运行使其·

circular_q.h

最底层是维护了一个用vector实现的队列 class circular_q 仅支持移动
pushback的时候 如果满了的话 对头会向前移动

    void push_back(T &&item) {if (max_items_ > 0) {v_[tail_] = std::move(item);tail_ = (tail_ + 1) % max_items_;//注意如果满了 可能会导致队头的消息靠后输出if (tail_ == head_)  // overrun last item if full{head_ = (head_ + 1) % max_items_;++overrun_counter_;}}}

at接口做了检查 std::vector 也做了

 const T &at(size_t i) const {assert(i < size());return v_[(head_ + i) % max_items_];}

注意获取size的时候需要判断一下

    size_t size() const {if (tail_ >= head_) {return tail_ - head_;} else {return max_items_ - (head_ - tail_);}}

backtracer-inl.h

大概就是日志追溯 封装了有锁的环形队列

使用原子变量 默认是flase 就是不开启日志追溯

  std::atomic<bool> enabled_{false};

console_globals.h

null_mutex 是一个锁的空实现 主要是为了保证接口统一

#include <mutex>
#include <spdlog/details/null_mutex.h>namespace spdlog {
namespace details {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;}
};
}  // namespace details
} 

file_helper.h

封装了文件操作

fmt_helper.h

顾名思义

log_msg_buffer-inl.h

都是对视图的操作 保证了高效性 c17引入了 string_view c20 也引入了span 不仅安全通常来时还是还都是高效的

   
class SPDLOG_API log_msg_buffer : public log_msg {memory_buf_t buffer;//使用alloctor 管理内存 大小为250bytesvoid update_string_views();public:log_msg_buffer() = default;explicit log_msg_buffer(const log_msg &orig_msg);log_msg_buffer(const log_msg_buffer &other);log_msg_buffer(log_msg_buffer &&other) SPDLOG_NOEXCEPT;log_msg_buffer &operator=(const log_msg_buffer &other);log_msg_buffer &operator=(log_msg_buffer &&other) SPDLOG_NOEXCEPT;
};

log_msg_inl.h

这里主要是是对日志消息的封装 名字 内容 级别 soc 核心内容
c++20 引入了 source_location 会更加方便 这里主要是为了保证兼容性

struct SPDLOG_API log_msg {log_msg() = default;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(const log_msg &other) = default;log_msg &operator=(const log_msg &other) = default;string_view_t logger_name;  //日志名字level::level_enum level{level::off};//枚举级别 log_clock::time_point time;size_t thread_id{0};// wrapping the formatted text with color (updated by pattern_formatter).mutable size_t color_range_start{0};mutable size_t color_range_end{0};source_loc source;  string_view_t payload; //内容
};

mpmc_blocking_q.h

是多生产者多消费者队列 封装了循环队列 支持三种push

1.第一种就是最正常的 只有队列有位置 才push // Block until message can be enqueued
2.第二种 会直接push 可以会直接覆盖旧数据/ Discard oldest message in the queue if full when trying to add new item.
3.如果没有满 push 满了就会丢弃 // Discard new message if the queue is full when trying to add new item.

内部维护了 discard_counter 原子变量 用来统计抛弃了多少日志

  void enqueue(T &&item) {{std::unique_lock<std::mutex> lock(queue_mutex_);pop_cv_.wait(lock, [this] { return !this->q_.full(); });q_.push_back(std::move(item));}push_cv_.notify_one();}// enqueue immediately. overrun oldest message in the queue if no room left.void enqueue_nowait(T &&item) {{std::unique_lock<std::mutex> lock(queue_mutex_);q_.push_back(std::move(item));}push_cv_.notify_one();}void enqueue_if_have_room(T &&item) {bool pushed = false;{std::unique_lock<std::mutex> lock(queue_mutex_);if (!q_.full()) {q_.push_back(std::move(item));pushed = true;}}if (pushed) {push_cv_.notify_one();} else {++discard_counter_;}}

一个是有timeout 一个没有

    bool dequeue_for(T &popped_item, std::chrono::milliseconds wait_duration) {{std::unique_lock<std::mutex> lock(queue_mutex_);if (!push_cv_.wait_for(lock, wait_duration, [this] { return !this->q_.empty(); })) {return false;}popped_item = std::move(q_.front());q_.pop_front();}pop_cv_.notify_one();return true;}// 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();}

null_mutex.h

空实现.

os_inl.h

操作系统接口

periodic_worker.h

主要是为了辅助 日志刷新到目标端

class SPDLOG_API periodic_worker {
public:template <typename Rep, typename Period>periodic_worker(const std::function<void()> &callback_fun,std::chrono::duration<Rep, Period> interval) {// 根据传入的 intval  标记active        active_ = (interval > std::chrono::duration<Rep, Period>::zero());if (!active_) {return;}worker_thread_ = std::thread([this, callback_fun, interval]() {for (;;) {std::unique_lock<std::mutex> lock(this->mutex_);if (this->cv_.wait_for(lock, interval, [this] { return !this->active_; })) {return;  // active_ == false, so exit this thread}callback_fun();}});}periodic_worker(const periodic_worker &) = delete;periodic_worker &operator=(const periodic_worker &) = delete;// stop the worker thread and join it~periodic_worker();private:bool active_;std::thread worker_thread_;std::mutex mutex_;std::condition_variable cv_;
};

registry-inl.h

管理全局日志的注册器

日志名称和对应的日志等级的映射

    using log_levels = std::unordered_map<std::string, level::level_enum>;

设置默认的日志 spdlog:: 进行操作的时候就会对该日志进行操作

   void set_default_logger(std::shared_ptr<logger> new_default_logger);      

线程池的get set方法

    //设置线程池void set_tp(std::shared_ptr<thread_pool> tp);//得到线程池std::shared_ptr<thread_pool> get_tp();

设置全局formatter 和日志等级 flush等级 出错的回调

 void set_formatter(std::unique_ptr<formatter> formatter);void set_level(level::level_enum log_level);void flush_on(level::level_enum log_level); void set_error_handler(err_handler handler);

上一个类就会在这里用到 刷新日志的 unique指针

    template <typename Rep, typename Period>void flush_every(std::chrono::duration<Rep, Period> interval) {std::lock_guard<std::mutex> lock(flusher_mutex_);auto clbk = [this]() { this->flush_all(); };periodic_flusher_ = details::make_unique<periodic_worker>(clbk, interval);}

线程池的锁 是可重入的

    std::recursive_mutex &tp_mutex();

注册类是单例的

    static registry &instance();

synchronous_factory.h

异步日志工厂
需要 日志名 和 sink信息
生成 对sink 信息shared ptr

    
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;}
};

thread_pool-inl.h

封装了整个异步日志的核心操作

struct async_msg : log_msg_buffer {}

异步日志的sp

using async_logger_ptr = std::shared_ptr<spdlog::async_logger>;

msg枚举类

enum class async_msg_type { log, flush, terminate };

携带msg体

  async_msg(async_logger_ptr &&worker, async_msg_type the_type, const details::log_msg &m)

用于给日志后端通知的 构造函数 只传入了 msg枚举类型

    async_msg(async_logger_ptr &&worker, async_msg_type the_type)

两个日志前端的操作

void SPDLOG_INLINE thread_pool::post_log(async_logger_ptr &&worker_ptr,const details::log_msg &msg,async_overflow_policy overflow_policy) {async_msg async_m(std::move(worker_ptr), async_msg_type::log, msg);post_async_msg_(std::move(async_m), overflow_policy);
}void SPDLOG_INLINE thread_pool::post_flush(async_logger_ptr &&worker_ptr,async_overflow_policy overflow_policy) {         post_async_msg_(async_msg(std::move(worker_ptr), async_msg_type::flush), overflow_policy);
}

根据传入参数的不同进行不同的 生产操作

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));}
}

线程会一直执行 这个操作 根据前端传入的不同类型进行不同的操作

void SPDLOG_INLINE thread_pool::worker_loop_() {while (process_next_msg_()) {}
}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_();return true;}case async_msg_type::terminate: {return false;}default: {assert(false);}}return true;
}

析构操作

SPDLOG_INLINE thread_pool::~thread_pool() {SPDLOG_TRY {//向日志前端发送终止信号for (size_t i = 0; i < threads_.size(); i++) {post_async_msg_(async_msg(async_msg_type::terminate), async_overflow_policy::block);}//等待每一个线程join  for (auto &t : threads_) {t.join();}}SPDLOG_CATCH_STD
}

构造函数 可以在线程开始的时候执行回调函数 即使没有 函数 也会委托构造到该函数

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) {// 线程的数量不能大于1000if (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();});}
}

end details

---------------------------------------------------------

sink

- 支持非常多的类型 tcp  mongodb   msvc   输出流sink   rotate sink   ......等等  简单看一下吧  主要就是日志后端的操作

sink.h

这些操作都很显然 因为日志后端 就需要 说明输出到哪 哪种格式 该不该输出 就是这些信息

class SPDLOG_API sink {
public://虚函数需要被重写virtual ~sink() = default;virtual void log(const details::log_msg &msg) = 0;virtual void flush() = 0;virtual void set_pattern(const std::string &pattern) = 0;virtual void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) = 0;void set_level(level::level_enum log_level);level::level_enum level() const;bool should_log(level::level_enum msg_level) const;protected:// sink log level - default is alllevel_t level_{level::trace};
};

base_sink.h

后面会有类继承 这个base类 分为有锁和无锁 因为单线程的话 我们就没有必要加锁 就可使用空实现的锁

  • 心得:final virtual override 标清楚 有助于代码理解 还能防止一些意料之外bug (见 effective modern c++ 某一章节)
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;void flush() final;void set_pattern(const std::string &pattern) final;void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) final;protected:// sink formatterstd::unique_ptr<spdlog::formatter> formatter_;Mutex mutex_;//纯虚函数 继承该类主要重新写的部分virtual void sink_it_(const details::log_msg &msg) = 0;virtual void flush_() = 0;//这里为虚函数 子类可以重新写 virtual void set_pattern_(const std::string &pattern);virtual void set_formatter_(std::unique_ptr<spdlog::formatter> sink_formatter);
};

basic_file_sink.h

一种有锁一种没有锁

using basic_file_sink_mt = basic_file_sink<std::mutex>;
using basic_file_sink_st = basic_file_sink<details::null_mutex>;

工厂方法
Factory::template create 是一个依赖于模板参数的静态成员函数,它调用了模板类 Factory 的 create 函数。因为 create 是一个模板成员函数,编译器需要通过 ::template 来显式指示模板参数

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);
}

file_sink的后端操作

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();
}
  • 其他sink不贴代码了 有兴趣的可以看看源码

  • stdout_sink 继承的是sink 没有继承base_sink

  • 这里也有一个很有意思的问题 系统调用的 原子性 可以看看这个

  • 拿write 来说 大概就是 如果在一个进程里面 write分为三部分 get_pos wirte_info update_pos

  • 同一个进程(子进程或者子线程)公用fd的时候 会有file锁保证线程安全 但是多进程的话 没有对inode 的 锁就不能保证线程安全了 需要添加APPEND标志才可以

  • 可以看看man 手册 3.14版本修复的BUG

end sink

----------------------------------------------------------------

common-inl.h

封装了一些常用的内联函数(inline functions)、宏定义(macros)、通用的数据结构或者辅助性的工具函数等。这些功能可能被项目中的多个文件共同引用,因此将其封装在一个公共的头文件中可以方便地进行集中管理和共享使用

  • 在下面加注释了
using log_clock = std::chrono::system_clock;
//sink的sp
using sink_ptr = std::shared_ptr<sinks::sink>;
//sink list的sp
using sinks_init_list = std::initializer_list<sink_ptr>;
// 出现err的回调函数
using err_handler = std::function<void(const std::string &err_msg)>;using string_view_t = fmt::basic_string_view<char>;
//250字节  日志存储的关键
using memory_buf_t = fmt::basic_memory_buffer<char, 250>;template <typename... Args>
using format_string_t = fmt::format_string<Args...>;
//移除cv限定
template <class T>
using remove_cvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
//原子变量的日志级别 因为可能运行时更改
//多种日志等级
using level_t = std::atomic<int>;
#define SPDLOG_LEVEL_TRACE 0
#define SPDLOG_LEVEL_DEBUG 1
#define SPDLOG_LEVEL_INFO 2
#define SPDLOG_LEVEL_WARN 3
#define SPDLOG_LEVEL_ERROR 4
#define SPDLOG_LEVEL_CRITICAL 5
#define SPDLOG_LEVEL_OFF 6// Log level enum 日志等级枚举
namespace level {
enum level_enum : int {trace = SPDLOG_LEVEL_TRACE,debug = SPDLOG_LEVEL_DEBUG,info = SPDLOG_LEVEL_INFO,warn = SPDLOG_LEVEL_WARN,err = SPDLOG_LEVEL_ERROR,critical = SPDLOG_LEVEL_CRITICAL,off = SPDLOG_LEVEL_OFF,n_levels
};enum class pattern_time_type {local,  // log localtimeutc     // log utc
};
//c++ 20已经有 更好的替代了
struct source_loc {SPDLOG_CONSTEXPR source_loc() = default;SPDLOG_CONSTEXPR source_loc(const char *filename_in, int line_in, const char *funcname_in): filename{filename_in},line{line_in},funcname{funcname_in} {}SPDLOG_CONSTEXPR bool empty() const SPDLOG_NOEXCEPT { return line == 0; }const char *filename{nullptr};int line{0};const char *funcname{nullptr};
};
//对文件进行操作的时候的回调
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;
};
//小小的优化 就是说 如果相同的类型就不同强制转换了 
template <typename T, typename U, enable_if_t<!std::is_same<T, U>::value, int> = 0>
constexpr T conditional_static_cast(U value) {return static_cast<T>(value);
}template <typename T, typename U, enable_if_t<std::is_same<T, U>::value, int> = 0>
constexpr T conditional_static_cast(U value) {return value;
}

logger.h

主要是同步日志

  • 所有的操作都会转发到 这个函数上 即使没有 loc信息 也是一个空实现
    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)...);}
    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();//如果不开启日志追随 并且 小于日志等级 returnif (!log_enabled && !traceback_enabled) {return;}SPDLOG_TRY {memory_buf_t buf;
#ifdef SPDLOG_USE_STD_FORMAT//格式化 fmt库fmt_lib::vformat_to(std::back_inserter(buf), fmt, fmt_lib::make_format_args(args...));
#elsefmt::vformat_to(fmt::appender(buf), fmt, fmt::make_format_args(args...));
#endif//转化为log msgdetails::log_msg log_msg(loc, name_, lvl, string_view_t(buf.data(), buf.size()));//准备输出log_it_(log_msg, log_enabled, traceback_enabled);}SPDLOG_LOGGER_CATCH(loc)}

进行 sink it操作

SPDLOG_INLINE 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);}}

log接口会进行 每个sink 的 sink it 操作 输出到目的地

SPDLOG_INLINE void logger::sink_it_(const details::log_msg &msg) {for (auto &sink : sinks_) {if (sink->should_log(msg.level)) {SPDLOG_TRY { sink->log(msg); }SPDLOG_LOGGER_CATCH(msg.source)}}if (should_flush_(msg)) {flush_();}
}

async.h

异步工厂 三要素

  • 拿到thread pool 因为可能进行多次操作 所以使用了可重入锁
  • 拿到sink 输出端
  • 进行注册
struct async_factory_impl {template <typename Sink, typename... SinkArgs>static std::shared_ptr<async_logger> create(std::string logger_name, SinkArgs &&...args) {auto &registry_inst = details::registry::instance();// create global thread pool if not already exists..//拿到threadoppl的锁auto &mutex = registry_inst.tp_mutex();std::lock_guard<std::recursive_mutex> tp_lock(mutex);auto tp = registry_inst.get_tp();if (tp == nullptr) {//日志默认大小为 8192  并且是单线程的 tp = std::make_shared<details::thread_pool>(details::default_async_q_size, 1U);registry_inst.set_tp(tp);}auto sink = std::make_shared<Sink>(std::forward<SinkArgs>(args)...);auto new_logger = std::make_shared<async_logger>(std::move(logger_name), std::move(sink),std::move(tp), OverflowPolicy);registry_inst.initialize_logger(new_logger);return new_logger;}
};

一种是阻塞的 一种是非阻塞的

using async_factory = async_factory_impl<async_overflow_policy::block>;
using async_factory_nonblock = async_factory_impl<async_overflow_policy::overrun_oldest>;
template <typename Sink, typename... SinkArgs>
inline std::shared_ptr<spdlog::logger> create_async(std::string logger_name,SinkArgs &&...sink_args) {return async_factory::create<Sink>(std::move(logger_name),std::forward<SinkArgs>(sink_args)...);
}template <typename Sink, typename... SinkArgs>
inline std::shared_ptr<spdlog::logger> create_async_nb(std::string logger_name,SinkArgs &&...sink_args) {return async_factory_nonblock::create<Sink>(std::move(logger_name),std::forward<SinkArgs>(sink_args)...);
}

默认是单线程 但是这里可以设置
set global thread pool.

//没有回调函数也会用空实现转发到这个接口
inline void init_thread_pool(size_t q_size,size_t thread_count,std::function<void()> on_thread_start,std::function<void()> on_thread_stop) {auto tp = std::make_shared<details::thread_pool>(q_size, thread_count, on_thread_start,on_thread_stop);details::registry::instance().set_tp(std::move(tp));
}

threadpool 可能被多人拥有 所以是shared ptr

inline std::shared_ptr<spdlog::details::thread_pool> thread_pool() {return details::registry::instance().get_tp();
}

async_logger.h

支持sink list 构造和单独 sink构造

SPDLOG_INLINE spdlog::async_logger::async_logger(std::string logger_name,sinks_init_list sinks_list,std::weak_ptr<details::thread_pool> tp,async_overflow_policy overflow_policy): async_logger(std::move(logger_name),sinks_list.begin(),sinks_list.end(),std::move(tp),overflow_policy) {}
//委托到上面的构造函数进行构造
SPDLOG_INLINE spdlog::async_logger::async_logger(std::string logger_name,sink_ptr single_sink,std::weak_ptr<details::thread_pool> tp,async_overflow_policy overflow_policy): async_logger(std::move(logger_name), {std::move(single_sink)}, std::move(tp), overflow_policy) {}

把消息交给线程池处理

SPDLOG_INLINE void spdlog::async_logger::sink_it_(const details::log_msg &msg){//lock 操作返回强 引用 多线程安全SPDLOG_TRY{if (auto pool_ptr = thread_pool_.lock()){pool_ptr->post_log(shared_from_this(), msg, overflow_policy_);
}
SPDLOG_INLINE void spdlog::async_logger::flush_(){SPDLOG_TRY{if (auto pool_ptr = thread_pool_.lock()){pool_ptr->post_flush(shared_from_this(), overflow_policy_);
}

后端操作 输出 到sink

SPDLOG_INLINE void spdlog::async_logger::backend_sink_it_(const details::log_msg &msg) {for (auto &sink : sinks_) {if (sink->should_log(msg.level)) {SPDLOG_TRY { sink->log(msg); }SPDLOG_LOGGER_CATCH(msg.source)}}if (should_flush_(msg)) {backend_flush_();}
}SPDLOG_INLINE void spdlog::async_logger::backend_flush_() {for (auto &sink : sinks_) {SPDLOG_TRY { sink->flush(); }SPDLOG_LOGGER_CATCH(source_loc())}
}

stopwatch.h

这个设计的很好值得学习
初始化会获得当前时间 必须要有一个特化类 这里继承了formatter< double > 就不用重新写parse函数
info输出的时候就会调用elapsed() 减去初始时间 用于记时间

#include "spdlog/stopwatch.h"
#include <thread>
void stopwatch_example() {spdlog::stopwatch sw;std::this_thread::sleep_for(std::chrono::milliseconds(123));spdlog::info("Stopwatch: {} seconds", sw);
}
class stopwatch {using clock = std::chrono::steady_clock;std::chrono::time_point<clock> start_tp_;public:stopwatch(): start_tp_{clock::now()} {}std::chrono::duration<double> elapsed() const {return std::chrono::duration<double>(clock::now() - start_tp_);}void reset() { start_tp_ = clock::now(); }
};
}  // namespace spdlog// Support for fmt formatting  (e.g. "{:012.9}" or just "{}")
namespace
#ifdef SPDLOG_USE_STD_FORMATstd
#elsefmt
#endif
{template <>
struct formatter<spdlog::stopwatch> : formatter<double> {template <typename FormatContext>auto format(const spdlog::stopwatch &sw, FormatContext &ctx) const -> decltype(ctx.out()) {return formatter<double>::format(sw.elapsed().count(), ctx);}
};

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

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

相关文章

四种方式实现[选择性注入SpringBoot接口的多实现类]

最近在项目中遇到两种情况&#xff0c;准备写个博客记录一下。 情况说明&#xff1a;Service层一个接口是否可以存在多个具体实现&#xff0c;此时应该如何调用Service&#xff08;的具体实现&#xff09;&#xff1f; 其实之前的项目中也遇到过这种情况&#xff0c;只不过我采…

嵌入式培训机构四个月实训课程笔记(完整版)-Linux系统编程第二天-Linux开发板外设开发(物联技术666)

更多配套资料CSDN地址:点赞+关注,功德无量。更多配套资料,欢迎私信。 物联技术666_嵌入式C语言开发,嵌入式硬件,嵌入式培训笔记-CSDN博客物联技术666擅长嵌入式C语言开发,嵌入式硬件,嵌入式培训笔记,等方面的知识,物联技术666关注机器学习,arm开发,物联网,嵌入式硬件,单片机…

操作系统课程设计——文件管理系统(C语言版)

操作系统系列文章 http://t.csdnimg.cn/7XAnU 文章目录 实验一、进程的创建与撤销&#xff1a;http://t.csdnimg.cn/po4V0 实验二、银行家算法&#xff1a;http://t.csdnimg.cn/O5zoF 目录 操作系统系列文章 文章目录 文件管理 一、目的 二、设计内容 三、 设计要求 …

Excel·VBA按指定顺序排序函数

与之前写过的《ExcelVBA数组冒泡排序函数》不同&#xff0c;不是按照数值大小的升序/降序对数组进行排序&#xff0c;而是按照指定数组的顺序&#xff0c;对另一个数组进行排序 以下代码调用了《ExcelVBA数组冒泡排序函数》bubble_sort_arr函数&#xff08;如需使用代码需复制…

如何在群晖7.2中运行WPS Office镜像容器并使用固定地址公网访问

文章目录 1. 拉取WPS Office镜像2. 运行WPS Office镜像容器3. 本地访问WPS Office4. 群晖安装Cpolar5. 配置WPS Office远程地址6. 远程访问WPS Office小结 7. 固定公网地址 wps-office是一个在Linux服务器上部署WPS Office的镜像。它基于WPS Office的Linux版本&#xff0c;通过…

LaTeX矩阵

在 LaTeX 中输入矩阵以及矩阵中增加公式。 LATEX 中 array 环境可以定义二维数组&#xff0c;具体需要定义列数&#xff0c;并用 \\ 换行&#xff0c;数组可作为一个公式块&#xff0c;在外套用 \left、\right 等定界符。 \mathbf{X} \left(\begin{array}{cccc}x_{11} &…

【AI】什么是大模型的偏见

目录 一、什么是大模型的偏见 二、偏见的危害 三、普通人可以做的一些偏见测试用例 1. 性别偏见测试&#xff1a; 2. 种族和民族偏见测试&#xff1a; 3. 职业偏见测试&#xff1a; 4. 年龄偏见测试&#xff1a; 5. 社会经济地位偏见测试&#xff1a; 6. 身体能力偏见…

Leetcode 3002. Maximum Size of a Set After Removals

Leetcode 3002. Maximum Size of a Set After Removals 1. 解题思路2. 代码实现3. 算法优化 题目链接&#xff1a;10037. Maximum Size of a Set After Removals 1. 解题思路 这一题的话我的思路就是分别以两个数组作为主数组&#xff0c;然后从中选择 n / 2 n/2 n/2个元素&…

接口限流方案

1.1 为什么要进行限流&#xff1f; 1.瞬时流量过高&#xff0c;服务被压垮&#xff1f; 2.恶意用户高频光顾&#xff0c;导致服务器宕机&#xff1f; 3.消息消费过快&#xff0c;导致数据库压力过大&#xff0c;性能下降甚至崩溃&#xff1f; 1.2 什么是限流 限流是对某一…

Zuul相关问题及到案(2024)

1、什么是Zuul&#xff1f;它在微服务架构中有什么作用&#xff1f; Zuul是Netflix开源的一种提供API网关服务的应用程序&#xff0c;它在微服务架构中扮演着流量的前门角色。主要功能包括以下几点&#xff1a; 路由转发&#xff1a;Zuul网关将外部请求转发到具体的微服务实例…

【Python常用函数】一文让你彻底掌握Python中的numpy.append函数

大数据时代的到来,使得很多工作都需要进行数据挖掘,从而发现更多有利的规律,或规避风险,或发现商业价值。而大数据分析的基础是学好编程语言。本文和你一起来探索Python中的append函数,让你以最短的时间明白这个函数的原理。也可以利用碎片化的时间巩固这个函数,让你在处…

高效管理文件方法:每4个文件前面加序号,4个文件后面又单独编号技巧

在日常工作中&#xff0c;文件管理是一项常见的任务。要更高效地管理文件&#xff0c;可以通过在每个文件前面加序号&#xff0c;并在每个序号对应的文件后面进行单独编号的方法来实现。这种方法有助于快速找到所需文件&#xff0c;也能提高工作效率。下面一起来看下云炫文件管…

2024出海潮,生态伙伴搭上华为HMS的“便车”?

作者 | 曾响铃 文 | 响铃说 回顾2023年&#xff0c;中国新能源车市场在加速内卷的同时&#xff0c;还诞生了一个 “超级物种”&#xff0c;那就是华为将车BU分拆。未来&#xff0c;华为智能汽车解决方案未来不仅会独立运营&#xff0c;还吸纳了庞大的盟友阵营&#xff0c;包括…

linux后台运行进程分类查看操作命令

例如需要查看所有运行的python程序进程&#xff1a; 执行的命令如下&#xff1a; ps -ef | grep python 解释&#xff1a; 在 UNIX 或类 UNIX 系统&#xff08;如 Linux&#xff09;中的作用是查找所有正在运行的与 Python 相关的进程。这个命令结合了两个常用的命令行工具…

R语言【base】——tempfile():返回一个字符串向量,这些字符串可以用作临时文件的名称

Package base version 4.2.0 Parameters tempfile(pattern "file", tmpdir tempdir(), fileext "") tempdir(check FALSE) 参数【pattern】&#xff1a;一个非空字符向量&#xff0c;给出名称的初始部分。 参数【tmpdir】&#xff1a;提供目录名称的…

修复HTTP动词篡改导致的认证旁路问题的方法

本文于2016年4月完成&#xff0c;发布在个人博客网站上。 诡异的问题 分析AppScan扫描报告的时候&#xff0c;发现报告里提示“HTTP动词篡改导致的认证旁路”&#xff0c;一个名字很长&#xff0c;很怪异的问题。咨询度娘没有获取到必要的信息&#xff0c;于是只好按照AppScan…

物理机与vm文件共享与传输的设置方法

今天跟各位小伙伴&#xff0c;分享一下物理机与vm虚拟机文件共享与传输的设置方法&#xff0c;以供大家参考&#xff01; 一、物理机与虚拟机文件共享设置方法 第一步&#xff1a;先关闭虚拟机&#xff08;客户机&#xff09; 第二步&#xff1a;选择编辑虚拟机设置 第三步&am…

Nacos和Eureka的全面对比

学习目标&#xff1a; 了解Nacos和Eureka的基本概念和特点。理解Nacos和Eureka在服务注册与发现、配置管理、服务路由、负载均衡等方面的区别。掌握Nacos和Eureka的部署和使用方法。比较Nacos和Eureka在性能、可靠性、扩展性等方面的优劣。理解Nacos和Eureka在微服务架构中的应…

leetcode经典【双指针】例题

删除有序数组中的重复项&#xff1a; https://leetcode.cn/problems/remove-duplicates-from-sorted-array/ 解题思路&#xff1a; 首先注意数组是有序的&#xff0c;那么重复的元素一定会相邻。 注: 要求删除重复元素&#xff0c;实际上就是将不重复的元素移到数组的左侧。 考…

【面试高频算法解析】算法练习2 回溯(Backtracking)

前言 本专栏旨在通过分类学习算法&#xff0c;使您能够牢固掌握不同算法的理论要点。通过策略性地练习精选的经典题目&#xff0c;帮助您深度理解每种算法&#xff0c;避免出现刷了很多算法题&#xff0c;还是一知半解的状态 专栏导航 二分查找回溯&#xff08;Backtracking&…