spdlog 日志库部分源码说明——日志格式设定,DIY你自己喜欢的调试信息,你能调试的远比你想象的还要丰富

本文主要是介绍spdlog 日志库部分源码说明——日志格式设定,DIY你自己喜欢的调试信息,你能调试的远比你想象的还要丰富,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言
最近,在使用spdlog日志库,但是不如自己使用std::cout 输出的方便,想要调整spdlog的格式化输出,但是网上缺少这块比较完整的资料,现在将这部分说明。

本章节主要说明创建日志后怎样格式化输出,以及可以的格式化输出有哪些。

准备

#define LOG_CHARCK_HOUR 0 // 每日创建日志的小时值(24h)
#define LOG_CHARCK_MINE 0 // 每日创建日志的分钟值(24h)std::shared_ptr<spdlog::logger> my_logger = spdlog::daily_logger_mt(FileName, logFilePath, LOG_CHARCK_HOUR, LOG_CHARCK_MINE,true);
spdlog::set_default_logger(my_logger);
my_logger->flush_on(spdlog::level::info);                                            // Flush logs immediately
my_logger->set_pattern("%^[%c|%x|ms:%e] [%l] [P:%P|t:%t] [Code:%@|%!(...)] - %v%$"); // Set log pattern

进过上述代码,可以创建一个每天零点零分切换的日志文件,路径是FileName字符串指定的内容,文件名称是logFilePath,创建的日志会自动在文件名后追加日期。

格式化

目标
我们的程序假设是一个包含多进程、多线程、多文件、函数众多,调用复杂的程序。
所以为了方便调试,我们期望能够知道 进程号线程号文件名称行号、函数名称、等调试信息
又因为我们的程序是长时间运行的,可能是多时区运行的,所以我们最好知道 日期时间时区
另外我们还想知道程序性能调用频率

基于以上内容我们可以如下设计我们的调试信息。

my_logger->set_pattern("%^[%c|%x|ms:%e] [%l] [P:%P|t:%t] [Code:|%@|%s|%g|%#|%!|] - %v%$"); // Set log pattern

他的打印输出如下:

[Tue Apr 23 00:00:17 2024|04/23/24|ms:981] [info] [P:22588|t:22600] [Code:/home/zry/src/modules/Connect/OpConnectMD.cpp:397|UploadStatusData(...)] - UploadStatusData start[Tue Apr 23 00:00:17 2024|04/23/24|ms:981] [debug] [P:22588|t:22600] [Code:/home/zry/src/modules/Connect/OpConnectMD.cpp:415|UploadStatusData(...)] -  : device_type = YWIND00[Tue Apr 23 00:00:17 2024|04/23/24|ms:981] [debug] [P:22588|t:22600] [Code:/home/zry/src/modules/Connect/OpConnectMD.cpp:416|UploadStatusData(...)] -  : device_nid = N01[Tue Apr 23 00:00:17 2024|04/23/24|ms:981] [debug] [P:22588|t:22600] [Code:/home/zry/src/modules/Connect/OpConnectMD.cpp:417|UploadStatusData(...)] -  : data_period = 20240423%0200[Tue Apr 23 00:00:17 2024|04/23/24|ms:981] [debug] [P:22588|t:22600] [Code:/home/zry/src/modules/Connect/OpConnectMD.cpp:418|UploadStatusData(...)] -  : data_len = 200[Tue Apr 23 00:00:17 2024|04/23/24|ms:981] [debug] [P:22588|t:22600] [Code:/home/zry/src/modules/Connect/OpConnectMD.cpp:419|UploadStatusData(...)] -  : data_content = DATADICK,V202201,00000,YWIND00,N01,ST,20240423000000,z,1,rAA,0,rAB,0,qAA,0,qAB,0,qCA,0,qDA,0,qEB,0,tF,0,tFA,-93,tFB,0,tFC,0,wA,25.7,wAA,0,xA,DC,xE,12.2,xEA,0,xF,16,xFA,0,xG,/,xGA,4,vAA,1,vAB,1,1929,ED[Tue Apr 23 00:00:17 2024|04/23/24|ms:981] [debug] [P:22588|t:22600] [Code:/home/zry/src/modules/Connect/OpConnectMD.cpp:420|UploadStatusData(...)] -  : is_sup = false

