当前位置: 首页 > news >正文

【c++中间件】spdlog日志介绍 二次封装

文章目录

  • I. spdlog 的介绍
      • spdlog 与 glog 组件的区别
  • Ⅱ. Spdlog 的使用
      • 1. 头文件 && 链接库
      • 2. 日志输出等级枚举
      • 3. 日志输出格式自定义
      • 4. 日志记录器类
      • 5. 异步日志记录类
      • 6. 日志记录器工厂类
      • 7. 日志落地类
      • 8. 全局接口
      • 9. 记录日志
  • Ⅲ. 使用样例
  • Ⅳ. spdlog的二次封装 -- logger.hpp

在这里插入图片描述

I. spdlog 的介绍

github链接

spdlog 是一个高性能、超快速、零配置的 C++ 日志库,它旨在提供简洁的 API 和丰富的功能,同时保持高性能的日志记录。它支持多种输出目标、格式化选项、线程安全以及异步日志记录。特点如下所示:

  • 高性能spdlog 专为速度而设计,即使在高负载情况下也能保持良好的性能。
  • 零配置:无需复杂的配置,只需包含头文件即可在项目中使用。
  • 异步日志:支持异步日志记录,减少对主线程的影响。
  • 格式化:支持自定义日志消息的格式化,包括时间戳、线程ID、日志级别等。
  • 多平台:跨平台兼容,支持 WindowsLinuxmacOS 等操作系统。
  • 丰富的 API:提供丰富的日志级别和操作符重载,方便记录各种类型的日志。

spdlog 与 glog 组件的区别

glogspdlog 都是流行的 C++ 日志库,它们各自具有不同的特点和优势。以下是对这两个库的对比分析,包括性能测试的结果和使用场景的考量。

glog 是由 Google 开发的一个开源 C++ 日志库,它提供了丰富的日志功能,包括多种日志级别、条件日志记录、日志文件管理、信号处理、自定义日志格式等。glog 默认情况下是同步记录日志的,这意味着每次写日志操作都会阻塞直到日志数据被写入磁盘。根据张生荣的性能对比测试分析,glog 在同步调用的场景下的性能较 spdlog。在一台低配的服务器上,glog 耗时 1.027 秒处理十万笔日志数据,而在固态硬盘上的耗时为 0.475 秒。

spdlog 是一个开源的、高性能的 C++ 日志库,它 支持异步日志记录,允许在不影响主线程的情况下进行日志写入。spdlog 旨在提供零配置的用户体验,只需包含头文件即可使用。它还支持多种输出目标、格式化选项和线程安全。在同样的性能测试中,spdlog 在同步调用的场景下比 glog。在低配服务器上的耗时为 0.135 秒,而在固态硬盘上的耗时为 0.057 秒。此外,spdlog 还提供了异步日志记录的功能,其简单异步模式的耗时为 0.158 秒。

对比总结如下:

  • 性能:从性能测试结果来看,spdlog 在同步调用场景下的性能优于 glog。当涉及到大量日志数据时,spdlog 显示出更快的处理速度。
  • 异步日志spdlog 支持异步日志记录,这在处理高负载应用程序时非常有用,可以减少日志操作对主线程的影响。
  • 易用性spdlog 提供了更简单的集成和配置方式,只需包含头文件即可使用,而 glog 可能需要额外的编译和配置步骤。
  • 功能glog 提供了一些特定的功能,如条件日志记录和信号处理,这些在某些场景下可能非常有用。
  • 使用场景glog 可能更适合那些对日志性能要求不是特别高,但需要一些特定功能的场景。而 spdlog 则适合需要高性能日志记录和异步日志能力的应用程序。

​ 在选择日志库时,开发者应根据项目的具体需求和性能要求来决定使用哪个库。如果项目对日志性能有较高要求,或者需要异步日志记录来避免阻塞主线程,spdlog 可能是更好的选择。如果项目需要一些特定的日志功能,或者已经在使用 glog 且没有显著的性能问题,那么继续使用 glog 也是合理的。

安装命令:

sudo apt-get install libspdlog-dev

Ⅱ. Spdlog 的使用

