qt cmake 使用 spdlog

在 Qt Creator 中使用 CMake 集成 spdlog 很简单。以下是详细步骤:

1. 添加 spdlog 依赖

直接包含头文件-head only

下载 spdlog 头文件到你的项目中:

cmake_minimum_required(VERSION 3.15)
project(MyQtProject)

# 查找 Qt
find_package(Qt6 REQUIRED COMPONENTS Core Widgets)

# 添加 spdlog 头文件路径(假设放在 third_party/spdlog/include)
include_directories(third_party/spdlog/include)

add_executable(MyQtProject
    main.cpp
    mainwindow.cpp
    mainwindow.h
)

target_link_libraries(MyQtProject PRIVATE Qt6::Core Qt6::Widgets)

target_compile_features(MyQtProject PRIVATE cxx_std_17)

2. 在代码中使用 spdlog

main.cpp 示例

#include <QApplication>
#include "mainwindow.h"
#include <spdlog/spdlog.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/sinks/stdout_color_sinks.h>

void setupLogging() {
    try {
        // 创建控制台日志器
        auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
        console_sink->set_level(spdlog::level::info);
        
        // 创建文件日志器
        auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("logs/qt_app.log", true);
        file_sink->set_level(spdlog::level::debug);
        
        // 创建组合日志器
        std::vector<spdlog::sink_ptr> sinks{console_sink, file_sink};
        auto logger = std::make_shared<spdlog::logger>("qt_logger", sinks.begin(), sinks.end());
        
        logger->set_level(spdlog::level::debug);
        logger->flush_on(spdlog::level::warn);
        
        spdlog::set_default_logger(logger);
        
        spdlog::info("Logging system initialized");
        
    } catch (const spdlog::spdlog_ex& ex) {
        std::cout << "Log initialization failed: " << ex.what() << std::endl;
    }
}

int main(int argc, char *argv[]) {
    // 先初始化日志系统
    setupLogging();
    
    QApplication app(argc, argv);
    
    spdlog::info("Qt Application started");
    
    MainWindow window;
    window.show();
    
    spdlog::debug("Main window shown");
    
    return app.exec();
}

mainwindow.h 示例

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <spdlog/spdlog.h>

class MainWindow : public QMainWindow {
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void onButtonClicked();

private:
    void setupUI();
};

#endif // MAINWINDOW_H

mainwindow.cpp 示例

#include "mainwindow.h"
#include <QPushButton>
#include <QVBoxLayout>
#include <QWidget>
#include <spdlog/spdlog.h>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent) {
    spdlog::info("MainWindow constructor called");
    setupUI();
}

MainWindow::~MainWindow() {
    spdlog::debug("MainWindow destroyed");
}

void MainWindow::setupUI() {
    auto centralWidget = new QWidget(this);
    auto layout = new QVBoxLayout(centralWidget);
    
    auto button = new QPushButton("Click me!", centralWidget);
    connect(button, &QPushButton::clicked, this, &MainWindow::onButtonClicked);
    
    layout->addWidget(button);
    setCentralWidget(centralWidget);
    
    spdlog::debug("UI setup completed");
}

void MainWindow::onButtonClicked() {
    spdlog::info("Button clicked!");
    
    // 带格式的日志
    spdlog::warn("User interaction at timestamp: {}", 
                 QDateTime::currentDateTime().toString().toStdString());
    
    // 错误日志示例
    try {
        // 一些可能失败的操作
        spdlog::debug("Processing button click...");
    } catch (const std::exception& e) {
        spdlog::error("Error in button click: {}", e.what());
    }
}

3. 配置 Qt Creator

构建项目:Qt Creator 会自动处理依赖

4. 高级配置

创建日志配置文件 logging.h

// logging.h
#pragma once
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/rotating_file_sink.h>
#include <memory>

class Logger {
public:
    static void initialize() {
        auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
        console_sink->set_level(spdlog::level::info);
        
        // 滚动文件,最大 5MB,保留 3 个文件
        auto file_sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
            "logs/app.log", 1024 * 1024 * 5, 3);
        file_sink->set_level(spdlog::level::debug);
        
        auto logger = std::make_shared<spdlog::logger>("qt_app", 
                      spdlog::sinks_init_list{console_sink, file_sink});
        
        logger->set_level(spdlog::level::debug);
        logger->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] [%s:%#] %v");
        
        spdlog::set_default_logger(logger);
        
        #ifdef NDEBUG
        spdlog::set_level(spdlog::level::info);
        #else
        spdlog::set_level(spdlog::level::debug);
        #endif
    }
};

这样你就可以在 Qt 项目中愉快地使用 spdlog 了,完全独立于 Qt 的日志系统!

在使用过程可能出现的问题

“我在cmake里面添加了include_directories(third_party),然后使用双引号的方法include,发现不行,换成尖括号的方式就可以了”
其实这是一个基础的问题,涉及到 C++ 头文件包含的搜索规则。让我详细解释一下:

头文件包含的搜索规则

双引号 "" 的搜索顺序:

  1. 当前源文件所在目录
  2. 当前源文件的父目录(递归向上)
  3. include_directories() 指定的目录
  4. 系统标准头文件路径

尖括号 <> 的搜索顺序:

  1. include_directories() 指定的目录
  2. 系统标准头文件路径
  3. 编译器特定的系统路径

你的情况分析

include_directories(third_party)  # 添加 third_party 到包含路径
#include "spdlog/spdlog.h"    // 可能失败
#include <spdlog/spdlog.h>     // 成功