介绍

使用者调用的set_pattern()函数

spdlog 日志序列化基于 set_pattern()函数,这个函数负责处理日志格式字符串中的各种标志。根据不同的标志,它会创建不同的格式化对象,并将这些对象添加到formatters_向量中。这些格式化对象会在日志消息被记录时用于格式化消息。

例如,如果标志是'%',则函数会创建一个percent_formatter对象;如果标志是'n',则函数会创建一个logger_name_formatter对象;如果标志是'd',则函数会创建一个date_formatter对象等等。这些格式化对象都是formatter类的派生类,它们实现了format函数,该函数用于将日志消息的特定部分格式化为字符串。

此外,函数还处理一些其他标志,例如'+',它会设置默认的格式化器;'-',它会取消设置默认的格式化器;'#',它会在日志消息中包含源代码文件的行号;'&',它会在日志消息中包含当前运行的线程ID;'@',它会在日志消息中包含源代码文件的名称。

总之,这个函数负责根据日志格式字符串中的标志创建适当的格式化对象,并将这些对象添加到formatters_向量中,以便在记录日志消息时使用。

格式化日志消息的pattern_formatter类的成员函数handle_flag_

这段代码是SPDLOG日志库的源代码的一部分,具体是格式化日志消息的pattern_formatter类的成员函数handle_flag_

具体的源码如下:路径为:usr->local->include->spdlog->pattern_formatter-inl.h->{}spdlog->handle_flag_(char,detailspadding info)


