[C++]Qt日志与调试进阶:性能、自定义格式化与编译优

【QT日志/告警信息】

QT日志级别映射表

在 Qt 开发和 Linux 系统运维中,常用的日志级别按严重程度从低到高分为以下几类,不同框架/工具的命名略有差异,但核心含义一致:
qDebug 
1. DEBUG:调试信息,用于开发阶段排查问题,记录详细的程序运行状态(如变量值、函数调用流程),生产环境一般关闭。
​qInfo
2. INFO:普通信息,记录程序正常运行的关键节点(如服务启动成功、用户登录),用于跟踪系统状态。
​qWarning
3. WARNING:警告信息,表示存在潜在风险但不影响程序继续运行(如配置项缺失但已使用默认值、磁盘空间不足阈值)。
​qCritical
4. ERROR:错误信息,记录程序运行中出现的故障(如文件读取失败、接口调用超时),会影响部分功能,但程序不会崩溃。
​qFatal
5. CRITICAL:严重错误,记录导致程序无法继续运行的致命问题(如数据库连接失败、权限不足无法启动核心服务),需要立即处理。

>>QDebug 耗时

 QDebug 主要用于输出调试信息。它的耗时通常是比较短的,但具体耗时会受到多种因素的影响,如输出的内容长度、输出的目标(例如是输出到控制台还是文件)等。在一般情况下,简单的 QDebug 输出一条基本信息(如一个整数或者短字符串)的耗时在微秒级别,这个时间对于大多数非实时性要求极高的应用程序来说几乎可以忽略不计。不过如果在一个非常紧凑的循环中大量使用 QDebug 输出很长的字符串,或者频繁地刷新输出缓冲区(比如在每次 QDebug 调用后立即刷新缓冲区),可能会导致性能下降。

>> QMessageLogger::information (通常是 qInfo 宏背后的实现)耗时

qInfo (或者 QMessageLogger::information )主要用于输出信息级别的日志消息。它的耗时和 QDebug 类似,也是比较短的。它在记录信息时可能会涉及一些额外的日志级别判断和可能的日志分发操作(例如,根据日志配置将消息发送到不同的输出目标)。但在简单的使用场景下,输出一条普通信息的耗时也在微秒级别。不过和 QDebug 一样,如果在性能敏感的区域(如频繁调用的函数内部或者紧密的循环中)大量使用,并且日志输出的内容过多或者日志系统配置复杂,也可能会产生一定的性能开销。

【控制调试输出】

QT_NO_DEBUG_OUTPUT

【磁盘操作】

C:\User\Desktop\poem.txtà” C:\\User\\Desktop\\poem.txt” or “C:/User/Desktop/poem.txt”

MySQL的日志缓冲区是一个内存区域,用于缓存待写入到磁盘的日志数据。它可以减少磁盘IO操作的次数,提高系统的性能。MySQL的日志缓冲区包括redo log buffer和binlog cache buffer两部分,分别用于缓存InnoDB存储引擎中的redo log和MySQL服务器中的binlog。       

原文链接:https://blog.csdn.net/qq_36777143/article/details/130560589

【建立缓冲区】

[QT] 一种创建缓冲区方法:优化程序性能的利器_qt缓冲区怎么写-CSDN博客

https://blog.csdn.net/weixin_68016945/article/details/130625556

【线程分配】

//【QT】

Static mutex Qmutex;

QmutexLocker locker(&Qmutex);

//【C++】

Function_Example(){

Lock_guard<mutex> guard(LogMutex);

}

Thread thread_one(Function_Example);

thread_one.join();

【编译瓶颈】

当工程代码量大的时候,尤其大量引用第三方类库不当的时候,会导致编译速度下降,严重影响工作效率。

 一. 并行编译

1、如果是VC++编译器,可以在 .pro里加入下面一行
QMAKE_CXXFLAGS += /MP

  或者:

*msvc* {

QMAKE_CXXFLAGS += /MP

}

  指定/mp编译选项,编译器将使用并行编译,同时起多个编译进程并行编译不同的cpp。

2、如果是MinGW编译器:
Projects->Build Settings->Build Steps->make ,参数中填入-j8 (后面的数字是需要指定编译的核数)

  二.预编译头文件

  编译时间长,很大一部分时间都是花在预编译上,尤其是头文件各种包含。

1.在pro文件中添加:
  PRECOMPILED_HEADER = <path_to_your_pch_file>

