• Qt 日志输出


    Qt学习(3)日志输出

    普通的打印输出

    用 QtCreator 开发 Qt 程序时, 经常需要向控制台打印一些参数。有时候是查看对象的属性是否被正确设置,有时候是查看程序是否执行了某一段代码,或者执行了多少次这一段代码。尽管使用调试模式可以一行一行的查看代码的执行情况,也可以看到执行代码后变量的相应值,但是 Qt 的实现采用了 D 指针,它隐藏了代码的实现,在查看变量的值时不是非常的方便(另外在 Windows 平台下打开调试模式经常会出现打开 cdb 程序异常缓慢,卡在 为 ABI 'x86-windows-msvc2015-pe-64bit' 启动调试器 'CdbEngine', 不清楚具体的原因是什么)。

    调试模式查看变量值

    初看变量面板其实很难看到 objectName 是不是已经设置成了 test,因为没法知道存储 objectName 属性的变量名叫什么,查看 Qt setObjectName 的源代码:

    // qobject.cpp
    void QObject::setObjectName(const QString &name)
    {
        Q_D(QObject);
        if (!d->extraData)
            d->extraData = new QObjectPrivate::ExtraData;
    
        if (d->extraData->objectName != name) {
            d->extraData->objectName = name;
            emit objectNameChanged(d->extraData->objectName, QPrivateSignal());
        }
    }
    

    看到实际上 objectName 这一属性实际上是存放在 d 指针的 extraData 对象里面。

    为了方便的看到属性的值,可以把属性值输出到控制台界面:

    #include <QCoreApplication>
    #include <QDebug>
    // #include <iostream>
    
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
        QObject obj;
        obj.setObjectName( "test" );
    
    //    std::cout << obj.objectName().toLocal8Bit().constData();  // 显示到标准输出流(不是Creator集成的控制台)
        qDebug() << obj.objectName();
        return a.exec();
    }
    

    首先包含 QDebug 头文件(.pro 文件中加入 Qt += core),然后在程序中使用 qDebug() 及流运算符,就可以把 objectName 输出到控制台。

    这里的 qDebug 函数实际上是 qlogging.h 中定义的宏,类似的还有 qInfoqFatal 等:

    // qlogging.h
    #define qDebug QMessageLogger(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC).debug
    #define qInfo QMessageLogger(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC).info
    #define qWarning QMessageLogger(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC).warning
    #define qCritical QMessageLogger(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC).critical
    #define qFatal QMessageLogger(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC).fatal
    

    格式化输出

    个人觉得直接在程序中输出某些属性,其实对于调试简单的逻辑代码时候非常方便,所以在程序中加入了大量的 qDebug 用来输出调试信息,然而当输出信息很多的时候,很难从一堆信息中找到需要的信息,还没法定位输出信息的位置。之前不知道 Qt 的输出机制,就在每个调用 qDebug() 的地方加上一段位置信息,比如:

    qDebug() << "[Debug] MyObject::test " << obj.objectName();
    

    每个输出都要自己加上一些额外信息,一般类名或者函数名都不会变化,所以可以当作位置信息加到要输出的前面,但是你可能注意到了,代码所在的行数经常会变化,因此没办法把行数添加到输出里。

    另外,如果想要在打印输出的时候加上时间戳,那怎么办呢?最开始我想可以在调用 qDebug() 的位置加上 QDateTime,后来想想还是算了,太麻烦了。

    为了格式化输出,加上一些额外信息,可以使用 qSetMessagePattern(const QString &) 函数或者设置 QT_MESSAGE_PATTERN 环境变量来定制自己的输出格式,它可以修改信息处理器的默认输出,以下是官网的两种方法(可用的占位符请参考官方文档):

    或者可以在 main 函数中加入以下代码:

    qSetMessagePattern( "[%{time yyyyMMdd h:mm:ss.zzz t} %{if-debug}D%{endif}%{if-info}I%{endif}%{if-warning}W%{endif}%{if-critical}C%{endif}%{if-fatal}F%{endif}] %{file}:%{line} - %{message}" );
    

    在项目的构建环境中添加环境变量
    QT_MESSAGE_PATTERN = [%{time yyyyMMdd h:mm:ss.zzz t} %{if-debug}D%{endif}%{if-info}I%{endif}%{if-warning}W%{endif}%{if-critical}C%{endif}%{if-fatal}F%{endif}] %{file}:%{line} - %{message}

    在控制台上可以看到 qDebug() << obj.objectName() 的输出变成

    output

    输出会显示文件名和行号,这是因为当前项目的构建模式选择的是 Debug,如果选择的是 Release 模式,那么在发布正式版程序时是看不到文件名和行号的(当然也包括函数名),文件名或函数名会显示为 unknown,行号会显示为 0。

    如果觉得这些格式并不好看,可以看看这篇文章,用 qt 做出漂亮的调试输出

    QT_MESSAGE_PATTERN 的优先级要比 qSetMessagePattern 的函数调用优先级高:

    The pattern can also be changed at runtime by setting the QT_MESSAGE_PATTERN environment variable; if both qSetMessagePattern() is called and QT_MESSAGE_PATTERN is set, the environment variable takes precedence.

    可以通过修改 QT_MESSAGE_PATTERN 环境变量在运行时修改输出格式;如果调用 qSetMessagePattern 的同时又设置了 QT_MESSAGE_PATTERN,那么这个环境变量将会生效。

    另一种格式化的方式

    此外还有一种格式化输出信息的方式,使用 qInstallMessageHandler 注册一个自定义的消息处理器替换掉 默认的 QtMessageHandler

    void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
    {
        QByteArray localMsg = msg.toLocal8Bit();
        switch (type) {
        case QtDebugMsg:
            fprintf(stderr, "Debug: %s (%s:%u, %s)
    ", localMsg.constData(), context.file, context.line, context.function);
            break;
        case QtInfoMsg:
            fprintf(stderr, "Info: %s (%s:%u, %s)
    ", localMsg.constData(), context.file, context.line, context.function);
            break;
        case QtWarningMsg:
            fprintf(stderr, "Warning: %s (%s:%u, %s)
    ", localMsg.constData(), context.file, context.line, context.function);
            break;
        case QtCriticalMsg:
            fprintf(stderr, "Critical: %s (%s:%u, %s)
    ", localMsg.constData(), context.file, context.line, context.function);
            break;
        case QtFatalMsg:
            fprintf(stderr, "Fatal: %s (%s:%u, %s)
    ", localMsg.constData(), context.file, context.line, context.function);
            break;
        }
    }
    
    int main(int argc, char *argv[])
    {
        qInstallMessageHandler( myMessageOutput );
        QCoreApplication a(argc, argv);
    
        ...
    }
    

    通过 qInstallMessageHandler(QtMessageHandler) 函数注册一个自定义的 MessageHandler,QtMessageHandler 的定义如下:

    typedef void (*QtMessageHandler)(QtMsgType, const QMessageLogContext &, const QString &);
    

    可以看到,QtMessageHandler 并不是一个类,而是一个函数指针,函数原型满足 (QtMsgType, const QMessageLogContext &, const QString &) 的函数都可以作为 QtMessageHandler 参数传递个 qInstallMessageHandler 函数中。

    注册 MessageHandler 后,输出调试信息,

    // int main() {
        ...
    
        QObject dobj;
        dobj.setObjectName( "Debug Test" );
        qDebug() << dobj.objectName();
        QObject wobj;
        wobj.setObjectName( "Warning Test" );
        qWarning() << wobj.objectName();
        QObject cobj;
        cobj.setObjectName( "Critical Test" );
        qCritical() << cobj.objectName();
        QObject iobj;
        iobj.setObjectName( "Info Test" );
        qInfo() << iobj.objectName();
    //    QObject fobj;
    //    fobj.setObjectName( "Fatal Test" );
    //    qFatal(fobj.objectName().toLocal8Bit().constData());
    // }
    

    这里的输出格式和 myMessageOutput 函数中 fprintf 函数的格式是一致的,然而请注意,在主函数里,qSetMessagePattern 函数是没有注释掉的,然而输出忽略掉了 qSetMessagePattern 设置的格式。

    导出调试信息到日志中

    以上的方法虽然可以格式化输出,但是调试输出的信息只会在控制台中显示,如果想要把输出信息导出到日志文件中怎么办呢?需要自己写个类(或者定义个新的宏),把 qDebug() 替换成自己定义的函数吗?其实是不需要的,我们可以在自定义的消息处理器中将信息输出到文件中。你可能已经注意到了,官方的示例中是把所有信息用 fprintf 输出到标准错误流 stderr 中的,我们只需要改变输出流到文件中就好了。

    这里我打算结合 qSetMessagePatternQtMessageHandler 来实现输出调试信息到文件中。

    在设置了 qSetMessagePattern 后,要想使用设置好的格式,需要调用 qFormatLogMessage()

    Custom message handlers can use qFormatLogMessage() to take pattern into account.

    void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
    {
        QString formatMsg = qFormatLogMessage( type, context, msg );
        if( type < msgLevel ) {
            return;
        }
        std::cout << formatMsg.toLocal8Bit().constData() << std::endl;
    }
    

    输出如下:

    要把输出信息导出到日志文件中,只需要将数据导入到文件输出流就行了。

    // Log::setLogFile {
        file.setFileName( name );
        file.open( QIODevice::WriteOnly | QIODevice::Append );
        outstream.setDevice( &file );
        outstream.setCodec( QTextCodec::codecForName("UTF-8") );
    // }
    
    // Log::myMessageOutput {
        ...
        outstream << formatMsg.toLocal8Bit().constData() << "
    ";
        outstream.flush();
    // }
    

    我在使用测试程序时,发现 log.txt 文件中一直没有内容,在 outstream 输出后加上 flush 操作,就可以看到 log.txt 文件中的内容了,和之前在控制台中打印出来的是一样的:

    [20180620 15:01:43.325 中国标准时间 D] main.cpp:main:93 -- "Debug Test"
    [20180620 15:01:43.328 中国标准时间 W] main.cpp:main:96 -- "Warning Test"
    [20180620 15:01:43.330 中国标准时间 C] main.cpp:main:99 -- "Critical Test"
    [20180620 15:01:43.331 中国标准时间 I] main.cpp:main:102 -- "Info Test"
    

    代码

    代码存放在 github

    参考

    http://wiki.qt.io/D-Pointer/zh
    https://blog.csdn.net/liang19890820/article/details/51838379
    https://woboq.com/blog/nice-debug-output-with-qt.html
    https://www.cnblogs.com/lvchaoshun/p/7806248.html
    https://blog.csdn.net/liang19890820/article/details/51839233
    http://doc.qt.io/qt-5/qtglobal.html#qSetMessagePattern
    http://doc.qt.io/qt-5/qdebug.html
    http://doc.qt.io/qt-5/qtglobal.html#qInstallMessageHandler

  • 相关阅读:
    全网最详细的Linux命令系列-ls命令
    Kubernetes 部署策略详解-转载学习
    Kubernetes工作流程--<1>
    详解CURL状态码,最全的代码列表
    Haproxy-4层和7层代理负载实战
    Keepalived+Nginx高可用实例
    实现FTP+PAM+MySQL环境,批量配置虚拟用户
    每秒处理10万订单乐视集团支付Mysql架构-转载
    构建 CDN 分发网络架构简析
    Linux系统入门简介<1>
  • 原文地址:https://www.cnblogs.com/brifuture/p/9203919.html
Copyright © 2020-2023  润新知