template<typename Padder>
SPDLOG_INLINE void pattern_formatter::handle_flag_(char flag, details::padding_info padding)
{// process custom flagsauto it = custom_handlers_.find(flag);if (it != custom_handlers_.end()){auto custom_handler = it->second->clone();custom_handler->set_padding_info(padding);formatters_.push_back(std::move(custom_handler));return;}// process built-in flagsswitch (flag){case ('+'): // default formatterformatters_.push_back(details::make_unique<details::full_formatter>(padding));need_localtime_ = true;break;case 'n': // logger nameformatters_.push_back(details::make_unique<details::name_formatter<Padder>>(padding));break;case 'l': // levelformatters_.push_back(details::make_unique<details::level_formatter<Padder>>(padding));break;case 'L': // short levelformatters_.push_back(details::make_unique<details::short_level_formatter<Padder>>(padding));break;case ('t'): // thread idformatters_.push_back(details::make_unique<details::t_formatter<Padder>>(padding));break;case ('v'): // the message textformatters_.push_back(details::make_unique<details::v_formatter<Padder>>(padding));break;case ('a'): // weekdayformatters_.push_back(details::make_unique<details::a_formatter<Padder>>(padding));need_localtime_ = true;break;case ('A'): // short weekdayformatters_.push_back(details::make_unique<details::A_formatter<Padder>>(padding));need_localtime_ = true;break;case ('b'):case ('h'): // monthformatters_.push_back(details::make_unique<details::b_formatter<Padder>>(padding));need_localtime_ = true;break;case ('B'): // short monthformatters_.push_back(details::make_unique<details::B_formatter<Padder>>(padding));need_localtime_ = true;break;case ('c'): // datetimeformatters_.push_back(details::make_unique<details::c_formatter<Padder>>(padding));need_localtime_ = true;break;case ('C'): // year 2 digitsformatters_.push_back(details::make_unique<details::C_formatter<Padder>>(padding));need_localtime_ = true;break;case ('Y'): // year 4 digitsformatters_.push_back(details::make_unique<details::Y_formatter<Padder>>(padding));need_localtime_ = true;break;case ('D'):case ('x'): // datetime MM/DD/YYformatters_.push_back(details::make_unique<details::D_formatter<Padder>>(padding));need_localtime_ = true;break;case ('m'): // month 1-12formatters_.push_back(details::make_unique<details::m_formatter<Padder>>(padding));need_localtime_ = true;break;case ('d'): // day of month 1-31formatters_.push_back(details::make_unique<details::d_formatter<Padder>>(padding));need_localtime_ = true;break;case ('H'): // hours 24formatters_.push_back(details::make_unique<details::H_formatter<Padder>>(padding));need_localtime_ = true;break;case ('I'): // hours 12formatters_.push_back(details::make_unique<details::I_formatter<Padder>>(padding));need_localtime_ = true;break;case ('M'): // minutesformatters_.push_back(details::make_unique<details::M_formatter<Padder>>(padding));need_localtime_ = true;break;case ('S'): // secondsformatters_.push_back(details::make_unique<details::S_formatter<Padder>>(padding));need_localtime_ = true;break;case ('e'): // millisecondsformatters_.push_back(details::make_unique<details::e_formatter<Padder>>(padding));break;case ('f'): // microsecondsformatters_.push_back(details::make_unique<details::f_formatter<Padder>>(padding));break;case ('F'): // nanosecondsformatters_.push_back(details::make_unique<details::F_formatter<Padder>>(padding));break;case ('E'): // seconds since epochformatters_.push_back(details::make_unique<details::E_formatter<Padder>>(padding));break;case ('p'): // am/pmformatters_.push_back(details::make_unique<details::p_formatter<Padder>>(padding));need_localtime_ = true;break;case ('r'): // 12 hour clock 02:55:02 pmformatters_.push_back(details::make_unique<details::r_formatter<Padder>>(padding));need_localtime_ = true;break;case ('R'): // 24-hour HH:MM timeformatters_.push_back(details::make_unique<details::R_formatter<Padder>>(padding));need_localtime_ = true;break;case ('T'):case ('X'): // ISO 8601 time format (HH:MM:SS)formatters_.push_back(details::make_unique<details::T_formatter<Padder>>(padding));need_localtime_ = true;break;case ('z'): // timezoneformatters_.push_back(details::make_unique<details::z_formatter<Padder>>(padding));need_localtime_ = true;break;case ('P'): // pidformatters_.push_back(details::make_unique<details::pid_formatter<Padder>>(padding));break;case ('^'): // color range startformatters_.push_back(details::make_unique<details::color_start_formatter>(padding));break;case ('$'): // color range endformatters_.push_back(details::make_unique<details::color_stop_formatter>(padding));break;case ('@'): // source location (filename:filenumber)formatters_.push_back(details::make_unique<details::source_location_formatter<Padder>>(padding));break;case ('s'): // short source filename - without directory nameformatters_.push_back(details::make_unique<details::short_filename_formatter<Padder>>(padding));break;case ('g'): // full source filenameformatters_.push_back(details::make_unique<details::source_filename_formatter<Padder>>(padding));break;case ('#'): // source line numberformatters_.push_back(details::make_unique<details::source_linenum_formatter<Padder>>(padding));break;case ('!'): // source funcnameformatters_.push_back(details::make_unique<details::source_funcname_formatter<Padder>>(padding));break;case ('%'): // % charformatters_.push_back(details::make_unique<details::ch_formatter>('%'));break;case ('u'): // elapsed time since last log message in nanosformatters_.push_back(details::make_unique<details::elapsed_formatter<Padder, std::chrono::nanoseconds>>(padding));break;case ('i'): // elapsed time since last log message in microsformatters_.push_back(details::make_unique<details::elapsed_formatter<Padder, std::chrono::microseconds>>(padding));break;case ('o'): // elapsed time since last log message in millisformatters_.push_back(details::make_unique<details::elapsed_formatter<Padder, std::chrono::milliseconds>>(padding));break;case ('O'): // elapsed time since last log message in secondsformatters_.push_back(details::make_unique<details::elapsed_formatter<Padder, std::chrono::seconds>>(padding));break;default: // Unknown flag appears as isauto unknown_flag = details::make_unique<details::aggregate_formatter>();if (!padding.truncate_){unknown_flag->add_ch('%');unknown_flag->add_ch(flag);formatters_.push_back((std::move(unknown_flag)));}// fix issue #1617 (prev char was '!' and should have been treated as funcname flag instead of truncating flag)// spdlog::set_pattern("[%10!] %v") => "[      main] some message"// spdlog::set_pattern("[%3!!] %v") => "[mai] some message"else{padding.truncate_ = false;formatters_.push_back(details::make_unique<details::source_funcname_formatter<Padder>>(padding));unknown_flag->add_ch(flag);formatters_.push_back((std::move(unknown_flag)));}break;}
}

