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++ 头文件包含的搜索规则。让我详细解释一下:
头文件包含的搜索规则
双引号 "" 的搜索顺序:
- 当前源文件所在目录
- 当前源文件的父目录(递归向上)
include_directories()指定的目录- 系统标准头文件路径
尖括号 <> 的搜索顺序:
include_directories()指定的目录- 系统标准头文件路径
- 编译器特定的系统路径
你的情况分析
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>
编译器会按顺序搜索:
-Ithird_party→third_party/spdlog/spdlog.h✅- 系统标准路径
/usr/include/等
#include "spdlog/spdlog.h"
编译器会按顺序搜索:
- 当前源文件目录
- 当前源文件的父目录(递归向上)
-Ithird_party→third_party/spdlog/spdlog.h✅- 系统标准路径
现代 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路径,最后系统路径
这就是为什么对于第三方库,使用尖括号更直接、更不容易出错的原因!

浙公网安备 33010602011771号