为什么双引号可能失败:

  • 双引号会先在当前源文件目录查找 spdlog/spdlog.h
  • 如果当前目录没有 spdlog 文件夹,它会向上级目录查找
  • 最终会找到 include_directories(third_party),但搜索路径较长

为什么尖括号能成功:

  • 尖括号直接跳过本地目录搜索,直接查找 include_directories() 指定的路径
  • 更直接地找到 third_party/spdlog/spdlog.h

最佳实践建议

1. 使用更明确的包含路径

# 更明确的写法
include_directories(third_party/spdlog/include)

然后使用:

#include <spdlog/spdlog.h>  // 直接找到头文件

2. 使用 target_include_directories(推荐)

# 现代 CMake 写法
target_include_directories(MyQtProject PRIVATE 
    third_party/spdlog/include
)

3. 区分头文件类型的使用场景

使用双引号 "" 的情况:

  • 项目自己编写的头文件
  • 与当前源文件在同一目录或子目录的头文件
  • 相对路径包含
#include "mainwindow.h"
#include "ui/mainwindow_ui.h"
#include "../utils/helper.h"

使用尖括号 <> 的情况:

  • 第三方库头文件
  • 系统头文件
  • 通过 include_directories() 添加的路径
#include <spdlog/spdlog.h>
#include <QtWidgets/QMainWindow>
#include <iostream>
#include <vector>

修正后的 CMakeLists.txt

cmake_minimum_required(VERSION 3.15)
project(MyQtProject)

find_package(Qt6 REQUIRED COMPONENTS Core Widgets)

# 方法1:明确指定 spdlog 包含路径
include_directories(third_party/spdlog/include)

# 或者方法2:使用 target_include_directories(更推荐)
add_executable(MyQtProject
    main.cpp
    mainwindow.cpp
    mainwindow.h
)

target_include_directories(MyQtProject PRIVATE
    third_party/spdlog/include
)

target_link_libraries(MyQtProject PRIVATE Qt6::Core Qt6::Widgets)
target_compile_features(MyQtProject PRIVATE cxx_std_17)

代码中的包含方式

// 第三方库 - 使用尖括号
#include <spdlog/spdlog.h>
#include <spdlog/sinks/basic_file_sink.h>

// Qt 头文件 - 使用尖括号(因为是外部库)
#include <QApplication>
#include <QMainWindow>

// 自己的头文件 - 使用双引号
#include "mainwindow.h"
#include "utils/logger.h"

总结

  • 尖括号 <>:用于系统库和通过编译选项明确添加路径的第三方库
  • 双引号 "":用于项目自身的头文件和相对路径包含

在我的情况下,spdlog 是第三方库,使用尖括号是更合适和可靠的选择。

尖括号 <>-I 的关系

尖括号 <> 确实相当于编译时直接使用 -I 指定的路径。

CMake 到编译命令的转换

当你在 CMake 中写:

include_directories(third_party)
# 或者
target_include_directories(MyQtProject PRIVATE third_party)

CMake 在生成构建文件时,会转换成类似这样的编译命令:

g++ -Ithird_party -I/usr/include/qt6 ... main.cpp

实际验证

你可以查看 CMake 生成的实际编译命令:

方法1:查看构建目录的编译命令

# 在构建目录中
cat CMakeFiles/MyQtProject.dir/flags.make
# 或者
cat CMakeFiles/MyQtProject.dir/build.make

方法2:让 CMake 显示详细输出

# 在 CMakeLists.txt 中添加
set(CMAKE_VERBOSE_MAKEFILE ON)

方法3:使用 make 的 verbose 模式

make VERBOSE=1

你会看到实际的编译命令中包含:

g++ -I/path/to/your/project/third_party ... other_flags ...

搜索路径的层次结构

#include <spdlog/spdlog.h>

编译器会按顺序搜索:

  1. -Ithird_partythird_party/spdlog/spdlog.h
  2. 系统标准路径 /usr/include/
#include "spdlog/spdlog.h"  

编译器会按顺序搜索:

  1. 当前源文件目录
  2. 当前源文件的父目录(递归向上)
  3. -Ithird_partythird_party/spdlog/spdlog.h
  4. 系统标准路径

现代 CMake 的最佳实践

# 传统方式(全局影响)
include_directories(third_party)  # 相当于给所有target添加 -Ithird_party

# 现代方式(目标特定)
target_include_directories(MyQtProject PRIVATE 
    third_party
)
# 只给 MyQtProject 目标添加 -Ithird_party

PRIVATE/PUBLIC/INTERFACE 的区别

# PRIVATE: 只影响当前目标(-I 只出现在当前目标的编译命令中)
target_include_directories(MyQtProject PRIVATE third_party)

# PUBLIC: 影响当前目标和依赖它的目标
target_include_directories(MyQtProject PUBLIC third_party)

# INTERFACE: 只影响依赖当前目标的其他目标
target_include_directories(MyQtLibrary INTERFACE third_party)

总结

你的理解完全正确:

  • 尖括号 <> = 直接搜索 -I 指定的路径 + 系统路径
  • 双引号 "" = 先搜索本地路径,再搜索 -I 路径,最后系统路径

这就是为什么对于第三方库,使用尖括号更直接、更不容易出错的原因!

posted @ 2025-11-18 11:27  Tlink  阅读(25)  评论(0)    收藏  举报