这段代码是SPDLOG日志库的源代码的一部分,具体是格式化日志消息的pattern_formatter类的成员函数handle_flag_

这个函数负责处理日志格式字符串中的各种标志。根据不同的标志,它会创建不同的格式化对象,并将这些对象添加到formatters_向量中。这些格式化对象会在日志消息被记录时用于格式化消息。

例如,如果标志是'%',则函数会创建一个percent_formatter对象;如果标志是'n',则函数会创建一个logger_name_formatter对象;如果标志是'd',则函数会创建一个date_formatter对象等等。这些格式化对象都是formatter类的派生类,它们实现了format函数,该函数用于将日志消息的特定部分格式化为字符串。

此外,函数还处理一些其他标志,例如'+',它会设置默认的格式化器;'-',它会取消设置默认的格式化器;'#',它会在日志消息中包含源代码文件的行号;'&',它会在日志消息中包含当前运行的线程ID;'@',它会在日志消息中包含源代码文件的名称。

总之,这个函数负责根据日志格式字符串中的标志创建适当的格式化对象,并将这些对象添加到formatters_向量中,以便在记录日志消息时使用。

格式化函数所属类pattern_formatter类的定义

而在 同级目录下的头文件定义中有 pattern_formatter 的定义,路径为:/usr/local/include/spdlog/pattern_formatter.h
具体源码如下:


class SPDLOG_API pattern_formatter final : public formatter
{
public:using custom_flags = std::unordered_map<char, std::unique_ptr<custom_flag_formatter>>;explicit pattern_formatter(std::string pattern, pattern_time_type time_type = pattern_time_type::local,std::string eol = spdlog::details::os::default_eol, custom_flags custom_user_flags = custom_flags());// use default pattern is not givenexplicit pattern_formatter(pattern_time_type time_type = pattern_time_type::local, std::string eol = spdlog::details::os::default_eol);pattern_formatter(const pattern_formatter &other) = delete;pattern_formatter &operator=(const pattern_formatter &other) = delete;std::unique_ptr<formatter> clone() const override;void format(const details::log_msg &msg, memory_buf_t &dest) override;template<typename T, typename... Args>pattern_formatter &add_flag(char flag, Args &&...args){custom_handlers_[flag] = details::make_unique<T>(std::forward<Args>(args)...);return *this;}void set_pattern(std::string pattern);void need_localtime(bool need = true);private:std::string pattern_;std::string eol_;pattern_time_type pattern_time_type_;bool need_localtime_;std::tm cached_tm_;std::chrono::seconds last_log_secs_;std::vector<std::unique_ptr<details::flag_formatter>> formatters_;custom_flags custom_handlers_;std::tm get_time_(const details::log_msg &msg);template<typename Padder>void handle_flag_(char flag, details::padding_info padding);// Extract given pad spec (e.g. %8X)// Advance the given it pass the end of the padding spec found (if any)// Return padding.static details::padding_info handle_padspec_(std::string::const_iterator &it, std::string::const_iterator end);void compile_pattern_(const std::string &pattern);
};

而这个类对象被作为智能指针所指向的对象在 我们调用 set_pattern函数时,被创建。

再回到 set_pattern函数的详细说明

具体的源码如下:路径为:/usr/local/include/spdlog/logger-inl.h
源码为:

SPDLOG_INLINE void logger::set_pattern(std::string pattern, pattern_time_type time_type)
{auto new_formatter = details::make_unique<pattern_formatter>(std::move(pattern), time_type);set_formatter(std::move(new_formatter));
}

