spdlog源码阅读 (2): sinks的创建和使用

2. sink创建

2.1 还是rotating_file_sink

我们仍然以rotating_file_sink为例来说明在spdlog中sink的创建过程。
在spdlog-master/tests中能够找到file_log.cpp文件,其中有关于rotate的示例代码,如下:

TEST_CASE("rotating_file_logger1", "[rotating_logger]]")
{
    1. prepare_logdir();
    2. std::string basename = "logs/rotating_log";
    3. auto logger = spdlog::rotating_logger_mt("logger", basename, 1024, 0);
    //......
}

line: 3,创建名为"logger",文件名"rotating_log",在文件夹logs下,文件最大为1024(字节),
且只能有一个文件。返回的是logger对象,并且它是支持多线程的(mt)。

2.2 create

继续查看rotating_logger_mt,如下:

inline std::shared_ptr<spdlog::logger> spdlog::rotating_logger_mt(const std::string& logger_name, const filename_t& filename, size_t max_file_size, size_t max_files)
{
    1. return create<spdlog::sinks::rotating_file_sink_mt>(logger_name, filename, SPDLOG_FILENAME_T("txt"), max_file_size, max_files);
}

line: 1, 实际创建对象。create是一个模板函数,如下:

template <typename Sink, typename... Args>
inline std::shared_ptr<spdlog::logger> spdlog::create(const std::string& logger_name, Args... args)
{
    2. sink_ptr sink = std::make_shared<Sink>(args...);
    3. return details::registry::instance().create(logger_name, { sink });
}

Sink在本示例中即spdlog::sinks::rotating_file_sink_mt, 第二个模板参数也就是
rotating_file_sink_mt的构造函数参数。针对每一种sink,都会存在一个对应的create函数。
当然sink的实际创建也是发生在该函数中(line2)。

2.3 单例registry

1.2中 line:3又继续调用了registry执行对象的创建,我们先来看下registry是个什么鬼

#ifdef SPDLOG_NO_REGISTRY_MUTEX
typedef registry_t<spdlog::details::null_mutex> registry;
#else
typedef registry_t<std::mutex> registry;
#endif

可以看出在多线程的情况下,类registry需要锁的支持。
下面来到最需要关心的位置,也就是最后的create, 代码如下:

template<class It>
std::shared_ptr<logger> create(const std::string& logger_name, const It& sinks_begin, const It& sinks_end)
{
    1. std::lock_guard<Mutex> lock(_mutex);
    throw_if_exists(logger_name);
    std::shared_ptr<logger> new_logger;
    2. if (_async_mode)
        new_logger = std::make_shared<async_logger>(logger_name, sinks_begin, sinks_end, _async_q_size, _overflow_policy, _worker_warmup_cb, _flush_interval_ms, _worker_teardown_cb);
    else
        new_logger = std::make_shared<logger>(logger_name, sinks_begin, sinks_end);

    3. if (_formatter)
        new_logger->set_formatter(_formatter);

    if (_err_handler)
        new_logger->set_error_handler(_err_handler);

    new_logger->set_level(_level);


    //Add to registry
    4. _loggers[logger_name] = new_logger;
    return new_logger;
}
  • line1: 在多线程的情况下,创建一个logger对象需要加锁。
  • line2: 区分日志同步写/异步写,实际logger对象创建也是发生在这里。
  • line3: 有自定义格式时,使用自定义的格式。
  • line4: 将新创建的对象加入全局管理(registry是个单例),这也是需要加锁的原因。

2.4 小结

从上述的过程中,可以看到sink的实际创建是发生在create中,创建的sink对象做为参数参与了
logger对象的创建。在logger中sink起到的作用是什么,就是下面要讨论的问题了。

3. sink的使用

仍然以2.1 的例子继续,看下当时省略的代码先:

TEST_CASE("rotating_file_logger1", "[rotating_logger]]")
{
    //......
    for (int i = 0; i < 10; ++i)
        1. logger->info("Test message {}", i);
    //......
}

line1: 调用info输出日志,跟踪代码看下info到底是做了什么。

//片段1
template <typename... Args>
inline void spdlog::logger::log(level::level_enum lvl, const char* msg)
{
    details::log_msg log_msg(&_name, lvl);
    log_msg.raw << msg;
    1. _sink_it(log_msg);
}