CONFIG += precompile_header  
2..h文件中使用前置声明, 所有的.cpp 文件中包含 预编译的头文件

 

三.删除多余的Q_Object宏

1.如果一个类中,不使用信号,槽,那就没必要使用Q_Object宏
2.在.cpp文件中的最后,包含相应的moc_*.cpp文件
    例如:a.cpp ,moc后,会生成moc_a.cpp, 所以在a.cpp文件的最后,添加#include"moc_a.cpp", 会提高编译速度。

四.过多使用全局变量

寄存器变量直接存储在CPU寄存器中,而非内存单元。这种存储方式显著提升了访问速度,尤其适用于频繁使用的变量(如循环变量)。由于CPU寄存器访问速度远高于内存,使用寄存器变量能有效减少程序运行时的延迟。

全局变量默认分配在内存中,无法直接存储在寄存器上。全局变量可能被指针或函数修改,导致不可预测的行为。频繁访问全局变量会增加内存访问次数,降低程序执行效率。

将全局变量赋值给临时变量进行操作是一种高效策略。临时变量可存储在寄存器中,减少内存访问次数。操作完成后,将临时变量的值赋回全局变量。这种方法尤其适用于循环结构,能显著提升性能。

int f(void);
int g(void);
int errs;
void test1(void){
  errs += f();

  errs += g();
}


void test2(void){
  int localerrs = errs;
  localerrs += f();
  localerrs += g();
  errs = localerrs;
}

【C++11特性】register关键字不再表示是寄存器变量,而只是显式指出变量是自动的。与auto关键字的作用相同。

【QT日志库】

//这是一个日志库

//它考虑了日志级别、线程安全、输出格式(文本、控制台)

#include <iostream> //读写库

#include <fstream> //文件输入输出库

#include <mutex> //互斥锁

#include <string> //数据结构库

#include <ctime> //时间库

#include <iomanip> //输入输出格式化库

#include <sstream> // stringstream库

#include <thread> //线程库

#include <chrono> //时间库



#define _FILE_ “this is a txt file!” //字符串宏定义

#define _LINE_ 25 //数值宏定义

#define _FUNCTION_ “SweepingDividedRegion” //字符串宏定义



class Logger { //名为Logger的类

public: //公共变量声明

    static Logger& getInstance() {

        static Logger instance;

        return instance;

    } //单例模式通用接口



    void logInfo(const std::string& message, const char* file, int line, const char* function) {

        log("INFO", message, file, line, function);

} //日志级别为INFO的实例对象

//参数解读:message:日志信息

//file:文件地址

//line:行号

//function:函数名

//通用功能封装log()

private://私有变量声明

    Logger() {

        LogFile.open("log.txt", std::ios::app); // 打开日志文件

        if (!LogFile.is_open()) {

            std::cerr << "Failed to open log file!" << std::endl;

        }

    }



    ~Logger() {

        if (LogFile.is_open()) {

            LogFile.close(); // 关闭日志文件

        }

    }



    std::string getCurrentTimestamp() {

        auto now = std::chrono::system_clock::now();

        auto now_time_t = std::chrono::system_clock::to_time_t(now);

        std::tm now_tm = *std::localtime(&now_time_t);

        std::ostringstream oss;

        oss << std::put_time(&now_tm, "%Y-%m-%d %H:%M:%S");

        return oss.str();

    }



    void log(const std::string& level, const std::string& message, const char* file, int line, const char* function) {

        std::lock_guard<std::mutex> guard(logMutex);

        std::ostringstream oss;

        oss << getCurrentTimestamp() << " [" << level << "] " << message

            << " (File: " << file << ", Line: " << line << ", Function: " << function

            << ", Thread ID: " << std::this_thread::get_id() << ")"

            << std::endl;



        // 输出到文件

        logFile << oss.str();

        // 输出到控制台

        std::cout << oss.str();

    }



std::ofstream logFile; // 日志文件流

std::ifstream LogFile

    std::mutex logMutex;   // 互斥锁

};



// 模拟耗时的循环

void timeConsumingLoop(int iterations) {

    for (int i = 0; i < iterations; ++i) {

        // 模拟耗时操作

        std::this_thread::sleep_for(std::chrono::milliseconds(500)); // 每次迭代等待 500 毫秒



        // 记录日志

        Logger::getInstance().logInfo("Iteration " + std::to_string(i + 1) + " completed.", __FILE__, __LINE__, __FUNCTION__);

    }

}

