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

spdlog

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

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

一、spdlog 简介

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

二、下载与安装(Ubuntu)

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

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

bash 复制代码
sudo apt update
sudo apt install libspdlog-dev

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

方法 2:源码下载源码

bash 复制代码
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(如网络输出、数据库存储),满足特殊场景需求。

四、简单用法示例

基本日志输出

cpp 复制代码
#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;
}

编译运行

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

输出示例:

plaintext 复制代码
[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)管理

cpp 复制代码
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 个备份)。

示例:

cpp 复制代码
// 创建滚动日志器
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:日志内容

示例:

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

输出:

plaintext 复制代码
[2024-09-27 15:35:00] [info]  - 自定义格式的日志

3. 异步日志

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

cpp 复制代码
#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 绿色),方便实时调试时快速定位关键信息。

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

入门代码:

cpp 复制代码
#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 文件夹),否则会报错。

入门代码

cpp 复制代码
#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

cpp 复制代码
#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

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;  // 返回唯一实例
}

使用封装后的日志库

cpp 复制代码
#include "log.h"

int main() {
    Log::init("my_app.log"); // 初始化日志

    LOG_INFO("程序启动");
    int x = 42;
    LOG_DEBUG("x 的值为: {}", x);
    LOG_ERROR("错误示例");

    return 0;
}

2.整体版

cpp 复制代码
#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。

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

相关推荐
hweiyu002 小时前
从0手写自己的Linux x86操作系统(视频教程)
linux·运维·数据库
铭哥的编程日记2 小时前
《Linux 基础 IO 完全指南:从文件描述符到缓冲区》
android·linux·运维
lang201509282 小时前
MySQL Online DDL:高性能表结构变更指南
数据库·mysql
tpoog2 小时前
[C++项目框架库]redis的简单介绍和使用
开发语言·c++·redis
阿沁QWQ3 小时前
MySQL程序简介
数据库·mysql
一 乐3 小时前
社区互助养老系统|基于java和小程序的社区互助养老系统小程序设计与实现(源码+数据库+文档)
java·数据库·spring boot·小程序·论文·毕设·社区互助养老系统小程序
郝学胜-神的一滴3 小时前
深入理解 C++ 中的 `std::bind`:功能、用法与实践
开发语言·c++·算法·软件工程
1白天的黑夜13 小时前
优先级队列(堆)-1046.最后一块砖的重量-力扣(LeetCode)
c++·leetcode·优先级队列
努力学习的小廉3 小时前
我爱学算法之—— 模拟(上)
c++·算法