简介:CuteLogger 是一款专为 Qt 开发者设计的轻量级、高性能日志库,适用于需要精细调试与跨平台日志管理的 C++/Qt 项目,尤其适合中高级开发者构建可维护、可追踪的桌面或嵌入式应用。资源包共20个文件,含10个头文件(.h)用于接口定义与宏封装,9个源文件(.cpp)实现核心日志逻辑与各平台适配器,以及1个 pri 配置文件便于 Qt 项目快速集成,整体仅29KB,即下即用。已有925人学习下载,反映出其在 Qt 社区中的实用认可度。读者可直接获得完整可编译的日志系统工程,涵盖滚动文件、控制台、Android logcat 等多端输出能力,支持函数级溯源、时间测量、日志分类及线程安全机制,并能无缝替代 qDebug,同时提供清晰的模块划分(如 Appender 抽象层、Logger 核心、LogManager 管理器),便于理解日志框架设计思想与二次扩展。
1. CuteLogger 是什么:一个轻量 Qt 日志组件,不是 GUI 日志查看器,而是嵌入式友好的 C++ 日志后端
你搜 “CuteLogger.rar” 进来,大概率是被某篇博客里一句“Qt 项目秒加彩色日志”带进来的。但先泼一盆冷水:它不是那种带搜索框、折叠树、实时刷新界面的日志查看工具(比如 QCustomPlot + QTextEdit 拼出来的“Logger UI”),而是一个纯头文件、零依赖、可静态链接的 C++ 日志库,专为 Qt 项目设计,但核心逻辑完全不绑定 QWidget 或 QML。它的价值不在“看起来多酷”,而在“编译进去不增重、运行时不卡顿、出问题时能精准定位”。我把它用在某跨平台系统中——Windows 上跑 Qt Widgets 主程序,Linux ARM 设备上跑 Qt Quick 后端服务,同一套日志配置(级别、格式、输出目标)无缝复用。它支持控制台彩色输出(ANSI)、文件滚动写入、自定义 sink(比如发到串口或 UDP)、线程安全写入,且所有功能都通过宏开关控制,关掉彩色和文件功能后,整个日志模块编译体积不到 8KB。适合中小型 Qt 项目快速集成,也适合对启动时间和内存敏感的嵌入式 Qt 场景。如果你正被 qDebug() 堆满控制台却找不到关键报错,或者想把日志从 qDebug() 平滑迁移到可配置、可过滤、可持久化的方案,CuteLogger 就是那个“不用改业务逻辑就能加”的后悔药。
2. 编译与集成:头文件直连 + qmake / CMake 双路径,不改构建系统也能用
CuteLogger 的核心优势之一是“零构建侵入”——它没有 .cpp 文件,只有cute_logger.h和配套的cute_logger_config.h,所有实现都在头文件内完成模板展开。这意味着你不需要修改.pro或CMakeLists.txt去 add_library、find_package,只要让编译器能找到头文件路径,#include <cute_logger.h>就能直接用。但为了工程规范和长期可维护,我仍推荐显式集成进构建流程。下面分 qmake 和 CMake 两种主流方式说明,每一步都对应真实项目结构中的可验证操作。
2.1 头文件解压与目录规划:别放错位置,否则宏定义会静默失效
你下载的CuteLogger.rar解压后,典型结构是:
CuteLogger/ ├── cute_logger.h ├── cute_logger_config.h ├── examples/ │ ├── console_example.cpp │ └── file_example.cpp └── README.md提示:不要把整个
CuteLogger/目录直接扔进你的src/下。正确做法是将其作为第三方库子模块管理。我一般在项目根目录建3rdparty/cutelogger/,然后把cute_logger.h和cute_logger_config.h放进去。这样后续升级只需替换这两个文件,不影响其他代码。
为什么强调路径?因为cute_logger.h内部通过#include "cute_logger_config.h"相对引用配置文件。如果你把cute_logger.h单独拷进src/utils/,但cute_logger_config.h还在3rdparty/,编译会报cute_logger_config.h: No such file or directory;更隐蔽的是,如果两个文件都在同一目录但路径没加进 include path,它可能 fallback 到 Qt 自带的某个同名头文件(极小概率),导致宏开关失效——比如你明明定义了CUTE_LOGGER_ENABLE_FILE_SINK,日志就是不写文件,查半天才发现是头文件加载错了。
2.2 qmake 集成:三行代码搞定,但必须放在 HEADERS 之后
在你的.pro文件中,添加以下三行(顺序不能错):
# 1. 声明头文件路径(让 moc 和编译器都能找到) INCLUDEPATH += $$PWD/3rdparty/cutelogger # 2. 显式声明头文件(触发 qmake 正确处理依赖,避免 moc 错误) HEADERS += $$PWD/3rdparty/cutelogger/cute_logger.h \ $$PWD/3rdparty/cutelogger/cute_logger_config.h # 3. (可选)启用 C++17,因部分模板特性依赖它 CONFIG += c++17参数说明:
INCLUDEPATH是编译器找#include <>的路径,必须加;HEADERS不仅是 IDE 识别用,qmake 会据此生成正确的依赖关系,漏掉会导致修改cute_logger_config.h后不自动 recompile;c++17是安全起见——CuteLogger 使用了if constexpr和std::string_view,Qt 5.15+ 默认支持,但老项目若用 Qt 5.9,需确认编译器版本(GCC 7+/Clang 5+)。
验证是否生效:新建一个test_logger.cpp,内容如下:
#include <QCoreApplication> #include "cute_logger.h" // 注意:这里用双引号,因我们已加 INCLUDEPATH int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); // 初始化:设置默认级别为 INFO,控制台输出开启 cute::logger::init(cute::LogLevel::INFO); CUTE_LOG_INFO("CuteLogger 初始化成功"); CUTE_LOG_WARN("这是一条警告,颜色应为黄色"); CUTE_LOG_ERROR("这是一条错误,颜色应为红色"); return a.exec(); }执行qmake && make,运行后应看到带 ANSI 色彩的输出。若无颜色,可能是终端不支持(如 Windows CMD 旧版),稍后「避坑」章节会详解。
2.3 CMake 集成:add_subdirectory 不适用,用 target_include_directories 更稳妥
CMake 用户注意:不要用add_subdirectory(3rdparty/cutelogger),因为 CuteLogger 没有CMakeLists.txt,强行调用会失败。正确姿势是将其视为 interface library(接口库),只导出头文件路径:
# 在你的主 CMakeLists.txt 中(通常在 project() 之后) # 1. 定义一个 INTERFACE 库,仅用于传播头文件路径 add_library(CuteLogger INTERFACE) target_include_directories(CuteLogger INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/cutelogger> $<INSTALL_INTERFACE:include/cutelogger> ) # 2. 将其链接到你的可执行目标 add_executable(MyApp main.cpp) target_link_libraries(MyApp PRIVATE CuteLogger Qt5::Core) # 或 Qt6::Core逻辑说明:
INTERFACE库不生成任何二进制,只传递include_directories;$<BUILD_INTERFACE:...>是 CMake 的 generator expression,确保构建时路径正确,安装时路径可配;target_link_libraries会自动将CuteLogger的 include path 传给MyApp,无需再手动写include_directories()。
验证方法同 qmake:在main.cpp中#include "cute_logger.h"并调用cute::logger::init(),编译通过即集成成功。
3. 核心功能配置:从默认控制台到文件滚动,四个关键宏与两级初始化
CuteLogger 的行为由一组预处理器宏控制,它们决定了编译期包含哪些功能模块。这不是运行时配置,而是“编译什么、不编译什么”的开关。理解这组宏,等于掌握了它的体积、性能和能力边界。所有宏均在cute_logger_config.h中定义,默认值已做合理取舍,但生产环境几乎都需要调整。
3.1 四个必调宏:决定日志能否写文件、是否线程安全、色彩是否生效
打开3rdparty/cutelogger/cute_logger_config.h,你会看到类似这样的段落(实际以你下载版本为准,但结构一致):
// ====== 日志输出目标开关 ====== #define CUTE_LOGGER_ENABLE_CONSOLE_SINK 1 // 控制台输出(ANSI 彩色) #define CUTE_LOGGER_ENABLE_FILE_SINK 0 // 文件输出(需手动开启) #define CUTE_LOGGER_ENABLE_CUSTOM_SINK 0 // 自定义 sink(如串口、网络) // ====== 线程与性能 ====== #define CUTE_LOGGER_THREAD_SAFE 1 // 全局锁保护,多线程安全 #define CUTE_LOGGER_LOCK_FREE 0 // 若设为 1,则禁用 THREAD_SAFE,用 lock-free 队列(需额外依赖) // ====== 格式与功能 ====== #define CUTE_LOGGER_ENABLE_COLOR 1 // 彩色输出(依赖 CONSOLE_SINK) #define CUTE_LOGGER_ENABLE_TIMESTAMP 1 // 每条日志前加 [HH:MM:SS.mmm] #define CUTE_LOGGER_ENABLE_FILE_LINE 1 // 显示 __FILE__ 和 __LINE__参数说明与选型理由:
CUTE_LOGGER_ENABLE_FILE_SINK默认为 0,因为文件 I/O 有阻塞风险,嵌入式场景常禁用;但桌面应用强烈建议设为 1,并配合滚动策略;CUTE_LOGGER_THREAD_SAFE和CUTE_LOGGER_LOCK_FREE互斥:THREAD_SAFE=1用std::mutex,简单可靠;LOCK_FREE=1理论吞吐更高,但需boost::lockfree或 C++20std::atomic高级特性,新手慎开;CUTE_LOGGER_ENABLE_COLOR在 Windows 上需额外调用SetConsoleMode()启用虚拟终端,否则 ANSI 不生效——这是高频翻车点,后面「避坑」详述。
修改后,必须重新编译整个项目,因为宏影响模板实例化。例如,关闭FILE_SINK后,cute::logger::add_file_sink()函数将不存在,链接时报undefined reference。
3.2 两级初始化:全局 init() + 动态 add_sink(),灵活应对多目标输出
CuteLogger 初始化分两层:第一层是全局基础设置(日志级别、默认输出目标),第二层是按需添加具体 sink(控制台、文件、自定义)。这种设计让你能在程序启动时只开控制台,运行中根据用户配置再追加文件日志,避免启动即写文件的冗余。
#include "cute_logger.h" #include <QDir> int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); // === 第一层:全局初始化 === // 设置最低记录级别为 WARNING,低于此级别的日志(如 DEBUG/INFO)直接丢弃 cute::logger::init(cute::LogLevel::WARNING); // === 第二层:动态添加 sink === // 1. 控制台 sink(默认已存在,但显式添加可覆盖格式) cute::logger::add_console_sink(); // 2. 文件 sink:指定路径、最大单文件大小(字节)、最多保留几个历史文件 QString logPath = QDir::homePath() + "/myapp/logs/app.log"; cute::logger::add_file_sink( logPath.toStdString().c_str(), // const char* 10 * 1024 * 1024, // 10MB 5 // 保留 5 个历史文件(app.log.1 ~ app.log.5) ); // 3. (可选)自定义 sink:继承 cute::sink_t 并实现 write() // MyUdpSink udpSink("127.0.0.1", 8080); // cute::logger::add_sink(&udpSink); CUTE_LOG_INFO("这条不会输出:级别低于 WARNING"); CUTE_LOG_WARN("这条会输出到控制台和文件"); CUTE_LOG_ERROR("错误信息带时间戳、文件名、行号"); return a.exec(); }代码后逻辑说明:
init()必须在add_*_sink()之前调用,否则 sink 添加失败;add_file_sink()的第三个参数是“滚动文件数量”,不是“总日志大小”,当app.log达到 10MB,它会重命名为app.log.1,原app.log.1变app.log.2,以此类推,超过 5 个则删除最老的app.log.5;- 所有 sink 共享同一日志级别阈值(由
init()设置),无法为不同 sink 设置不同级别——这是设计取舍,简化了模型。
3.3 日志级别与宏语法:CUTE_LOG_X 宏如何展开为带上下文的语句
CuteLogger 提供六级宏:CUTE_LOG_TRACE,CUTE_LOG_DEBUG,CUTE_LOG_INFO,CUTE_LOG_WARN,CUTE_LOG_ERROR,CUTE_LOG_FATAL。它们不是简单printf,而是编译期展开为完整语句,自动注入__FILE__,__LINE__,__FUNCTION__和当前时间戳。
看CUTE_LOG_INFO的简化展开逻辑(实际更复杂,含线程 ID 和级别检查):
// 你写的: CUTE_LOG_INFO("User login: %s, status=%d", username.toStdString().c_str(), status); // 实际编译后等效于: do { if (cute::logger::get_level() <= cute::LogLevel::INFO) { cute::logger::log( cute::LogLevel::INFO, __FILE__, __LINE__, __FUNCTION__, "User login: %s, status=%d", username.toStdString().c_str(), status ); } } while(0)关键点:
do-while(0)包裹确保宏在if语句中能正确分号结尾,避免if(...) CUTE_LOG_INFO(...); else ...报错;get_level() <= LEVEL是编译期常量折叠(若级别被宏禁用,整条语句被优化掉),所以CUTE_LOG_DEBUG在init(INFO)时完全不产生指令;- 参数格式与
printf一致,支持%s,%d,%f等,但不支持%p打印指针(会崩溃),需转成uintptr_t:%lx。
4. 避坑与常见问题排查:Windows 彩色失效、文件权限拒绝、多线程卡死的血泪经验
集成 CuteLogger 最容易在三个地方翻车:Windows 终端不显示颜色、Linux 下文件写入 Permission denied、多线程高并发时日志卡住。这些问题网上搜不到直接答案,因为它们藏在 Qt、Windows API 和 C++ ABI 的夹缝里。以下是我在某图像处理 Demo 中踩过的坑,每一条都附带现象、根因和可复制的解决命令。
4.1 现象:Windows 控制台日志无颜色,全是白字;但同样代码在 VS Code 终端或 Windows Terminal 中正常
- 原因:Windows 10 以前的 CMD 和 PowerShell 默认禁用 ANSI 转义序列。CuteLogger 发送的
\033[33mWARN\033[0m被当成乱码。Qt Creator 内置终端也基于旧版控制台 API。 - 解决:在
main()开头强制启用虚拟终端模式。需包含<windows.h>,且必须在QApplication或QCoreApplication构造之前调用:
#ifdef Q_OS_WIN #include <windows.h> #endif int main(int argc, char *argv[]) { #ifdef Q_OS_WIN // 启用 ANSI 颜色支持(Windows 10+) HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); if (hOut != INVALID_HANDLE_VALUE) { DWORD dwMode = 0; GetConsoleMode(hOut, &dwMode); dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; SetConsoleMode(hOut, dwMode); } #endif QCoreApplication a(argc, argv); cute::logger::init(cute::LogLevel::INFO); CUTE_LOG_WARN("现在应该显示黄色了"); // ... }验证命令:在 CMD 中运行
reg query HKCU\Console /v VirtualTerminalLevel,返回0x1表示已启用。若未启用,上述代码会自动设置。
4.2 现象:add_file_sink()返回 false,日志文件创建失败,strace显示open() = -1 EACCES (Permission denied)
- 原因:Qt 项目在 Linux 上常以
setuid方式启动(如某公司设备守护进程),此时进程有效 UID 不是文件所有者,且目标目录(如/var/log/myapp/)权限为drwxr-x---,组外用户无写权限。CuteLogger 调用fopen()失败,但错误被静默吞掉。 - 解决:不硬编码绝对路径,改用
QStandardPaths获取用户可写目录,并确保父目录存在:
#include <QStandardPaths> #include <QDir> QString logDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/logs"; QDir().mkpath(logDir); // 递归创建目录 QString logPath = logDir + "/app.log"; cute::logger::add_file_sink( logPath.toStdString().c_str(), 5 * 1024 * 1024, 3 );参数说明:
QStandardPaths::AppDataLocation在 Linux 返回~/.local/share/MyApp,在 Windows 返回%LOCALAPPDATA%\MyApp,天然可写,规避权限问题。
4.3 现象:多线程环境下,某次CUTE_LOG_INFO调用后整个程序卡死,CPU 占用 100%,gdb 显示线程阻塞在std::mutex::lock()
- 原因:
CUTE_LOGGER_THREAD_SAFE=1时,CuteLogger 内部用static std::mutex s_mutex保护全局日志队列。但如果某个线程在持有该 mutex 期间调用了qDebug()或其他可能间接触发日志的 Qt 函数(如QFile::open()失败时的 warning),就会造成递归锁等待——线程 A 持有 s_mutex,调用 QFile,QFile 报错触发 Qt 自身日志,Qt 日志又尝试获取 s_mutex,死锁。 - 解决:永远不要在日志 sink 的 write() 函数内调用任何可能触发日志的代码。CuteLogger 的
add_console_sink()和add_file_sink()是安全的,但如果你写了自定义 sink,务必检查:
// ❌ 危险:自定义 sink 中调用 qDebug() class BadSink : public cute::sink_t { public: void write(const char* msg) override { qDebug() << "[BadSink]" << msg; // 触发 Qt 日志 → 可能死锁 } }; // ✅ 安全:只用 write() 系统调用,不碰 Qt class SafeSink : public cute::sink_t { public: void write(const char* msg) override { write(STDOUT_FILENO, msg, strlen(msg)); // 纯系统调用 write(STDOUT_FILENO, "\n", 1); } };4.4 现象:日志文件写入后立即被截断,每次只存最后几 KB,ls -l显示文件大小反复归零
- 原因:
add_file_sink()的第三个参数(历史文件数)设为 0。CuteLogger 认为“不保留历史”,于是每次启动都fopen(path, "w")清空重写,而非"a"追加。 - 解决:明确设置滚动数量,最小值为 1:
// ❌ 错误:0 表示不滚动,等同于每次都清空 cute::logger::add_file_sink("/tmp/app.log", 1024*1024, 0); // ✅ 正确:至少保留 1 个历史文件,实际效果是追加写入 cute::logger::add_file_sink("/tmp/app.log", 1024*1024, 1);底层逻辑:当
max_files=1,CuteLogger 用fopen(path, "a")打开;当max_files>1,才启用重命名滚动逻辑。设为 0 是文档未明说的陷阱。
5. 进阶技巧:自定义格式、异步写入、与 Qt Message Handler 无缝桥接
做到上一章,你已经能稳定输出带颜色、带时间戳的日志了。但真实项目往往需要更深的定制:比如把日志格式改成 JSON 便于 ELK 收集;比如避免文件 I/O 阻塞主线程;比如让qDebug()、qWarning()的输出也走 CuteLogger 统一管道。这些不是“锦上添花”,而是上线前必须解决的工程问题。下面三个技巧,每个我都已在某跨平台系统中实测落地。
5.1 自定义日志格式:从[INFO] 10:22:33 main.cpp:42到{"level":"INFO","ts":"2024-03-15T10:22:33.123Z","file":"main.cpp","line":42,"msg":"..."}
CuteLogger 允许你完全接管日志字符串的拼接过程。关键在于重载cute::logger::set_formatter(),传入一个函数对象,它接收cute::LogEntry&并返回std::string。LogEntry结构体公开了所有原始字段:level,timestamp,file,line,function,message。
#include <nlohmann/json.hpp> // 需提前安装 nlohmann/json(header-only) std::string json_formatter(const cute::LogEntry& entry) { nlohmann::json j; j["level"] = cute::level_to_string(entry.level); j["ts"] = entry.timestamp; // 注意:timestamp 是 std::string 格式 "HH:MM:SS.mmm" j["file"] = entry.file; j["line"] = entry.line; j["function"] = entry.function; j["msg"] = entry.message; return j.dump(); // 返回 JSON 字符串 } int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); cute::logger::init(cute::LogLevel::INFO); cute::logger::set_formatter(json_formatter); // 关键:设置自定义格式器 cute::logger::add_console_sink(); cute::logger::add_file_sink("/tmp/app.json.log", 1024*1024, 3); CUTE_LOG_INFO("User logged in"); // 输出:{"level":"INFO","ts":"10:22:33.123","file":"main.cpp","line":42,"function":"main","msg":"User logged in"} }参数说明与边界:
cute::level_to_string()是 CuteLogger 内置工具函数,返回"TRACE"/"DEBUG"等字符串;entry.timestamp是std::string,格式固定为HH:MM:SS.mmm,如需 ISO8601 全格式,需自己用std::chrono重做;- JSON 库非必需,你也可以用
sprintf拼接 CSV 或自定义分隔符格式,只要返回std::string即可。
5.2 异步文件写入:用 std::thread + 阻塞队列,彻底解除主线程 IO 依赖
add_file_sink()默认同步写入,高频率日志(如每毫秒一条)会让主线程卡顿。CuteLogger 本身不提供异步 sink,但你可以用标准库轻松封装一个。核心思路:创建独立线程,监听一个std::queue<std::string>,主线程只负责push,工作线程pop并fwrite。
#include <queue> #include <mutex> #include <condition_variable> #include <thread> class AsyncFileSink : public cute::sink_t { private: std::string m_path; std::queue<std::string> m_queue; std::mutex m_mutex; std::condition_variable m_cv; std::thread m_thread; std::atomic<bool> m_running{true}; void worker() { FILE* f = fopen(m_path.c_str(), "a"); if (!f) return; while (m_running) { std::string msg; { std::unique_lock<std::mutex> lock(m_mutex); m_cv.wait(lock, [this]{ return !m_queue.empty() || !m_running; }); if (!m_queue.empty()) { msg = std::move(m_queue.front()); m_queue.pop(); } } if (!msg.empty()) { fputs(msg.c_str(), f); fputs("\n", f); fflush(f); // 确保立即写入磁盘 } } fclose(f); } public: explicit AsyncFileSink(const std::string& path) : m_path(path) { m_thread = std::thread(&AsyncFileSink::worker, this); } ~AsyncFileSink() override { m_running = false; m_cv.notify_one(); if (m_thread.joinable()) { m_thread.join(); } } void write(const char* msg) override { std::lock_guard<std::mutex> lock(m_mutex); m_queue.emplace(msg); m_cv.notify_one(); } }; // 使用: AsyncFileSink asyncSink("/tmp/async.log"); cute::logger::add_sink(&asyncSink);关键设计点:
std::queue+std::mutex+std::condition_variable是 C++11 标准异步队列模式,无第三方依赖;fflush(f)确保日志不因缓冲区未满而延迟写入;- 析构函数中
m_running=false+notify_one()+join()保证线程安全退出,避免程序退出时日志丢失。
5.3 与 Qt Message Handler 桥接:让 qDebug()、qWarning() 全部重定向到 CuteLogger
这是最实用的技巧——你不必改满屏的qDebug(),只需一次注册,所有 Qt 自身日志(包括QFile、QNetworkAccessManager等内部 warning)都进入 CuteLogger 管道,统一格式、统一级别、统一输出目标。
#include <QMessageLogContext> #include <QMessageLogger> void qt_message_handler(QtMsgType type, const QMessageLogContext &context, const QString &msg) { // 将 Qt 日志类型映射为 CuteLogger 级别 cute::LogLevel level; switch (type) { case QtDebugMsg: level = cute::LogLevel::DEBUG; break; case QtInfoMsg: level = cute::LogLevel::INFO; break; case QtWarningMsg: level = cute::LogLevel::WARN; break; case QtCriticalMsg: level = cute::LogLevel::ERROR; break; case QtFatalMsg: level = cute::LogLevel::FATAL; break; default: level = cute::LogLevel::INFO; break; } // 构造日志消息:包含 Qt 上下文(文件、行号、函数)和原始消息 std::string full_msg = QString("[%1] %2") .arg(context.function ? context.function : "unknown") .arg(msg).toStdString(); // 调用 CuteLogger 原生 log 接口(绕过宏,避免二次格式化) cute::logger::log(level, context.file, context.line, context.function, "%s", full_msg.c_str()); } int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); // 注册 Qt 消息处理器(必须在 QApplication 构造前!) qInstallMessageHandler(qt_message_handler); cute::logger::init(cute::LogLevel::DEBUG); cute::logger::add_console_sink(); qDebug() << "This goes to CuteLogger"; // ✅ qWarning() << "Network timeout"; // ✅ QFile f("/nonexistent"); f.open(QIODevice::ReadOnly); // ✅ Qt 内部 warning 也被捕获 return a.exec(); }注意事项:
qInstallMessageHandler()必须在QApplication/QCoreApplication构造之前调用,否则无效;cute::logger::log()是底层接口,直接接受const char*格式化字符串,比宏更底层,避免了宏展开的嵌套问题;context.function可能为nullptr,需判空,否则std::string(nullptr)崩溃。
从那以后我每次新启 Qt 项目,第一件事就是把qt_message_handler注册和cute::logger::init()写进main.cpp开头,再也没为“为什么 qDebug 看不见”和“Qt 内部 warning 找不到”浪费过半小时。希望帮到你。
本文还有配套的精品资源,点击获取