异步日志方案——spdlog

本文详细介绍了spdlog,一个C++高性能的日志库,探讨了其零成本抽象、异步日志记录、高效的fmt格式化以及灵活的配置选项。还展示了如何使用和配置spdlog,包括输出控制、线程池处理和示例代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

日志的作用

在程序运行过程中打印日志,可以帮助我们跟踪调试程序的运行状态,获取需要的调试信息。通过日志打印的时间,我们可以了解程序在特定时间的行为,分析性能瓶颈,甚至有大佬可以预测潜在的系统故障。

什么是spdlog

spdlog是一款开源的C++日志库,他有着极高的性能和几乎零成本的抽象。spdlog支持异步和同步日志记录,提供了多种日志级别,可以将日志打印在终端,文件或者自定义接收器(如数据库)。

git clone https://github.com/gabime/spdlog.git

cd spdlog
mkdir build & cd build
cmake ..
make
sudo make install

spdlog为什么高效

  • 零成本的抽象:spdlog通过模版和内联函数,确保只有在真正需要的时候才进行日志记录。(内联函数在需要的时候会直接编译在程序中,模版只有在我们需要的时候才会展开)
  • 异步日志记录:spdlog支持异步日志记录,这意味着他可以将消息发送到线程池进行处理,不会阻塞当前线程,减少了主线程的开销。
  • 高效的格式化:spdlog使用fmt库进行高效的字符串格式化,减少格式化的时间。

spdlog的特征

  • 高速的日志记录:spdlog可以每秒处理百万条日志消息。
  • 低内存占用:spdlog的设计确保在高负载下依然保持低内存占用。
  • 灵活的配置:用户可以根据需要配置spdlog,选择异步(mt)或者同步(st),选择不同的日志级别和输出目标。

spdlog的输出控制

  • 多种日志级别:spdlog的日志级别包括:trace,debug,info,warn,error和critical。
  • 多目标:可以将日志输出到终端,文件和接收器(数据库、远程服务器等)。
  • 格式化输出:fmt支持高调的格式化输出

spdlog处理流程

spdlog的不同组件负责不同的部分。registry负责管理所有的组件,logger负责记录日志,sink负责将日志输出到指定位置,formatter将日志转化为特定格式,Async Logger可以异步将日志消息写入sink。

在这里插入图片描述

spdlog使用方法

使用默认logger打印输出

#include <chrono>
#include <iostream>
#include <memory>
#include <spdlog/async.h>
#include <spdlog/async_logger.h>
#include <spdlog/common.h>
#include <spdlog/logger.h>
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/basic_file_sink.h>

int main()
{
    spdlog::trace("hello trace");
    spdlog::debug("hello debug");
    spdlog::info("hello info");
    spdlog::warn("hello warn");
    spdlog::error("hello error");
    spdlog::critical("hello critical");
    return 0;
}

在这里插入图片描述

我们查看这些接口的源代码实现如下:

template <typename T>
inline void trace(const T &msg) {
    default_logger_raw()->trace(msg);
}

template <typename T>
inline void debug(const T &msg) {
    default_logger_raw()->debug(msg);
}

template <typename T>
inline void info(const T &msg) {
    default_logger_raw()->info(msg);
}

template <typename T>
inline void warn(const T &msg) {
    default_logger_raw()->warn(msg);
}

template <typename T>
inline void error(const T &msg) {
    default_logger_raw()->error(msg);
}

template <typename T>
inline void critical(const T &msg) {
    default_logger_raw()->critical(msg);
}

我们上面说过,logger是负责记录日志消息的,在spdlog中存在一个默认logger可以直接使用,不需要我们进行配置。通过上图的输出可以看出来:默认的logger只会输出info级别及其以上级别的日志。

通过工厂模式接口配置logger

int main()
{
    auto logger = spdlog::stdout_color_mt<spdlog::async_factory>("console");
    logger->set_pattern("***[%H:%M:%S %z][thread %t] %v***");
    logger->info("logger info");
    spdlog::get("console");
    spdlog::get("console")->error("logger error");

    return 0;
}

在这里插入图片描述

这边我们自己定义了一个logger,对他进行了输出的格式化,并且将已经打印的日志get出来,重新打印输出。这里之所以可以get出来是因为:从上面的结构图可以看出,logger是归registry管理的,我们通过单例registry将logger找到,换句话说,每一个logger在创建时,都是向registry注册。

源码接口:

namespace spdlog {

template <typename Factory>
SPDLOG_INLINE std::shared_ptr<logger> stdout_color_mt(const std::string &logger_name,
                                                      color_mode mode) {
    return Factory::template create<sinks::stdout_color_sink_mt>(logger_name, mode);
}

template <typename Factory>
SPDLOG_INLINE std::shared_ptr<logger> stdout_color_st(const std::string &logger_name,
                                                      color_mode mode) {
    return Factory::template create<sinks::stdout_color_sink_st>(logger_name, mode);
}

template <typename Factory>
SPDLOG_INLINE std::shared_ptr<logger> stderr_color_mt(const std::string &logger_name,
                                                      color_mode mode) {
    return Factory::template create<sinks::stderr_color_sink_mt>(logger_name, mode);
}

template <typename Factory>
SPDLOG_INLINE std::shared_ptr<logger> stderr_color_st(const std::string &logger_name,
                                                      color_mode mode) {
    return Factory::template create<sinks::stderr_color_sink_st>(logger_name, mode);
}
}
//这里是通过工厂模式的接口,有mt(异步)和st(同步)的接口。


SPDLOG_INLINE std::shared_ptr<logger> get(const std::string &name) {
    return details::registry::instance().get(name);
}
//从这里可以看出,单例registry可以get到logger

配置格式化打印(logger层面)

logger->set_pattern("***[%H:%M:%S %z][thread %t] %v***");

这一行就是配置格式化打印,配置方法可以参考文档中的表格:

在这里插入图片描述
在这里插入图片描述

配置输出位置sink & 配置格式化打印(sink层面)

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_text = "lenn-flag";
        dest.append(some_text.data(), some_text.data() + some_text.size());
    }

    std::unique_ptr<spdlog::custom_flag_formatter> clone() const override
    {
        return spdlog::details::make_unique<my_formatter_flag>();
    }
};

int main()
{
    auto logger1 = std::make_shared<spdlog::logger>(std::string("console1"));
    auto sink1 = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
    auto sink2 = std::make_shared<spdlog::sinks::basic_file_sink_mt>("lenn.log");

    auto formatter = std::make_unique<spdlog::pattern_formatter>();
    formatter->add_flag<my_formatter_flag>('*').set_pattern("[%n] [%*] [%^%l%$] %v");
    sink1