// 使用示例

void threadFunction() {

    Logger::getInstance().logInfo("This is an info message from thread.", __FILE__, __LINE__, __FUNCTION__);

}

int main() {

    std::thread t1(threadFunction);

    t1.join();

    const int iterations = 5; // 设置循环次数

    std::thread worker(timeConsumingLoop, iterations); // 创建线程执行耗时循环

    worker.join(); // 等待线程完成

    std::thread t2(threadFunction);

    t2.join();    

return 0;

}

五、重载技术

在QT中,继承现有的qDebug,然后重写qDebug实现,并为其添加“一旦使用就能自动输出该行的行号、所在文件、所在函数、时间戳”的功能

#include <QDebug>

2#include <QDateTime>

3

4class CustomDebug : public QDebug {

5public:

6    CustomDebug() : QDebug(stdout) {}

7

8    // 重载输出运算符

9    CustomDebug& operator<<(const QString &msg) {

10        // 获取当前时间戳

11        QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss");

12        

13        // 输出调试信息

14        *this << QString("[%1] [%2:%3] [%4] %5")

15                 .arg(timestamp)

16                 .arg(__FILE__)

17                 .arg(__LINE__)

18                 .arg(__FUNCTION__)

19                 .arg(msg);

20        return *this;

21    }

22};

void exampleFunction() {

2    CustomDebug() << "This is a debug message";

3}

 

//最终采纳方案

#include <QDebug>

2#include <QDateTime>

3

4// 自定义调试宏

5#define CUSTOM_DEBUG(msg) \

6    do { \

7        QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss"); \

8        qDebug() << QString("[%1] [%2:%3] [%4] %5") \

 

9                    .arg(timestamp) \

10                    .arg(__FILE__) \

11                    .arg(__LINE__) \

12                    .arg(__FUNCTION__) \

13                    .arg(msg); \

14    } while (0)

void exampleFunction() {

2    CUSTOM_DEBUG("This is a debug message");

3}

#include <QDebug>

2#include <QDateTime>

3

4class CustomDebug {

5public:

6    CustomDebug() {

//CustomDebug() = default;

7

7        // 初始化时可以做一些设置

8    }

9

10    // 重载输出运算符

11    template<typename T>

12    CustomDebug& operator<<(const T& msg) {

13        // 获取当前时间戳

14        QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss");

15

16        // 输出调试信息

17        qDebug() << QString("[%1] [%2:%3] [%4] %5")

18                    .arg(timestamp)

19                    .arg(__FILE__)

20                    .arg(__LINE__)

21                    .arg(__FUNCTION__)

22                    .arg(msg);

23        return *this;

24    }

25};

26

27// 使用自定义调试类的宏

28#define CUSTOM_DEBUG CustomDebug()

void exampleFunction() {

2    CUSTOM_DEBUG << "This is a debug message";

3}

Qdebug私有成员变量,无法直接继承。

//最终采纳方案

8 #define  MyDebug() qDebug() << QString("[%1] [%2:%3] [%4] %5") \

9                    .arg(timestamp) \

10                    .arg(__FILE__) \

11                    .arg(__LINE__) \

12                    .arg(__FUNCTION__) \

13                    .arg(msg); \

要使得所有的qDebug()语句失效,可以在Qt的.pro文件中添加一个宏定义,例如使用DEFINES += QT_NO_DEBUG_OUTPUT。这样,编译时会禁用所有的调试输出。 要使得所有的qDebug()语句失效,可以在Qt的.pro文件中添加一个宏定义,例如使用DEFINES += QT_NO_DEBUG_OUTPUT。这样,编译时会禁用所有的调试输出。

QT_NO_DEBUG_OUTPUT 宏实现 - 邓加领 - 博客园 (cnblogs.com) https://www.cnblogs.com/dengjialing/p/17236638.html

_ _FILE_ _   当前文件的相对路径

_ _FUNCTION_ _ 当前函数的全称名字

_ _DATE_ _ 当前的月日年

_ _TIME_ _ 当前时间

_ _func_ _ 当前函数的名字,不是全称

_ _LINE_ _ 当前行号

QmessageLogContext

【Qt】Qt日志信息处理_qmessagelogcontext-CSDN博客

https://blog.csdn.net/qq_65207641/article/details/140897099

【修订】

1<<