1. 头文件 && 链接库

注意在编译的时候要链接库

// 头文件:include <spdlog/spdlog.h>// makefile文件:g++ -std=c++17 -o $@ $^ -lspdlog

注意事项:

在这里插入图片描述

​ 由于 spdlog.h 头文件可能没有包含一些落地类的头文件,比如 spdlog/sinks/stdout_color_sinks.h,所以报错的时候需要我们用 grep/usr/include/spdlog 文件夹中查找一下需要的头文件,然后包含进来!

在这里插入图片描述

​ 还需要在编译时候带上 -lfmt 的选项进行链接!

在这里插入图片描述

2. 日志输出等级枚举

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

3. 日志输出格式自定义

logger->set_pattern("%Y-%m-%d %H:%M:%S [%t] [%-8l] %v");
  • %t:线程 ID(Thread ID)
  • %n:日志器名称(Logger name)
  • %l:日志级别名称(Level name),如 INFODEBUGERROR
  • %v:日志内容(message)
  • %Y:年(Year)
  • %m:月(Month)
  • %d:日(Day)
  • %H:小时(24-hour format)
  • %M:分钟(Minute)
  • %S:秒(Second)

4. 日志记录器类

所有的日志信息都是通过该日志记录器类也就是 logger 类来进行输出的

​ 创建一个基本的日志记录器,并设置日志级别和输出模式:

namespace spdlog {class logger {// 构造函数logger(std::string name)logger(std::string name, sink_ptr single_sink);logger(std::string name, sinks_init_list sinks);// 设置日志级别和输出模式void set_level(level::level_enum log_level);void set_formatter(std::unique_ptr<formatter> f);// 设置不同级别的日志输出模式template<typename... Args>void trace(fmt::format_string<Args...> fmt, Args &&...args);template<typename... Args>void debug(fmt::format_string<Args...> fmt, Args &&...args);template<typename... Args>void info(fmt::format_string<Args...> fmt, Args &&...args);template<typename... Args>void warn(fmt::format_string<Args...> fmt, Args &&...args);template<typename... Args>void error(fmt::format_string<Args...> fmt, Args &&...args);template<typename... Args>void critical(fmt::format_string<Args...> fmt, Args &&...args);// 刷新日志void flush(); // 策略刷新--触发指定等级日志的时候立即刷新日志的输出void flush_on(level::level_enum log_level);
};

注意事项:

  1. set_level() 用于设置日志级别的函数。日志级别决定了哪些日志消息会被实际输出,哪些会被忽略。
    • 例如,当你调用 logger->set_level(spdlog::level::warn); 时,只有 warnerrorcritical 级别的日志消息会被记录,而 tracedebuginfo 级别的消息将被忽略
  2. flush_on() 用于设置日志在何种级别的消息输出时进行刷新操作。刷新操作是将日志信息从缓冲区写入到实际的输出目标(如文件、控制台等)。
    • 例如,当你调用 logger->flush_on(spdlog::level::error);,表示在 输出 error 级别及以上的日志时,会立即刷新日志缓冲区。在一些需要确保重要日志信息及时输出的场景下很重要,避免信息滞留在缓冲区而没有及时写入文件或显示在控制台,比如在处理错误时,确保错误信息能尽快输出,避免程序崩溃后丢失错误日志。
  3. flush_every() 用于设置日志的定期刷新间隔。

5. 异步日志记录类

​ 异步日志记录类的作用是 将日志的写入操作与主线程分离,通过线程池来异步完成日志记录工作,从而提高性能,可以使用 spdlog::async_logger 来创建:

// 继承于logger,可以使用设置日志级别以及输出模式的接口
class async_logger final : public 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_overflow_policy::block // 队满时的策略);async_logger(std::string logger_name,sink_ptr single_sink, // 单一日志输出目标的指针,与上面的区别是这里只支持一个Sinkstd::weak_ptr<details::thread_pool> tp,async_overflow_policy overflow_policy = async_overflow_policy::block);// async_logger的工作需要异步工作线程的支持,下面是线程池类class SPDLOG_API 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); // 一个可选的回调函数,用于在每个线程结束时执行thread_pool(size_t q_max_items, size_t threads_n, std::function<void()> on_thread_start);thread_pool(size_t q_max_items, size_t threads_n);};
};// 获取默认线程池的实例的接口,默认线程池由spdlog内部维护,多个async_logger可以共享同一个线程池
// 应用场景:当不需要单独创建线程池时,可以直接使用默认线程池。
std::shared_ptr<spdlog::details::thread_pool> thread_pool() {return details::registry::instance().get_tp();
}// 默认线程池的初始化接口
inline void init_thread_pool(size_t q_size, size_t thread_count);// 使用例子:
auto async_logger = spdlog::async_logger_mt("async_logger", "logs/async_log.txt"); // 工厂类(下面会讲)
async_logger->info("This is an asynchronous info message");

6. 日志记录器工厂类

using async_factory = async_factory_impl<async_overflow_policy::block>;template<typename Sink, typename... SinkArgs>
inline std::shared_ptr<spdlog::logger> create_async(std::string logger_name, SinkArgs &&...sink_args);// 创建一个彩色输出到标准输出的日志记录器,默认工厂创建同步日志记录器
template<typename Factory = spdlog::synchronous_factory>
std::shared_ptr<logger> stdout_color_mt(const std::string &logger_name, color_mode mode = color_mode::automatic);// 标准错误
template<typename Factory = spdlog::synchronous_factory>
std::shared_ptr<logger> stderr_color_mt(const std::string &logger_name, color_mode mode = color_mode::automatic);// 指定文件 
template<typename Factory = spdlog::synchronous_factory>
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 = {});// 循环文件(即用多个文件记录,保证单个文件大小不会太大)
template<typename Factory = spdlog::synchronous_factory>
std::shared_ptr<logger> rotating_logger_mt(const std::string &logger_name, const filename_t &filename, size_t max_file_size, size_t max_files, bool rotate_on_open = false);
...

7. 日志落地类

​ 所谓日志落地类,就是 指定日志要往哪里输出,是往文件输出,还是往终端设备输出,都是用该类指定的!

namespace spdlog {namespace sinks {// 可以继承下面的sink类来重写接口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);};// 不同类型的日志输出目标#ifdef _WIN32using stdout_color_sink_mt = wincolor_stdout_sink_mt;using stdout_color_sink_st = wincolor_stdout_sink_st;using stderr_color_sink_mt = wincolor_stderr_sink_mt;using stderr_color_sink_st = wincolor_stderr_sink_st;#elseusing stdout_color_sink_mt = ansicolor_stdout_sink_mt;using stdout_color_sink_st = ansicolor_stdout_sink_st;using stderr_color_sink_mt = ansicolor_stderr_sink_mt;using stderr_color_sink_st = ansicolor_stderr_sink_st;#endif// 滚动日志文件-超过一定大小则自动重新创建新的日志文件sink_ptr rotating_file_sink(filename_t base_filename, std::size_t max_size, std::size_t max_files, bool rotate_on_open = false, const file_event_handlers &event_handlers = {});using rotating_file_sink_mt = rotating_file_sink<std::mutex>;// 普通的文件落地类sink_ptr basic_file_sink(const filename_t &filename,bool truncate = false,const file_event_handlers &event_handlers = {});using basic_file_sink_mt = basic_file_sink<std::mutex>;using kafka_sink_mt = kafka_sink<std::mutex>;using mongo_sink_mt = mongo_sink<std::mutex>;using tcp_sink_mt = tcp_sink<std::mutex>;using udp_sink_mt = udp_sink<std::mutex>;.....// *_st:单线程版本,不用加锁,效率更高。// *_mt:多线程版本,用于多线程程序是线程安全的。} 
}// 使用例子:
auto console_logger = spdlog::stdout_logger_mt("console_logger");
console_logger->info("This is an info message to stdout.");

8. 全局接口

spdlog 中的 set_level 接口既存在于 logger 类中,又有全局的 set_level 接口,这种设计是 为了提供灵活的日志级别控制

void set_level(level::level_enum log_level); // 输出等级设置接口void flush_every(std::chrono::seconds interval); // 日志刷新策略:每隔 N 秒刷新一次
void flush_on(level::level_enum log_level); // 日志刷新策略:触发指定等级以上立即刷新

9. 记录日志

​ 使用日志记录器记录不同级别的日志:

logger->trace("This is a trace message");
logger->debug("This is a debug message");
logger->info("This is an info message");
logger->warn("This is a warning message");
logger->error("This is an error message");
logger->critical("This is a critical message");

注意事项:

  • 若要打印占位元素信息,不再像 printf 等函数一样用 %s 等来表示,而是 直接用 {} 来表示占位符,如下所示:

    logger->debug("你好啊!{}", "小明");
    

Ⅲ. 使用样例

​ 下面我们分别创建同步日志记录器和异步日志记录器来看看区别,无非就是创建工厂类使用的参数不同而已,其他基本都是一样的!

​ 同步日志记录器:

#include <iostream>
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
using namespace std;int main()
{// 设置全局刷新策略spdlog::flush_every(std::chrono::seconds(1));       // 设置每秒刷新spdlog::flush_on(spdlog::level::level_enum::debug); // 设置为debug级别以上立刻刷新// 设置全局日志输出等级(可忽略,因为一般下面都会对特定的日志级别进行输出)spdlog::set_level(spdlog::level::level_enum::debug);// 创建同步日志器auto logger = spdlog::stdout_color_mt("default-logger");// 设置日志同步器的刷新策略、输出级别(这也是为什么上面不需要设置全局输出等级的原因)logger->flush_on(spdlog::level::level_enum::debug);logger->set_level(spdlog::level::level_enum::info);// 设置输出格式logger->set_pattern("[%H:%M:%S][%t][%-8l] %v");// 进行简单的日志输出logger->trace("你好!{}", "liren");logger->debug("你好!{}", "liren");logger->info("你好!{}", "liren");logger->warn("你好!{}", "liren");logger->error("你好!{}", "liren");logger->critical("你好!{}", "liren");std::cout << "同步日志输出演示完毕!" << std::endl;return 0;
}

​ 异步日志记录器:

#include <iostream>
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/async.h>
using namespace std;int main()
{spdlog::flush_every(std::chrono::seconds(1));       spdlog::flush_on(spdlog::level::level_enum::debug); // 唯一不同之处:创建异步日志器,传入模板参数即可auto logger = spdlog::stdout_color_mt<spdlog::async_factory>("default-logger");logger->flush_on(spdlog::level::level_enum::debug);logger->set_level(spdlog::level::level_enum::info);logger->set_pattern("[%H:%M:%S][%t][%-8l] %v");logger->trace("你好!{}", "liren");logger->debug("你好!{}", "liren");logger->info("你好!{}", "liren");logger->warn("你好!{}", "liren");logger->error("你好!{}", "liren");logger->critical("你好!{}", "liren");std::cout << "异步日志输出演示完毕!" << std::endl;return 0;
}

makefile 文件:

all : sync async
sync : sync.ccg++ -std=c++17 -o $@ $^ -lspdlog -lfmt
async : async.ccg++ -std=c++17 -o $@ $^ -lspdlog -lfmt

​ 执行结果如下图所示:

在这里插入图片描述

Ⅳ. spdlog的二次封装 – logger.hpp

spdlog 二次封装的原因如下所示:

  • 为了避免单例对象的锁冲突,所以需要直接创建一个全局的线程安全的日志器进行使用!因为对于日志操作,通常是频繁调用的操作,如果每次调用都需要加锁检查单例对象是否存在,会带来性能损耗,而在程序初始化时创建一个全局的日志器,可以避免这种开销。
  • 因为 spdlog 中的日志输出 没有包含【行号】与【文件名】,所以需要用宏来二次封装输出
  • 封装出一个初始化接口,便于使用!如根据不同模式采取不同的输出落地目标,调试模式输出到标准输出,而发行模式则输出到文件中!
// logger.hpp头文件
#include <iostream>
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <string>
#include <memory>std::shared_ptr<spdlog::logger> logger;// mode:true表示发布模式,false表示调试模式
// file:文件名
// level:输出等级
void init_logger(bool mode, const std::string& file, int level)
{if(mode == false) // 如果是调试模式,则建立标准输出日志器,输出等级为最低{logger = spdlog::stdout_color_mt("default-logger");logger->set_level(spdlog::level::level_enum::trace);logger->flush_on(spdlog::level::level_enum::trace);}else // 如果是发布模式,则建立文件输出日志器,输出等级根据传入的参数决定{logger = spdlog::basic_logger_mt("default-logger", file);logger->set_level((spdlog::level::level_enum)level);logger->flush_on(spdlog::level::level_enum::trace);}logger->set_pattern("[%n][%H:%M:%S][%t][%-8l]%v");
}#define LOG_TRACE(format, ...) logger->trace(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__);
#define LOG_DEBUG(format, ...) logger->debug(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__);
#define LOG_INFO(format, ...) logger->info(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__);
#define LOG_WARN(format, ...) logger->warn(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__);
#define LOG_ERROR(format, ...) logger->error(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__);
#define LOG_CRITICAL(format, ...) logger->critical(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__);

​ 下面是测试样例,结合上前面学到的 gflags 一起使用:

// main.cc文件:
#include "logger.hpp"
#include <gflags/gflags.h>DEFINE_bool(run_mode, false, "程序运行模式:true为发布模式,false为调试模式");
DEFINE_string(log_file, "", "发布模式下用于指定日志的输出文件");
DEFINE_int32(log_level, 0, "发布模式下用于指定日志输出等级");int main(int argc, char* argv[])
{google::ParseCommandLineFlags(&argc, &argv, true);init_logger(FLAGS_run_mode, FLAGS_log_file, FLAGS_log_level);LOG_TRACE("你好啊,{}!", "liren");LOG_DEBUG("你好啊,{}!", "liren");LOG_INFO("你好啊,{}!", "liren");LOG_WARN("你好啊,{}!", "liren");LOG_ERROR("你好啊,{}!", "liren");LOG_CRITICAL("你好啊,{}!", "liren");return 0;
}// makefile文件:
main : main.ccg++ -std=c++17 -o $@ $^ -lspdlog -lfmt -lgflags

​ 执行结果如下所示:

在这里插入图片描述

在这里插入图片描述

http://www.dtcms.com/a/618774.html

相关文章:

  • 设计网站中如何设置特效wordpress自定义短码
  • 4.FPGA字符格式
  • 网站服务器有问题怎么办啊南宁手机建站公司
  • 【Java 基础】4 面向对象 - 封装:面向对象三大特征之一
  • vps建设网站需要条件瀑布流资源网站模板
  • 还有做网站的必要吗识图 WordPress
  • 俄语 俄文 俄罗斯语外贸网站建设礼品定制
  • 郑州好的建网站公司wordpress 采集器
  • 轻松设置-系统优化万能工具
  • query加强之深度解析ReDI:通过分解与解释增强query理解的推理方法
  • 观点动力学和回音室
  • 中小学网站建设域名论坛网站
  • 5.网络原理之TCP_IP
  • 全球访问量top100网站建设银行官方网站-云服务
  • 小梦音乐下载 1.0.5 | 提供三条音源,支持多种音质选择和批量下载的音乐下载工具
  • GIS:揭开你神秘的面纱
  • 怎么做网站小图标有的网站域名解析错误
  • 安徽省网站肥建设网站湖北望新建设有限公司网站
  • 机器学习周报二十二
  • 计算二叉树的深度 | C语言
  • 什么网站算是h5做的网络推广企划
  • 传导案例:某医疗仪器传导骚扰整改案例
  • 做跨境电商有没推荐的网站新闻稿件代发平台
  • C++篇(18)类型转换与IO库
  • 海口中小企业网站制作3D特效做首页的网站
  • 专业做家政网站( )是网站可以提供给用户的价值
  • 网站活动专题页面学校网站建设制作方案
  • 【C++】从理论到实践:类和对象完全指南(上)
  • 网站不排名一切等于零做网站推广维护需要学些什么
  • 公考面试资源合集