//片段2
inline void spdlog::logger::_sink_it(details::log_msg& msg)
{
    _formatter->format(msg);
    2. for (auto &sink : _sinks)
    {
    if( sink->should_log( msg.level))
    {
        3. sink->log(msg);
    }
    }

    if(_should_flush_on(msg))
    flush();
}
  • line1: 创建一个log_msg对象,并将msg存入到该对象中,执行_sink_it
  • line2: 遍历当前对象中所有的sink,并对每一个sink执行它的log函数。

到这里,我们已经和上篇中的sink衔接上了。

3.1 小结

本篇已经接近尾声,从之前的分析中,可以得到如下的关键信息:

  1. log_msg是spdlog真正存储日志信息的位置,自然也是最后的输出对象。
  2. sink通过base_sink实现对mt/st的区分。
  3. registry是存储logger对象的单例,而logger是日志输出的真正执行者。
  4. spdlog通过创建不同的sink决定最后实例化的logger是mt/st,也能够决定最后的输出目标。
  5. 用户自定义的格式在logger中_sink_it时生效。

下一篇我们从log_msg开始。

转载于:https://www.cnblogs.com/eskylin/p/6483649.html

<think>我们正在处理关于spdlog日志库源码解读的问题。用户希望了解spdlog源码结构、关键机制实现细节。根据提供的引用片段,我们可以总结出以下几个关键点:1.**日志消息处理流程**:引用[1]展示了`async_logger`如何将日志消息投递到线程池的消息队列中(`sink_it_`方法)。2.**日志格式化**:引用[2]详细说明了模式字符串的解析占位符替换机制。3.**logger创建**:引用[3]展示了如何通过`basic_logger_mt`模板函数创建logger。4.**线程池任务分配**:引用[4]给出了一个简化的线程池任务分配示例(注意:此示例并非spdlog源码,但概念类似)。5.**日志消息结构**:引用[5]介绍了`log_msg`类的成员。基于这些信息,我们将系统性地解读spdlog的核心设计。###spdlog源码核心设计解读####1.日志消息(log_msg)spdlog中每条日志消息封装为`log_msg`对象,包含以下关键字段:-`logger_name`:日志记录器名称-`level`:日志级别(如debug,info,error等)-`time`:时间戳(默认为当前时间)-`thread_id`:线程ID(可通过宏定义禁用)-`source`:源代码位置(文件名、行号、函数名)-`payload`:日志正文内容该结构体定义类似于:```cppstructlog_msg{std::stringlogger_name;level::level_enumlevel;log_clock::time_pointtime;size_tthread_id;source_locsource;fmt::string_viewpayload;};```####2.日志记录器(logger)日志记录器是用户直接交互的接口。spdlog提供两种主要类型的logger:-**同步logger**:直接在当前线程执行日志写入操作-**异步logger**:通过线程池异步写入日志创建logger的典型方式(引用[3]):```cpp//创建多线程安全的文件loggerautofile_logger=spdlog::basic_logger_mt("file_logger","logs/basic.txt");```####3.异步日志处理机制异步logger的核心在于将日志消息投递到线程池(引用[1]):```cppvoidasync_logger::sink_it_(constdetails::log_msg&msg){if(autopool_ptr=thread_pool_.lock()){//将日志消息提交到线程池队列pool_ptr->post_log(shared_from_this(),msg,overflow_policy_);}else{throw_spdlog_ex("asynclog:threadpooldoesn'texistanymore");}}```此处`post_log`方法将日志任务加入线程池队列,由工作线程异步执行实际的sink操作。####4.日志格式化(pattern_formatter)spdlog通过`pattern_formatter`解析用户定义的模式字符串(如`"[%Y-%m-%d%H:%M:%S][%l]%v"`),其工作流程如下:1.**解析模式字符串**:识别占位符(如`%Y`、`%l`)普通文本2.**生成格式化项**:每个占位符对应一个格式化项(formatter)3.**格式化消息**:遍历格式化项,将`log_msg`中的数据转换为字符串关键占位符类型(引用[2]):-时间相关:`%Y`(年)、`%m`(月)、`%d`(日)、`%H`(时)、`%M`(分)、`%S`(秒)-日志级别:`%l`(完整级别名)、`%L`(简短级别名)-消息正文:`%v`-线程ID:`%t`-源码位置:`%s`(短文件名)、`%g`(全文件名)、`%#`(行号)、`%!`(函数名)####5.线程池设计spdlog的异步日志依赖于线程池(`thread_pool`),其核心结构包括:-**任务队列**:存储待处理的日志消息-**工作线程组**:多个线程从队列中取出任务并执行-**溢出策略**:当队列满时的处理方式(如阻塞或丢弃日志)线程池提交任务的简化逻辑(引用[4]概念):```cppvoidthread_pool::post_log(/*...*/){//将日志任务封装为可调用对象autotask=[logger,msg](){logger->sink_it_(msg);//实际执行日志写入};queue_.enqueue(std::move(task));//加入任务队列}```####6.sink机制sink负责最终输出日志,常见类型包括:-控制台sink(`stdout_color_sink_mt`)-文件sink(`basic_file_sink`)-旋转文件sink(`rotating_file_sink`)每个sink需要实现`sink_it_(constlog_msg&)`方法,例如文件sink会打开文件并写入字符串。###总结spdlog的设计围绕以下核心展开:1.**高效异步**:通过线程池实现非阻塞日志写入2.**灵活格式化**:支持自定义模式字符串3.**模块化sink**:可扩展的输出目标支持4.**类型安全**:基于fmt库的格式化机制§§相关问题§§1.spdlog如何处理日志格式化的性能优化?2.异步日志模式下如何避免消息丢失?3.spdlog如何实现日志文件的滚动(rotation)?4.在多进程写入同一日志文件时spdlog如何保证安全性?</think>### spdlog源码核心设计解读 spdlog是一个高性能C++日志库,其源码设计围绕**线程安全**、**异步处理****模块化扩展**三大核心展开。以下是关键机制解析: #### 1. 异步日志处理机制 核心类`async_logger`通过线程池实现非阻塞日志写入: ```cpp void 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"); } } ``` - **线程池调度**:`post_log`将日志任务投递到线程池队列(引用[1]) - **消息封装**:`log_msg`包含时间戳、线程ID、日志级别等元数据(引用[5]) - **溢出策略**:`overflow_policy_`控制队列满时的行为(阻塞/丢弃) #### 2. 格式化引擎设计 `pattern_formatter`实现高效的占位符解析: ```cpp // 模式字符串示例: "[%Y-%m-%d %H:%M:%S] [%l] %v" void pattern_formatter::format(...) { // 解析占位符并替换实际值 switch (flag) { case 'Y': // 年份 case 't': // 线程ID case 'v': // 消息正文 // ...其他占位符处理 } } ``` 支持40+种占位符类型(引用[2]): - 时间类:`%Y`(), `%S`(), `%f`(毫秒) - 上下文类:`%t`(线程ID), `%s`(源文件名) - 日志类:`%l`(完整级别), `%L`(简短级别) #### 3. 模块化sink架构 sink是实际输出设备(文件/控制台等)的抽象基类: ```cpp class sink { public: virtual void log(const details::log_msg& msg) = 0; virtual ~sink() = default; }; ``` - **多sink支持**:一个logger可绑定多个sink - **线程安全**:`sink_mt`提供互斥锁保护的线程安全版本 - **自定义扩展**:通过继承实现网络sink等自定义输出 #### 4. 核心对象关系 $$ \text{logger} \xrightarrow{\text{持有}} \text{sinks} \xrightarrow{\text{输出}} \text{目标设备} $$ $$ \text{async\_logger} \xrightarrow{\text{委托}} \text{thread\_pool} \xrightarrow{\text{异步执行}} \text{sink\_操作} $$ #### 5. 性能优化点 1. **内存池**:预分配`log_msg`对象减少动态内存分配 2. **无锁队列**:异步模式使用无锁队列(如moodycamel::ConcurrentQueue) 3. **格式化延迟**:仅在需要时执行字符串格式化 4. **批量提交**:线程池支持批量任务处理 ### 典型工作流程 1. 用户调用`spdlog::info("message")` 2. 构造`log_msg`对象(包含时间戳、线程ID等) 3. 同步模式:直接调用sink输出 4. 异步模式:提交任务到线程池队列 5. 工作线程从队列取出任务 6. 执行`pattern_formatter`格式化消息 7. sink将格式化结果写入目标设备 > 提示:通过`SPDLOG_LEVEL_TRACE`编译选项可启用内部调试日志(引用[3])
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值