代码改变世界

LinuxC++——spdlog日志运用入门

2025-10-02 11:58  tlnshuju  阅读(74)  评论(0)    收藏  举报

spdlog 库:Linux Ubuntu 环境下的使用指南

spdlog 是一个快速、易用的 C++ 日志库,以高性能和简洁的 API 著称,广泛用于 C++ 项目的日志记录。以下是其详细介绍和使用方法。

一、spdlog 简介

spdlog 是一个 - header-only(仅头文件)的日志库,支持多种日志输出目标(控制台、文件、滚动文件等),提供丰富的日志级别和格式化选项,且性能优异(无锁设计,支持异步日志)。

二、下载与安装(Ubuntu)

spdlog 无需编译安装,只需将头文件引入项目即可。

方法 1:通过包管理器安装(推荐)

sudo apt update
sudo apt install libspdlog-dev

安装后,头文件位于 `/usr/include/spdlog/,可直接在项目中引用。

方法 2:源码下载源码

git clone clone git clone https://github.com/gabime/spdlog.git
cd spdlog
# 只需将 include/spdlog 目录复制到项目或系统头文件路径
sudo cp -r include/spdlog /usr/local/include/

三、核心特点

  • 极致高性能:spdlog 采用无锁设计(多线程场景下避免锁竞争开销),且内部优化了日志缓存与 I/O 调度,即使高并发、高频日志输出场景,也能减少主线程阻塞,保持低延迟响应。

  • 零成本上手:无需编译链接静态 / 动态库,仅需包含头文件即可集成,省去复杂的环境配置步骤,尤其适合快速搭建项目或轻量级开发场景。

  • 异步日志解耦:支持异步日志模式(通过独立线程处理日志写入),主线程无需等待 I/O 操作完成,从根本上避免日志记录拖慢业务逻辑执行效率。

  • 灵活格式化:内置丰富的占位符(时间戳、线程 ID、文件名、行号等),支持自定义日志格式,既能满足调试时的详细上下文需求,也能适配生产环境的精简日志规范。

  • 跨平台无缝兼容:底层通过条件编译适配不同操作系统的 I/O 接口(如 Linux 的 write、Windows 的 WriteFile),无需修改代码即可在多平台运行,降低跨平台项目的日志层适配成本。

  • 易用性与扩展性兼顾:除了 trace/debug/info 等 6 级日志级别,还支持重载 << 操作符(如 log << "value: " << x),可直接记录任意支持流输出的数据类型,同时允许自定义日志 sink(如网络输出、数据库存储),满足特殊场景需求。

四、简单用法示例

基本日志输出

#include <spdlog/spdlog.h>
  int main() {
  // 控制台日志
  spdlog::info("这是一条 info 级别的日志");
  spdlog::warn("这是一条 warn 级别的日志:{}", 123); // 支持格式化
  spdlog::error("这是一条 error 级别的日志");
  // 设置全局日志级别(只输出 >= 该级别的日志)
  spdlog::set_level(spdlog::level::debug); // 默认为 info
  spdlog::debug("这条 debug 日志会被输出");
  // 关闭并释放所有日志器
  spdlog::shutdown();
  return 0;
  }

编译运行

g++ main.cpp -o main -std=c++11  # spdlog 需 C++11 及以上标准
./main

输出示例:

[2024-09-27 15:30:00.123] [info] 这是一条 info 级别的日志
[2024-09-27 15:30:00.124] [warn] 这是一条 warn 级别的日志:123
[2024-09-27 15:30:00.124] [error] 这是一条 error 级别的日志
[2024-09-27 15:30:00.124] [debug] 这条 debug 日志会被输出

五、常用功能与函数

1. 日志器(Logger)管理

spdlog::get("logger_name"):获取指定名称的日志器。
spdlog::basic_logger_mt("logger_name", "logfile.txt"):创建多线程安全的文件日志器。
spdlog::rotating_logger_mt("logger_name", "logfile.txt", 1024*1024, 3):创建滚动日志器(文件大小达 1MB 时轮转,保留 3 个备份)。

示例:

// 创建滚动日志器
auto rotating_logger = spdlog::rotating_logger_mt("rotating_logger", "app.log", 1024*1024, 3);
rotating_logger->info("写入滚动日志文件");

2. 日志格式设置

通过 set_pattern 自定义格式,常用占位符:

%Y-%m-%d %H:%M:%S.%e:日期时间(精确到毫秒)

%l:日志级别(info/warn 等)

%t:线程id

%n:日志器名称

%v:日志内容

示例:

// 设置全局格式
spdlog::set_pattern("[%Y-%m-%d %H:%M:%S] [%l]  - %v");
spdlog::info("自定义格式的日志");

输出:

[2024-09-27 15:35:00] [info]  - 自定义格式的日志

3. 异步日志

通过异步模式提高性能(避免 I/O 阻塞主线程):

#include <spdlog/async.h>
  #include <spdlog/sinks/basic_file_sink.h>
    int main() {
    // 初始化异步日志(队列大小 8192,线程数 1)
    spdlog::init_thread_pool(8192, 1);
    // 创建异步文件日志器
    auto async_file = spdlog::basic_logger_mt<spdlog::async_factory>("async_logger", "async.log");
      async_file->info("这是一条异步日志");
      spdlog::shutdown(); // 必须调用,确保队列中日志被写入
      return 0;
      }

4. spdlog 输出器(Sink)

在 spdlog 中,输出器(Sink)是日志的 “最终目的地载体”—— 它决定了日志会被输出到哪里(控制台、文件、系统日志等),是连接日志生成与存储 / 展示的核心组件。对于初学者,掌握输出器的基本类型、使用方法和组合逻辑,就能快速实现灵活的日志输出需求。

一、输出器的核心概念

定义:输出器是 spdlog 中负责 “写入日志” 的最小单元,每个输出器对应一个具体的输出目标(如控制台、单个文件、滚动文件)。

核心作用:解耦日志 “生成逻辑” 与 “输出逻辑”—— 日志器(Logger)负责收集日志内容,输出器负责将内容写入目标,二者配合实现 “一份日志多目标输出”(如同时输出到控制台和文件)。

线程安全:输出器名称通常带后缀 _mt(multi-thread,多线程安全)或 _st(single-thread,单线程),多线程程序必须使用 _mt 版本,避免日志错乱。

二、常用输出器类型及入门用法

spdlog 内置了多种常用输出器,覆盖绝大多数场景,以下是初学者必学的 4 种核心类型:
1. 彩色控制台输出器(stdout_color_sink_mt)
功能:将日志输出到终端,且按日志级别显示不同颜色(如 error 红色、info 绿色),方便实时调试时快速定位关键信息。

适用场景:开发阶段实时查看日志。
入门代码:

#include <spdlog/spdlog.h>
  #include <spdlog/sinks/stdout_color_sink_mt.h>
    int main() {
    // 1. 创建彩色控制台输出器(多线程安全)
    auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
      // 2. 创建日志器,绑定输出器
      auto console_logger = std::make_shared<spdlog::logger>(
        "console_logger",  // 日志器名称(自定义,用于后续获取)
        console_sink       // 绑定控制台输出器
        );
        // 3. 配置日志(可选:设置格式、级别)
        console_logger->set_pattern("[%H:%M:%S] [%l] %v");  // 时间 + 级别 + 内容
        console_logger->set_level(spdlog::level::info);     // 只输出 info 及以上级别
        // 4. 输出日志(会显示在控制台,带颜色)
        console_logger->info("这是彩色控制台日志(info 级,绿色)");
        console_logger->error("这是错误日志(error 级,红色)");
        // 5. 关闭日志(确保缓存写入)
        spdlog::shutdown();
        return 0;
        }

2. 基础文件输出器(basic_file_sink_mt)

功能:将日志写入指定文件,适合持久化存储日志(如生产环境归档)。

适用场景:需要保存日志用于后续排查问题。

注意:文件路径需确保目录存在(如 logs/app.log 需先创建 logs 文件夹),否则会报错。

入门代码

#include <spdlog/spdlog.h>
  #include <spdlog/sinks/basic_file_sink_mt.h>
    int main() {
    try {
    // 1. 创建基础文件输出器(多线程安全,日志写入 "app.log")
    auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("app.log");
      // 2. 创建日志器,绑定文件输出器
      auto file_logger = std::make_shared<spdlog::logger>("file_logger", file_sink);
        // 3. 配置:输出所有级别日志(trace 最低)
        file_logger->set_level(spdlog::level::trace);
        // 4. 输出日志(会写入 app.log 文件)
        file_logger->trace("详细调试日志(仅文件可见)");
        file_logger->warn("警告日志(也写入文件)");
        } catch (const spdlog::spdlog_ex& ex) {
        // 捕获日志初始化失败(如文件权限不足)
        std::cerr << "日志初始化失败:" << ex.what() << std::endl;
        }
        spdlog::shutdown();
        return 0;
        }

六、二次封装

1.头文件版

在大型项目中,通常会对 spdlog 进行二次封装,以统一日志配置、简化调用,并支持全局开关等功能。
封装示例(log.h 和 log.cpp)

log.h

#ifndef LOG_H  // 防止头文件被重复包含(预处理指令)
#define LOG_H  // 定义宏LOG_H,标识头文件已包含
#include <spdlog/spdlog.h>  // 包含spdlog库的头文件,使用其日志功能
  #include <string>           // 包含字符串处理头文件
    // 日志封装类(单例模式):确保全局只有一个日志实例,统一管理日志配置
    class Log {
    public:
    // 初始化日志(控制台 + 文件输出)
    // 参数:log_file - 日志文件路径,默认值为"app.log"
    static void init(const std::string& log_file = "app.log");
    // 获取单例实例:全局唯一的Log对象入口
    static Log& get_instance();
    // 日志输出接口(trace级别)
    // 模板参数Args...:支持可变参数(如日志中包含的变量)
    // 参数:fmt - 日志格式字符串(如"x = {}");args... - 格式字符串中的占位符对应的值
    template<typename... Args>
      void trace(const char* fmt, const Args&... args) {
      logger_->trace(fmt, args...);  // 调用spdlog的trace级别日志输出
      }
      // 日志输出接口(info级别),参数含义同上
      template<typename... Args>
        void info(const char* fmt, const Args&... args) {
        logger_->info(fmt, args...);   // 调用spdlog的info级别日志输出
        }
        // 日志输出接口(error级别),参数含义同上
        template<typename... Args>
          void error(const char* fmt, const Args&... args) {
          logger_->error(fmt, args...);  // 调用spdlog的error级别日志输出
          }
          // 其他级别(debug/warn/critical)类似...
          private:
          Log() = default;  // 私有构造函数:禁止外部创建对象(单例模式核心)
          ~Log() = default; // 私有析构函数:禁止外部销毁对象
          // 禁用拷贝构造和赋值操作:防止单例被复制
          Log(const Log&) = delete;
          Log& operator=(const Log&) = delete;
          std::shared_ptr<spdlog::logger> logger_;  // 日志器指针:spdlog的核心对象,负责实际日志输出
            };
            // 宏定义,简化调用(自动添加文件名和行号)
            // __FILE__:当前源文件路径(预定义宏)
            // __LINE__:当前代码行号(预定义宏)
            // __VA_ARGS__:替换宏调用时传入的所有参数
            #define LOG_TRACE(...) Log::get_instance().trace("[{}:{}] {}", __FILE__, __LINE__, __VA_ARGS__)
            #define LOG_INFO(...)  Log::get_instance().info("[{}:{}] {}", __FILE__, __LINE__, __VA_ARGS__)
            #define LOG_ERROR(...) Log::get_instance().error("[{}:{}] {}", __FILE__, __LINE__, __VA_ARGS__)
            #endif // LOG_H  // 结束头文件保护宏

log.cpp

#include "log.h"  // 包含自定义日志头文件
#include <spdlog/sinks/stdout_color_sinks.h>  // 包含带颜色的控制台输出器头文件
  #include <spdlog/sinks/basic_file_sink.h>     // 包含文件输出器头文件
    // 初始化日志函数实现
    void Log::init(const std::string& log_file) {
    // 多输出目标(控制台 + 文件):sinks是输出器的集合
    std::vector<spdlog::sink_ptr> sinks;
      // 添加带颜色的控制台输出器(_mt表示多线程安全)
      // spdlog::sinks::stdout_color_sink_mt:spdlog提供的带颜色的控制台输出器类
      // std::make_shared:创建智能指针(自动管理内存)
      sinks.push_back(std::make_shared<spdlog::sinks::stdout_color_sink_mt>());
        // 添加文件输出器(_mt表示多线程安全)
        // 参数:log_file - 日志文件路径
        sinks.push_back(std::make_shared<spdlog::sinks::basic_file_sink_mt>(log_file));
          // 创建日志器(logger):管理输出器的核心对象
          // 参数1:日志器名称("main_logger");参数2-3:输出器集合的起止迭代器
          auto logger = std::make_shared<spdlog::logger>("main_logger", sinks.begin(), sinks.end());
            // 设置日志格式
            // 格式说明:
            // [%Y-%m-%d %H:%M:%S.%e]:日期时间(年-月-日 时:分:秒.毫秒)
            // [%l]:日志级别(如info/error)
            // %v:日志内容(包括宏添加的文件名、行号和用户输入的内容)
            logger->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] %v");
            // 设置全局日志级别:trace(最低级别,所有日志都输出)
            // 可选级别:trace < debug < info < warn < error < critical
            logger->set_level(spdlog::level::trace);
            // 注册为默认日志器:后续spdlog::xxx()调用会使用该日志器
            spdlog::set_default_logger(logger);
            }
            // 获取单例实例函数实现
            Log& Log::get_instance() {
            static Log instance;  // 静态局部变量:第一次调用时创建,全局唯一(单例核心)
            // 若未初始化(logger_为空),使用spdlog的默认日志器
            if (!instance.logger_) {
            instance.logger_ = spdlog::default_logger();
            }
            return instance;  // 返回唯一实例
            }

使用封装后的日志库

#include "log.h"
int main() {
Log::init("my_app.log"); // 初始化日志
LOG_INFO("程序启动");
int x = 42;
LOG_DEBUG("x 的值为: {}", x);
LOG_ERROR("错误示例");
return 0;
}

2.整体版

#pragma once
#include <cstddef>
  #include <memory>
    #include <string>
      #include <vector>
        #include <spdlog/spdlog.h>
          #include <spdlog/async.h>
            #include <spdlog/async_logger.h>
              #include <spdlog/sinks/stdout_color_sinks.h>
                #include <spdlog/sinks/basic_file_sink.h>
                  namespace LogModule
                  {
                  enum OutputMode
                  {
                  CONSOLE_ONLY,
                  FILE_ONLY,
                  BOTH
                  };
                  typedef struct LogInfo
                  {
                  OutputMode outmode = CONSOLE_ONLY;
                  bool is_debug_ = true;
                  std::string logfile_ = "logfile.txt";
                  bool is_async_ = false;
                  size_t queue_size_ = 1 << 12;
                  size_t thread_num_ = 1;
                  } LogInfo_t;
                  class Log
                  {
                  public:
                  static void Init(const LogInfo_t& loginfo = LogInfo_t())
                  {
                  logconf_ = loginfo;
                  create_logger();
                  }
                  static Log& GetInstance()
                  {
                  static Log log;
                  return log;
                  }
                  template<typename... Args>
                    void trace(const char* fmt, const Args&... args)
                    {
                    if(logger_)
                    logger_->trace(fmt, args...);
                    }
                    template<typename... Args>
                      void info(const char* fmt, const Args&... args)
                      {
                      if(logger_)
                      logger_->info(fmt, args...);
                      }
                      template<typename... Args>
                        void debug(const char* fmt, const Args&... args)
                        {
                        if(logger_)
                        logger_->debug(fmt, args...);
                        }
                        template<typename... Args>
                          void warn(const char* fmt, const Args&... args)
                          {
                          if(logger_)
                          logger_->warn(fmt, args...);
                          }
                          template<typename... Args>
                            void error(const char* fmt, const Args&... args)
                            {
                            if(logger_)
                            logger_->error(fmt, args...);
                            }
                            template<typename... Args>
                              void critical(const char* fmt, const Args&... args)
                              {
                              if(logger_)
                              logger_->critical(fmt, args...);
                              }
                              static void shutdown()
                              {
                              spdlog::shutdown();
                              }
                              private:
                              Log() = default;
                              ~Log() = default;
                              Log& operator=(const Log&) = delete;
                              Log(const Log&) = delete;
                              static void create_logger()
                              {
                              std::vector<spdlog::sink_ptr> sinks;
                                if(logconf_.outmode == CONSOLE_ONLY || logconf_.outmode == BOTH)
                                {
                                auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
                                  sinks.push_back(console_sink);
                                  }
                                  if(logconf_.outmode == FILE_ONLY || logconf_.outmode == BOTH)
                                  {
                                  auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(logconf_.logfile_);
                                    sinks.push_back(file_sink);
                                    }
                                    spdlog::level::level_enum lenum =
                                    logconf_.is_debug_ ? spdlog::level::trace : spdlog::level::info;
                                    if(logconf_.is_async_)
                                    {
                                    spdlog::init_thread_pool(logconf_.queue_size_, logconf_.thread_num_);
                                    logger_ = std::make_shared<spdlog::async_logger>(
                                      "mainlog", sinks.begin(), sinks.end(),
                                      spdlog::thread_pool(),
                                      spdlog::async_overflow_policy::block);
                                      }
                                      else
                                      {
                                      logger_ = std::make_shared<spdlog::logger>(
                                        "mainlog", sinks.begin(), sinks.end());
                                        }
                                        logger_->set_level(lenum);
                                        spdlog::set_default_logger(logger_);  // 重要:设置默认日志器
                                        logger_->set_pattern("[%Y-%m-%d %H:%M:%S.%e][%t][%-8l]%v");
                                        }
                                        private:
                                        static inline std::shared_ptr<spdlog::logger> logger_;
                                          static inline LogInfo_t logconf_;
                                          };
                                          };
                                          // // 使用简化版本的宏定义
                                          // #define LOG_TRACE(...) LogModule::Log::GetInstance().trace(__VA_ARGS__)
                                          // #define LOG_INFO(...) LogModule::Log::GetInstance().info(__VA_ARGS__)
                                          // #define LOG_DEBUG(...) LogModule::Log::GetInstance().debug(__VA_ARGS__)
                                          // #define LOG_WARN(...) LogModule::Log::GetInstance().warn(__VA_ARGS__)
                                          // #define LOG_ERROR(...) LogModule::Log::GetInstance().error(__VA_ARGS__)
                                          // #define LOG_CRITICAL(...) LogModule::Log::GetInstance().critical(__VA_ARGS__)
                                          // 修改后的正确宏定义:
                                          #define LOG_TRACE(fmt, ...) LogModule::Log::GetInstance().trace("[{}:{}] " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
                                          #define LOG_INFO(fmt, ...) LogModule::Log::GetInstance().info("[{}:{}] " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
                                          #define LOG_DEBUG(fmt, ...) LogModule::Log::GetInstance().debug("[{}:{}] " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
                                          #define LOG_WARN(fmt, ...) LogModule::Log::GetInstance().warn("[{}:{}] " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
                                          #define LOG_ERROR(fmt, ...) LogModule::Log::GetInstance().error("[{}:{}] " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
                                          #define LOG_CRITICAL(fmt, ...) LogModule::Log::GetInstance().critical("[{}:{}] " fmt, __FILE__, __LINE__, ##__VA_ARGS__)

七、注意事项

线程安全:多线程环境下,使用 _mt 后缀的日志器(如 basic_logger_mt),单线程可用 _st 后缀(性能更好)。

日志级别:发布版本建议将级别设为 info 或更高,避免 debug/trace 日志影响性能。

异步日志:必须调用 spdlog::shutdown() 确保所有日志被写入,否则可能丢失数据。

编译选项:需启用 C++11 及以上标准(-std=c++11),部分功能(如格式化)依赖 C++17 时需指定 -std=c++17。

性能优化:高频日志场景建议使用异步模式,并合理设置日志级别过滤无关信息。