这段代码定义了一个名为 set_pattern 的成员函数,用于设置日志记录器的格式化模式。函数接受两个参数:一个字符串 pattern 和一个枚举类型 pattern_time_type

  1. auto new_formatter = details::make_unique<pattern_formatter>(std::move(pattern), time_type); 这行代码创建了一个名为 new_formatter 的智能指针,指向一个新创建的 pattern_formatter 对象。pattern_formatterspdlog 中用于格式化日志信息的类。std::move(pattern) 用于将 pattern 字符串移动到新创建的 pattern_formatter 对象中,time_type 参数指定了时间格式的类型。
  2. set_formatter(std::move(new_formatter)); 这行代码调用了 set_formatter 成员函数,将新创建的格式化器 new_formatter 设置为当前日志记录器的格式化器。

总的来说,set_pattern 函数用于设置日志记录器的格式化模式,可以通过传入一个特定的模式字符串和一个时间格式来定制日志输出的格式。

补充

另外,也附赠 创建日志的源码路径,有兴趣的朋友可以看一下都有哪些创建特定日志风格的方式。
具体路径为:/usr/local/include/spdlog/sinks/daily_file_sink.h

其中我使用的可以定点日切的源码为:

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

而我们整个程序的生命周期中操作的日志对象,其实是一个名为logger的类对象,它的定义路径为/usr/local/include/spdlog/logger.h

源码由于太多,这里不放了,有兴趣的同学可以收藏,在空闲时间自己查找路径查看源码。
这里只列出对logger类的解释,解释如下:

这段代码是C++实现的SPDLOG日志类。SPDLOG是一个快速、头文件只读、C++日志库,旨在易于使用、高效,同时仍然提供必要的灵活性来满足各种应用程序的需求。

logger类是SPDLOG库的核心组件。它提供了在不同严重级别记录消息的方法(例如,跟踪、调试、信息、警告、错误和严重),并支持将日志记录到多个接收器(例如,控制台、文件和网络)。

logger类有几种构造函数,包括:

  • explicit logger(std::string name): 使用指定的名称和空接收器列表构造一个日志记录器。
  • logger(std::string name, sink_ptr single_sink): 使用指定的名称和一个接收器构造一个日志记录器。
  • logger(std::string name, sinks_init_list sinks): 使用指定的名称和初始接收器列表构造一个日志记录器。

logger类还有几种用于记录消息的方法,包括:

  • log(level::level_enum lvl, const std::string& msg): 在指定的严重级别记录一条消息。
  • log(level::level_enum lvl, const char* msg): 在指定的严重级别记录一条消息。
  • log(level::level_enum lvl, const wchar_t* msg): 在指定的严重级别记录一条消息。
  • log(level::level_enum lvl, const void* msg): 在指定的严重级别记录一条消息。
  • log(level::level_enum lvl, std::string&& msg): 在指定的严重级别记录一条消息。
  • log(level::level_enum lvl, const std::string& fmt, ...): 在指定的严重级别记录一条格式化的消息。
  • log(level::level_enum lvl, const char* fmt, ...): 在指定的严重级别记录一条格式化的消息。
  • log(level::level_enum lvl, const wchar_t* fmt, ...): 在指定的严重级别记录一条格式化的消息。

logger类还有设置记录级别、启用和禁用回溯记录、将缓冲记录刷新到接收器的方法。

spdlog日志格式化可以使用符号查找表

标志描述
+默认格式化器
n记录器名称
l级别
L短级别
t线程ID
v消息
a星期
A短星期
b月份
B短月份
c日期
C年份(2位数字)
Y年份(4位数字)
D日期(MM/DD/YY)
d月份中的天数
H小时(24小时制)
I小时(12小时制)
M分钟
S
e毫秒
f微秒
F纳秒
p上午/下午
r时间(12小时制)
R时间(24小时制)
TISO 8601时间
z时区
P进程ID
^颜色范围开始
$颜色范围结束
@源位置
s短源文件名
g完整源文件名
#源行号
!源函数名
%转义字符
u上次记录消息以来的经过时间(纳秒)
i上次记录消息以来的经过时间(微秒)
o上次记录消息以来的经过时间(毫秒)
O上次记录消息以来的经过时间(秒)

后记

这里我故意留了一个小尾巴,考虑到篇幅长度,这里只对源码做解释,后续我会更新一篇实操代码中遇到的问题记录,并在其中附赠一份可用的源码框架。


分享一个有趣的 学习链接:https://xxetb.xet.tech/s/HY8za