QString filename;

filename.sliced()

Qt的QString用法,修改、截取,查找_qstring修改-CSDN博客

https://blog.csdn.net/qq_41429220/article/details/96869586

2<<

static Mutex mutex;

QmutexLocker<mutex> locker(&mutex)

QmutexLocker locker(&mutex)

# 启用 Qt 调试输出(Arm 平台必需)
DEFINES += QT_MESSAGELOGCONSOLE
# 若使用 Qt 5 及以上,添加此配置确保日志不被优化掉
CONFIG += debug
# 防止 release 模式下优化掉调试日志
DEFINES += QT_NO_DEBUG_OUTPUT_DISABLE

行缓冲
在Qt开发中, qDebug() 、 std::cout  输出不了但  printf  能输出,是输出流缓冲区刷新机制、Qt 输出配置、编译选项三者共同导致的,核心结论: printf  大概率能正常输出,但需先解决  qDebug() / std::cout  输出失败的根源(避免后续隐藏问题)。

为什么  qDebug() / std::cout  输出不了

1.  std::cout  输出失败:缓冲区未刷新

 std::cout  默认是行缓冲(和  printf  类似),但 Qt 中若未加  std::endl (或  std::flush ),缓冲区未满时不会输出,而  printf  可能因 Qt 对 C 标准库的特殊处理,默认刷新策略不同:

解决:

#include <iostream>
using namespace std;

// 方法1:加 std::endl(自动刷新缓冲区)
cout << "std::cout 测试" << endl;  
// 方法2:手动刷新
cout << "std::cout 测试";
cout.flush();  

2.  qDebug()  输出失败:Qt 输出开关未开启

Qt 中  qDebug()  的输出受  QT_NO_DEBUG_OUTPUT  宏 和 控制台配置 影响:

情况1:

编译时定义了  QT_NO_DEBUG_OUTPUT (Release 模式默认开启),导致  qDebug()  被禁用;

解决:

在  .pro  文件中添加  DEFINES -= QT_NO_DEBUG_OUTPUT ,重新编译(Debug 模式默认未定义)。

情况2:

Qt 控制台未启用(如 GUI 程序未配置控制台输出)

解决:

① 非 GUI 程序(控制台程序): .pro  文件中确保  CONFIG += console ;
② GUI 程序:在  main()  函数开头添加:
cpp   
#include <QtCore/QCoreApplication>
int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);
    // 启用控制台输出(GUI 程序)
    freopen("CONOUT$", "w", stdout);  
    qDebug() << "qDebug() 测试";
    return a.exec();
}
二、 printf  能正常输出的原因
 
 printf  属于 C 标准库函数,其输出流( stdout )在 Qt 中默认:
 
- 行缓冲模式:遇到  \n  自动刷新(如  printf("printf 测试\n"); );
- 不受 Qt  QT_NO_DEBUG_OUTPUT  宏影响(该宏仅作用于 Qt 自带的  qDebug() / qInfo()  等);
- 即使是 GUI 程序, printf  也能直接输出到系统控制台(只要程序有控制台权限)。
 
三、验证:3行代码测试三者输出
cpp   
#include <QDebug>
#include <iostream>
#include <cstdio>
using namespace std;

int main() {
    // 1. printf(大概率能输出)
    printf("printf 输出\n");  
    // 2. std::cout(加 endl 刷新)
    cout << "std::cout 输出" << endl;  
    // 3. qDebug(启用输出开关后生效)
    qDebug() << "qDebug 输出";  

    return 0;
}
编译运行:
 
- 若仅  printf  输出:检查  std::cout  未刷新、 qDebug()  输出开关未开启;
- 三者都输出:正常(说明配置无误);
- 三者都不输出:可能是程序权限问题(如国产系统中未获取控制台权限),需用  ./程序 > output.txt  重定向输出查看。
四、建议:优先修复  qDebug() / std::cout ,而非依赖  printf 
 qDebug()  支持 Qt 数据类型(如  QString 、 QList )的直接输出(如  qDebug() << QString("测试") ), std::cout  是 C++ 标准输出,两者更适配 Qt 开发;而  printf  需手动格式化 Qt 类型(如  printf("%s", qstr.toUtf8().data()) ),不够便捷。

如果这篇文章对你有帮助,别忘了点个关注,我会持续分享更多开发避坑与实战干货,下次更新你就能第一时间看到啦~

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星柚程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值