在 Qt 项目中使用 spdlog 的全攻略
目录
1. 准备工作:安装 spdlog
方法一:使用 CMake 的 FetchContent(推荐)
方法二:手动下载并添加到项目中
2. 在 Qt 项目中集成 spdlog
a. 初始化 spdlog
b. 在 Qt 的各个部分使用 spdlog
3. 基本使用示例
4. 高级配置:多种输出目标和格式化
a. 多种 Sink
b. 自定义格式化
5. 异步日志记录
6. 在 Qt 中处理线程安全
7. 常见问题与解决方案
问题1:日志文件没有生成或写入
问题2:日志级别不正确
问题3:异步日志丢失日志消息
1. 准备工作:安装 spdlog
首先,我们需要将 spdlog
添加到你的 Qt 项目中。spdlog
是一个头文件库,你可以通过以下几种方式来获取它:
方法一:使用 CMake 的 FetchContent(推荐)
如果你的 Qt 项目使用 CMake 构建,这是最简单的方法。
# 在你的 CMakeLists.txt 文件中添加以下内容
cmake_minimum_required(VERSION 3.14)
project(MyQtApp)
set(CMAKE_CXX_STANDARD 17)
# 引入 Qt
find_package(Qt5 COMPONENTS Widgets REQUIRED)
# 使用 FetchContent 下载 spdlog
include(FetchContent)
FetchContent_Declare(
spdlog
GIT_REPOSITORY https://github.com/gabime/spdlog.git
GIT_TAG v1.11.0 # 选择一个稳定的版本
)
FetchContent_MakeAvailable(spdlog)
# 添加可执行文件
add_executable(MyQtApp main.cpp mainwindow.cpp mainwindow.h mainwindow.ui)
# 链接 Qt 和 spdlog
target_link_libraries(MyQtApp PRIVATE Qt5::Widgets spdlog::spdlog)
方法二:手动下载并添加到项目中
- 前往 spdlog 的 GitHub 仓库。
- 下载最新的 release 版本(ZIP 文件)。
- 将
spdlog/include
文件夹复制到你的项目目录中(例如,放在external/spdlog
)。 - 在你的
CMakeLists.txt
中添加如下内容:
# 添加 spdlog 的包含路径
include_directories(${CMAKE_SOURCE_DIR}/external/spdlog/include)
# 假设你不需要构建 spdlog 库,而是作为头文件库使用
add_executable(MyQtApp main.cpp mainwindow.cpp mainwindow.h mainwindow.ui)
# 链接 Qt
target_link_libraries(MyQtApp PRIVATE Qt5::Widgets)
2. 在 Qt 项目中集成 spdlog
假设你已经将 spdlog
添加到项目中,接下来我们来配置和使用它。
a. 初始化 spdlog
在你的 Qt 项目的入口处( main.cpp
),初始化 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>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
// 初始化 spdlog
try {
// 创建一个同时输出到控制台和文件的 logger
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("logs/myapp.log", true);
std::vector<spdlog::sink_ptr> sinks {console_sink, file_sink};
auto logger = std::make_shared<spdlog::logger>("multi_sink", sinks.begin(), sinks.end());
// 设置全局日志器
spdlog::set_default_logger(logger);
spdlog::set_level(spdlog::level::info); // 设置日志级别
spdlog::flush_on(spdlog::level::info); // 设置自动刷新级别
}
catch (const spdlog::spdlog_ex &ex) {
fprintf(stderr, "日志初始化失败: %s\n", ex.what());
return 1;
}
return a.exec();
}
b. 在 Qt 的各个部分使用 spdlog
在你的 MainWindow
或其他类中使用 spdlog
来记录日志:
// mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <spdlog/spdlog.h>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
spdlog::info("应用程序已启动");
}
MainWindow::~MainWindow()
{
spdlog::info("应用程序正在关闭");
delete ui;
}
void MainWindow::on_someButton_clicked()
{
spdlog::info("按钮被点击了!");
// 你的其他代码
}
void MainWindow::on_errorCondition()
{
spdlog::error("发生了一个错误!");
// 错误处理代码
}
3. 基本使用示例
让我们来看一个简单的示例,展示如何在 Qt 应用中记录不同级别的日志。
// 在你的某个函数中
void MainWindow::performTask()
{
spdlog::debug("任务开始");
spdlog::info("任务进行中");
if (/* 某个条件 */) {
spdlog::warn("遇到警告条件");
}
try {
// 可能会抛出异常的代码
}
catch (const std::exception &e) {
spdlog::error("异常捕获: {}", e.what());
}
spdlog::critical("任务完成,但有严重问题!");
}
4. 高级配置:多种输出目标和格式化
a. 多种 Sink
spdlog
允许你将日志输出到多个目标(sinks),比如文件、控制台、甚至网络。
// 仅输出到控制台
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
// 仅输出到文件
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("logs/myapp.log", true);
// 创建包含多个 sinks 的 logger
std::vector<spdlog::sink_ptr> sinks {console_sink, file_sink};
auto logger = std::make_shared<spdlog::logger>("multi_sink", sinks.begin(), sinks.end());
// 设置为默认 logger
spdlog::set_default_logger(logger);
b. 自定义格式化
你可以定义日志的输出格式,使其更符合你的需求。
// 设置日志格式
// 例如: [2024-04-27 12:34:56.789] [info] [main.cpp:42] 这是一个信息日志
spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] [%s:%#] %v");
常用的格式化标识:
%Y-%m-%d %H:%M:%S
:日期和时间%l
:日志级别(info, error, etc.)%s
:源文件名%#
:源代码行号%v
:日志消息
5. 异步日志记录
对于需要高性能的应用,异步日志记录是个不错的选择。spdlog
提供了简便的异步日志支持。
#include <spdlog/async.h> // 需要包含这个头文件
#include <spdlog/sinks/basic_file_sink.h>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
try {
// 初始化线程池,队列大小为8192,线程数为1
spdlog::init_thread_pool(8192, 1);
// 创建一个异步文件日志器
auto async_file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("logs/async_log.txt", true);
auto async_logger = std::make_shared<spdlog::async_logger>("async_logger", async_file_sink, spdlog::thread_pool(), spdlog::async_overflow_policy::block);
// 将异步 logger 添加到默认 logger
spdlog::register_logger(async_logger);
spdlog::set_default_logger(async_logger);
spdlog::set_level(spdlog::level::info);
spdlog::flush_on(spdlog::level::info);
}
catch (const spdlog::spdlog_ex &ex) {
fprintf(stderr, "日志初始化失败: %s\n", ex.what());
return 1;
}
return a.exec();
}
6. 在 Qt 中处理线程安全
Qt 应用通常涉及多线程操作,spdlog
是线程安全的,这意味着你可以在多个线程中安全地记录日志而无需担心竞争条件。
// 在主线程中
void MainWindow::startBackgroundTask()
{
std::thread([=]() {
spdlog::info("后台任务开始");
// 执行一些耗时操作
spdlog::info("后台任务完成");
}).detach();
}
7. 常见问题与解决方案
问题1:日志文件没有生成或写入
解决方案:
- 确保指定的日志目录存在。例如,如果你指定
logs/myapp.log
,确保logs
目录已创建。 - 检查文件路径是否正确,尤其是在不同操作系统上的路径格式。
问题2:日志级别不正确
解决方案:
- 确保设置了正确的日志级别。例如,默认级别可能是
info
,如果你使用debug
级别的日志,可能不会输出。通过spdlog::set_level(spdlog::level::debug);
来调整。
问题3:异步日志丢失日志消息
解决方案:
-
在应用程序退出前,调用
spdlog::shutdown();
确保所有日志消息被写入。int main(int argc, char *argv[]) { // 初始化代码... int result = a.exec(); spdlog::shutdown(); // 确保所有日志被写入 return result; }
参考:
日志的艺术:深入理解 spdlog-CSDN博客