spdlog源码学习

2024-01-06 20:04
文章标签 源码 学习 spdlog

本文主要是介绍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

  • 同一个进程会有file 锁保证线程安全 但是多进程的话 没有对inode 的 锁就不能保证线程安全了 需要添加APPEND标志才可以

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

这篇关于spdlog源码学习的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/577449

相关文章

Java深度学习库DJL实现Python的NumPy方式

《Java深度学习库DJL实现Python的NumPy方式》本文介绍了DJL库的背景和基本功能,包括NDArray的创建、数学运算、数据获取和设置等,同时,还展示了如何使用NDArray进行数据预处理... 目录1 NDArray 的背景介绍1.1 架构2 JavaDJL使用2.1 安装DJL2.2 基本操

Go中sync.Once源码的深度讲解

《Go中sync.Once源码的深度讲解》sync.Once是Go语言标准库中的一个同步原语,用于确保某个操作只执行一次,本文将从源码出发为大家详细介绍一下sync.Once的具体使用,x希望对大家有... 目录概念简单示例源码解读总结概念sync.Once是Go语言标准库中的一个同步原语,用于确保某个操

Java汇编源码如何查看环境搭建

《Java汇编源码如何查看环境搭建》:本文主要介绍如何在IntelliJIDEA开发环境中搭建字节码和汇编环境,以便更好地进行代码调优和JVM学习,首先,介绍了如何配置IntelliJIDEA以方... 目录一、简介二、在IDEA开发环境中搭建汇编环境2.1 在IDEA中搭建字节码查看环境2.1.1 搭建步

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss