🌈 个人主页:Zfox_
🔥 系列专栏:C++框架/库

🔥 介绍

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

github 链接: https://github.com/gabime/spdlog

🦋 特点

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

🦋 安装

# sudo apt-get install libspdlog-dev

🔥 使用

包含头文件:

在你的 C++ 源文件中包含 spdlog 的头文件:

#include <spdlog/spdlog.h>

日志输出等级枚举

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

日志输出格式自定义:

可以自定义日志消息的格式

logger->set_pattern("%Y-%m-%d %H:%M:%S [%t] [%-7l] %v");

%t - 线程 ID(Thread ID)。
%n - 日志器名称(Logger name)。
%l - 日志级别名称(Level name),如 INFO, DEBUG, ERROR 等。
%v - 日志内容(message)。
%Y - 年(Year)。
%m - 月(Month)。
%d - 日(Day)。
%H - 小时(24-hour format)。
%M - 分钟(Minute)。
%S - 秒(Second)。

日志记录器类:

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

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

异步日志记录类:

为了异步记录日志,可以使用 spdlog::async_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,
	std::weak_ptr<details::thread_pool> tp,
	async_overflow_policy overflow_policy =
	async_overflow_policy::block);
	
	//异步日志输出需要异步工作线程的支持,这里是线程池类
	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);
	};
}
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");

日志记录器工厂类:

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)

日志落地类

namespace spdlog {
namespace sinks {
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);
};
	using stdout_sink_mt;
	using stderr_sink_mt;
	using stdout_color_sink_mt;
	using stderr_color_sink_mt;
	//滚动日志文件-超过一定大小则自动重新创建新的日志文件
	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>;
	//普通的文件落地对啊 ing
	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:多线程版本,用于多线程程序是线程安全的。
}
}

全局接口:

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

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

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

使用样例
sync.cc

#include <iostream>
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/basic_file_sink.h>

int main()
{
    // 设置全局的刷新策略
    // 每秒刷新
    spdlog::flush_every(std::chrono::seconds(1));
    // 遇到 debug 以上等级的日志立即刷新
    spdlog::flush_on(spdlog::level::level_enum::debug);
    // 设置全局的日志输出等级 -- 无所谓 --每个日志器可以独立进行设置
    spdlog::set_level(spdlog::level::level_enum::debug);

    // 创建同步日志器(标准输出 / 文件)  -- 工厂接口默认创建的就是同步日志器
    // auto logger = spdlog::stdout_color_mt("default-logger");
    auto logger = spdlog::basic_logger_mt("file-logger", "sync.log");

    // 设置日志器的刷新策略,以及设置日志器的输出等级
    // logger->flush_on(spdlog::level::level_enum::debug);
    // logger->set_level(spdlog::level::level_enum::debug);

    // 设置日志输出格式
    logger->set_pattern("[%n][%H:%M:%S][%t][%-8l] %v");

    // 进行简单的日志输出
    logger->trace("你好! {}", "小明");
    logger->debug("你好! {}", "小明");
    logger->info("你好! {}", "小明");
    logger->warn("你好! {}", "小明");
    logger->error("你好! {}", "小明");
    logger->critical("你好! {}", "小明");

    std::cout << "日志输出演示完毕!" << std::endl;

    return 0;
}

async.cc

#include <iostream>
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/async.h>

int main()
{
    // 设置全局的刷新策略
    // 每秒刷新
    spdlog::flush_every(std::chrono::seconds(1));
    spdlog::flush_on(spdlog::level::level_enum::debug);
    spdlog::set_level(spdlog::level::level_enum::debug);

    // 初始化异步日志输出线程配置
    // void init_thread_pool(size_t q_size, size_t thread_count)
    spdlog::init_thread_pool(3072, 1);

    // 创建异步日志器(标准输出 / 文件)  -- 工厂接口默认创建的就是同步日志器   工厂不同所以创建出来的日志器也不同
    auto logger = spdlog::stdout_color_mt<spdlog::async_factory>("async-logger");

    // 设置日志输出格式
    logger->set_pattern("[%n][%H:%M:%S][%t][%-8l] %v");

    // 进行简单的日志输出
    logger->trace("你好! {}", "小明");
    logger->debug("你好! {}", "小明");
    logger->info("你好! {}", "小明");
    logger->warn("你好! {}", "小明");
    logger->error("你好! {}", "小明");
    logger->critical("你好! {}", "小明");

    std::cout << "日志输出演示完毕!" << std::endl;

    return 0;
}

🦋 二次封装:

因为 spdlog 的日志输出对文件名和行号并不是很友好(也有可能是调研不到位…),以及因为 spdlog 本身实现了线程安全,如果使用默认日志器每次进行单例获取,效率会有降低(单例的锁冲突),因此进行二次封装(直接创建全局的线程安全的日志器进行使用),简化使用。

  • 日志的初始化封装接口
  • 日志的输出接口封装宏
#include <iostream>
#include <string>
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/async.h>

// mode - 运行模式 true-发布模式   false - 调试模式

std::shared_ptr<spdlog::logger> g_default_logger;
void init_logger(bool mode, const std::string &file, int level)
{
    if (mode == false) {
        // 如果是调试模式,则创建标准输出日志器,输出等级为最低
        g_default_logger = spdlog::stdout_color_mt("default-logger");
        g_default_logger->set_level(spdlog::level::level_enum::trace);
        g_default_logger->flush_on(spdlog::level::level_enum::trace);
    } else {
        // 否则是发布模式,则创建文件输出日志器,输出等级根据参数而定
        g_default_logger = spdlog::basic_logger_mt("default-loggerr", file);
        g_default_logger->set_level((spdlog::level::level_enum)level);
        g_default_logger->flush_on((spdlog::level::level_enum)level);
    }
    g_default_logger->set_pattern("[%n][%H:%M:%S][%t][%-8l]%v");
}

#define LOG_TRACE(format, ...) g_default_logger->trace(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_DEBUG(format, ...) g_default_logger->debug(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_INFO(format, ...) g_default_logger->info(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_WARN(format, ...) g_default_logger->warn(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_ERROR(format, ...) g_default_logger->error(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__)
#define LOG_FATAL(format, ...) g_default_logger->critical(std::string("[{}:{}] ") + format, __FILE__, __LINE__, ##__VA_ARGS__)

测试代码 :

#include <iostream>
#include "logger.hpp"
#include <gflags/gflags.h>

DEFINE_bool(run_mode, false, "程序的运行模式, false-调试; true-发布;");
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_DEBUG("你好: {}", "比特");
    LOG_INFO("你好: {}", "比特");
    LOG_WARN("你好: {}", "比特");
    LOG_ERROR("你好: {}", "比特");
    LOG_FATAL("你好: {}", "比特");

    return 0;
}

spdlog 与 glog 组件对比

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

🦋 glog

glog 是由 Google 开发的一个开源 C++ 日志库,它提供了丰富的日志功能,包括多种日志级别、条件日志记录、日志文件管理、信号处理、自定义日志格式等。 glog 默认情况下是同步记录日志的,这意味着每次写日志操作都会阻塞直到日志数据被写入磁盘。

🎀 性能

根据张生荣的性能对比测试分析, glog 在同步调用的场景下的性能较 spdlog 慢。在一台低配的服务器上, glog 耗时 1.027 秒处理十万笔日志数据,而在固态硬盘上的耗时为 0.475 秒。

🦋 spdlog

spdlog 是一个开源的、高性能的 C++ 日志库,它支持异步日志记录,允许在不影响主线程的情况下进行日志写入。 spdlog 旨在提供零配置的用户体验,只需包含头文件即可使用。它还支持多种输出目标、格式化选项和线程安全。

🎀 性能

在同样的性能测试中, spdlog 在同步调用的场景下比 glog 快。在低配服务器上的耗时为 0.135 秒,而在固态硬盘上的耗时为 0.057 秒。此外, spdlog 还提供了异步日志记录的功能,其简单异步模式的耗时为 0.158 秒。

🦋 对比总结

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

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

🦋 总结

spdlog 是一个功能强大且易于使用的 C++ 日志库,它提供了丰富的功能和高性能的日志记录能力。通过简单的 API,开发者可以快速地在项目中实现日志记录,同时保持代码的清晰和可维护性。无论是在开发阶段还是生产环境中, spdlog 都能提供稳定和高效的日志服务。

🔥 共勉

😋 以上就是我对 【C++组件】Spdlog 日志组件的安装及使用 的理解, 觉得这篇博客对你有帮助的,可以点赞收藏关注支持一波~ 😉
在这里插入图片描述

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