这篇关于spdlog 日志库部分源码说明——日志格式设定,DIY你自己喜欢的调试信息,你能调试的远比你想象的还要丰富的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

JavaScript中的高级调试方法全攻略指南

《JavaScript中的高级调试方法全攻略指南》什么是高级JavaScript调试技巧,它比console.log有何优势,如何使用断点调试定位问题,通过本文,我们将深入解答这些问题,带您从理论到实... 目录观点与案例结合观点1观点2观点3观点4观点5高级调试技巧详解实战案例断点调试:定位变量错误性能分

使用Python批量将.ncm格式的音频文件转换为.mp3格式的实战详解

《使用Python批量将.ncm格式的音频文件转换为.mp3格式的实战详解》本文详细介绍了如何使用Python通过ncmdump工具批量将.ncm音频转换为.mp3的步骤,包括安装、配置ffmpeg环... 目录1. 前言2. 安装 ncmdump3. 实现 .ncm 转 .mp34. 执行过程5. 执行结

SpringBoot日志级别与日志分组详解

《SpringBoot日志级别与日志分组详解》文章介绍了日志级别(ALL至OFF)及其作用,说明SpringBoot默认日志级别为INFO,可通过application.properties调整全局或... 目录日志级别1、级别内容2、调整日志级别调整默认日志级别调整指定类的日志级别项目开发过程中,利用日志

深度剖析SpringBoot日志性能提升的原因与解决

《深度剖析SpringBoot日志性能提升的原因与解决》日志记录本该是辅助工具,却为何成了性能瓶颈,SpringBoot如何用代码彻底破解日志导致的高延迟问题,感兴趣的小伙伴可以跟随小编一起学习一下... 目录前言第一章:日志性能陷阱的底层原理1.1 日志级别的“双刃剑”效应1.2 同步日志的“吞吐量杀手”

Redis中哨兵机制和集群的区别及说明

《Redis中哨兵机制和集群的区别及说明》Redis哨兵通过主从复制实现高可用,适用于中小规模数据;集群采用分布式分片,支持动态扩展,适合大规模数据,哨兵管理简单但扩展性弱,集群性能更强但架构复杂,根... 目录一、架构设计与节点角色1. 哨兵机制(Sentinel)2. 集群(Cluster)二、数据分片

Springboot项目构建时各种依赖详细介绍与依赖关系说明详解

《Springboot项目构建时各种依赖详细介绍与依赖关系说明详解》SpringBoot通过spring-boot-dependencies统一依赖版本管理,spring-boot-starter-w... 目录一、spring-boot-dependencies1.简介2. 内容概览3.核心内容结构4.

java -jar example.jar 产生的日志输出到指定文件的方法

《java-jarexample.jar产生的日志输出到指定文件的方法》这篇文章给大家介绍java-jarexample.jar产生的日志输出到指定文件的方法,本文给大家介绍的非常详细,对大家的... 目录怎么让 Java -jar example.jar 产生的日志输出到指定文件一、方法1:使用重定向1、

redis和redission分布式锁原理及区别说明

《redis和redission分布式锁原理及区别说明》文章对比了synchronized、乐观锁、Redis分布式锁及Redission锁的原理与区别,指出在集群环境下synchronized失效,... 目录Redis和redission分布式锁原理及区别1、有的同伴想到了synchronized关键字

MySQL 临时表创建与使用详细说明

《MySQL临时表创建与使用详细说明》MySQL临时表是存储在内存或磁盘的临时数据表,会话结束时自动销毁,适合存储中间计算结果或临时数据集,其名称以#开头(如#TempTable),本文给大家介绍M... 目录mysql 临时表详细说明1.定义2.核心特性3.创建与使用4.典型应用场景5.生命周期管理6.注

c++日志库log4cplus快速入门小结

《c++日志库log4cplus快速入门小结》文章浏览阅读1.1w次,点赞9次,收藏44次。本文介绍Log4cplus,一种适用于C++的线程安全日志记录API,提供灵活的日志管理和配置控制。文章涵盖... 目录简介日志等级配置文件使用关于初始化使用示例总结参考资料简介log4j 用于Java,log4c