进步始于交流
收获源于分享

qInstallMessageHandler输出重定向实现日志功能

介绍

Qt提供了一系列消息模板:qInfo、qDebug、qWarning、qCritical、qFatal,官方文档

qDebug可能比较常用,他们分别表示消息、调试信息、一般警告、严重错误、致命错误,默认情况会输出至调试窗口。而在release模式下可以将其输出重定向至文件,从而实现日志功能。

qInstallMessageHandler

使用此方法可以将上述五种消息重定向至指定函数,范例如下:

/**
 * @brief CustomOutputMessage
 * @param type 消息类型
 * @param context 消息信息
 * @param msg 消息内容
 */
void CustomOutputMessage(QtMsgType type,
                         const QMessageLogContext &context,
                         const QString &msg) {
    QString message_type;
    switch(type) {
    case QtInfoMsg:
        message_type = QString("Info:");
        break;
    case QtDebugMsg:
        message_type = QString("Debug:");
        break;
    case QtWarningMsg:
        message_type = QString("Warning:");
        break;
    case QtCriticalMsg:
        message_type = QString("Critical:");
        break;
    case QtFatalMsg:
        message_type = QString("Fatal:");
    }
    QString current_date = QDateTime::currentDateTime().
                           toString("yyyy-MM-dd hh:mm:ss");
    QFile file("log.txt");
    file.open(QIODevice::WriteOnly | QIODevice::Append);
    QString message = QString("%1 %2 (%3)\r\n").arg(message_type).
                      arg(msg).arg(current_date);
    file.write(message.toLatin1());
    file.close();
    if(type == QtFatalMsg) {
        QMessageBox::critical(nullptr, "致命错误",
                              QString("%2\r\n%1\r\n请解决问题后重新启动程序").
                              arg(msg).arg(current_date));
    }
}

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    //日志功能
#ifndef QT_DEBUG
    qInstallMessageHandler(CustomOutputMessage);
#endif
    qDebug("123");
//    qFatal("123");
    MainWindow w;
    w.show();
    return a.exec();
}

其中使用qInstallMessageHandler方法前判断了QT_DEBUG宏,即在调试模式直接输出,在release模式输出至文件

函数模板:

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

方法的第一个参数是枚举类型,除上述类型外还有一个QtSystemMsg=QtCriticalMsg

其他

取消重定向

通过qInstallMessageHandler(nullptr);即可取消重定向。此处不建议写0,毕竟此处应当传递的是一个指针。

qFatal

Qt比较人性化,当发生qFatal时(致命错误),会在完成qInstallMessageHandler的操作后就直接调用std::abort让程序被系统kill,并由系统弹出错误框。系统弹出提示框的情况在用户使用时并不美观。

分析原因:

qFatal宏会调用如下方法:

void QMessageLogger::fatal(const char *msg, ...) const Q_DECL_NOTHROW
{
    QString message;
    va_list ap;
    va_start(ap, msg); // use variable arg list
    QT_TERMINATE_ON_EXCEPTION(message = qt_message(QtFatalMsg, context, msg, ap));//1
    va_end(ap);
    qt_message_fatal(QtFatalMsg, context, message);//2
}

1处会调用qInstallMessageHandler指向的函数句柄

2处代码如下:

static void qt_message_fatal(QtMsgType, const QMessageLogContext &context, const QString &message)
{
#if defined(Q_CC_MSVC) && defined(QT_DEBUG) && defined(_DEBUG) && defined(_CRT_ERROR)
    wchar_t contextFileL[256];
    convert_to_wchar_t_elided(contextFileL, sizeof contextFileL / sizeof *contextFileL,
                              context.file);
    int reportMode = _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_WNDW);
    _CrtSetReportMode(_CRT_ERROR, reportMode);
    int ret = _CrtDbgReportW(_CRT_ERROR, contextFileL, context.line, _CRT_WIDE(QT_VERSION_STR),
                             reinterpret_cast<const wchar_t *>(message.utf16()));
    if ((ret == 0) && (reportMode & _CRTDBG_MODE_WNDW))
        return; // ignore
    else if (ret == 1)
        _CrtDbgBreak();
#else
    Q_UNUSED(context);
    Q_UNUSED(message);
#endif
    std::abort();
}

最后调用了abort。。。。真的是致命错误,所以直接通过abort令系统杀死当前程序,这算他杀性致命?

对比qDebug:

void QMessageLogger::debug(const char *msg, ...) const
{
    va_list ap;
    va_start(ap, msg); // use variable arg list
    const QString message = qt_message(QtDebugMsg, context, msg, ap);
    va_end(ap);
    if (isFatal(QtDebugMsg))//5
        qt_message_fatal(QtDebugMsg, context, message);
}

会在5行提前判断一下是不是fatal,此处当然不是,所以不会调用后面的qt_message_fatal。
为什么明知不是还判断?isFatal里面对非Fatal做了一些操作,具体的没有深入阅读

解决方案:

上述代码修改:

if(type == QtFatalMsg) {
    QMessageBox::critical(nullptr, "致命错误",
             QString("%2\r\n%1\r\n请解决问题后重新启动程序").
             arg(msg).arg(current_date));
    exit(0);
}

此方法等于主动退出,算是自杀了。但是无法很好地处理所有的线程、内存等各种问题。如果没有exit,紧接着即将调用的abort也无法优雅的解决这些问题,线程问题、数据存储等等该如何处理就要看系统了。

当然,既然已经发生了致命错误,并且成功在log日志文件中记录了此错误,那就让他崩溃吧。由此也说明程序中除非无法挽回的问题用qFatal,其他的最好最高只用到qCritical.

赞(1) 打赏
未经允许不得转载:Coologic » qInstallMessageHandler输出重定向实现日志功能

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