> 文章列表 > Windows下Release版本Qt程序生成日志和dump文件(用于程序异常崩溃检测)

Windows下Release版本Qt程序生成日志和dump文件(用于程序异常崩溃检测)

Windows下Release版本Qt程序生成日志和dump文件(用于程序异常崩溃检测)

文章目录

  • 前言
  • 一、基于qInstallMessageHandler生成输出日志
  • 二、基于qBreakpad生成dump文件
  • 三、基于DbgHelp和SetUnhandledExceptionFilter生成dump文件
  • 四、示例完整代码
  • 五、下载链接
  • 总结

前言

在实际项目开发时,一般打包发布给客户的程序是release版本Qt程序,然而在客户环境下可能会出现程序异常崩溃的问题,为了解决这个问题,一般会在程序中添加运行日志,或者生成dump文件,用来检测并定位异常。这里总结以下几种方式,用于程序异常崩溃检测定位:
1.基于qInstallMessageHandler生成输出日志
2.基于qBreakpad生成dump文件
3.基于DbgHelp和SetUnhandledExceptionFilter生成dump文件

编译环境:QT:5.14.1
编译器:MSVC_64_bit_Release

项目效果


提示:以下是本篇文章正文内容,下面案例可供参考

一、基于qInstallMessageHandler生成输出日志

在QT中我们可以调用qInstallMessageHandler这个函数来进行消息处理,这里自定义消息处理程序,可以将你调试代码时添加的打印输出保存为文件到指定路径。
1.main.cpp中添加

//自定义消息处理
void outputMessage(QtMsgType type,const QMessageLogContext &context,const QString &msg)
{static QMutex mutex;mutex.lock();//初始化log文件夹QString logFilePath = QCoreApplication::applicationDirPath() + "/DebugLog/";QDir dstDir(logFilePath);if(!dstDir.exists()){if(!dstDir.mkpath(logFilePath)){qDebug()<<__FILE__<<__LINE__<<"创建DebugLog文件夹失败!";}}//获取输出内容QString debugDateTime = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss ddd");QString debugMsg = QString("%1 \\r\\n%2").arg(debugDateTime).arg(msg);//保存文件QString logFileName = logFilePath + "log_" + QDate::currentDate().toString("yyyyMMdd") + ".txt";QFile file(logFileName);file.open(QIODevice::WriteOnly | QIODevice::Append);QTextStream textStream(&file);textStream << debugMsg << "\\r\\n \\r\\n";file.flush();file.close();mutex.unlock();
}//使用
int main(int argc, char *argv[])
{QApplication a(argc, argv);//生成输出日志qInstallMessageHandler(outputMessage);Widget w;w.show();return a.exec();
}

二、基于qBreakpad生成dump文件

qBreakpad是对Breakpad的封装,其详细介绍可以参考这篇文章Windows下Qt生成dump文件并定位bug(基于qBreakpad),这里引用一下参考文章中的内容,来了解一下Breakpad的一些常识。

Breakpad介绍
Breakpad是Google公司开发的开源多平台C++崩溃检测库。Breakpad可以捕获发布给用户的应用程序的崩溃,并记录软件崩溃的调试信息到“minidump”文件中,即*.dmp。除此之外,Breakpad还可以调试信息包括错误行号,报错详情,堆栈错误(stack traces)。支持软件崩溃时候把生成的dump文件上传到自己的服务器上就可以方便的获取崩溃详情。

BreakPad工作原理
1.我们在编译的时候,需要在Release版程序中生成调试信息。
2.使用Breakpad提供的dump_syms工具,从release版本程序导出符号文件。
3.当程序崩溃时,breakpad会捕捉崩溃,并生成dump文件。
4.dump文件可以直接发送到指定服务器,或者由用户手动发给开发者。
5.收到dump文件后,结合符号文件,可通过minidump_stackwalk工具生成堆栈调用信息文件,这个文件可以直接阅读,定位bug。

我的示例中已经编译好了相关的库,并将库和头文件集成到项目同级文件夹下方便使用:
Windows下Release版本Qt程序生成日志和dump文件(用于程序异常崩溃检测)
Windows下Release版本Qt程序生成日志和dump文件(用于程序异常崩溃检测)
Windows下Release版本Qt程序生成日志和dump文件(用于程序异常崩溃检测)

接下来讲述一下qBreakpad的使用:
1.pro中添加

#for qBreakpad
#qBreakpad中需要使用到network模块
QT += network#启用多线程、异常、RTTI、STL支持
CONFIG += thread exceptions rtti stl#没有c++11和AppKit库编译器不能解决符号的地址
CONFIG += c++11
macx: LIBS += -framework AppKit#release版程序带上调试信息
QMAKE_CXXFLAGS_RELEASE = $$QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO
QMAKE_LFLAGS_RELEASE = $$QMAKE_LFLAGS_RELEASE_WITH_DEBUGINFO#配置头文件搜索路径和链接库路径
INCLUDEPATH += $$PWD/qBreakpad/includeCONFIG(debug, debug|release) {
LIBS += -L$$PWD/qBreakpad/lib/debug -lqBreakpad
} else {
LIBS += -L$$PWD/qBreakpad/lib/release -lqBreakpad
}

2.main.cpp中添加

//包含头文件
//qBreakpad
#include "qBreakpad/include/QBreakpadHandler.h"//使用
int main(int argc, char *argv[])
{QApplication a(argc, argv);//设置生成dump文件路径QBreakpadInstance.setDumpPath("BreakCrashes");   //捕获构造时的异常Widget w;w.show();QBreakpadInstance.setDumpPath("BreakCrashes");   //捕获构造完成之后的异常return a.exec();
}

三、基于DbgHelp和SetUnhandledExceptionFilter生成dump文件

SetUnhandledExceptionFilter设置未处理的异常筛选器函数,对于该函数的详细介绍可以直接查看windows官方文档,这里也是直接讲述其在Qt下的使用:
1.pro中添加

#for DbgHelp
#方便生成DUMP调试
LIBS += -lDbgHelp
QMAKE_LFLAGS_RELEASE = /INCREMENTAL:NO /DEBUG#release版程序带上调试信息
QMAKE_CXXFLAGS_RELEASE = $$QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO
QMAKE_LFLAGS_RELEASE = $$QMAKE_LFLAGS_RELEASE_WITH_DEBUGINFO

2.main.cpp中添加

//包含头文件
//DbgHelp
#include <windows.h>
#include <DbgHelp.h>
#include <QDateTime>
#include <QMessageBox>//程序异常捕获
LONG ApplicationCrashHandler(EXCEPTION_POINTERS *pException)
{//初始化dump文件夹QString logFilePath = QCoreApplication::applicationDirPath() + "/DumpCrashes/";QDir dstDir(logFilePath);if(!dstDir.exists()){if(!dstDir.mkpath(logFilePath)){qDebug()<<__FILE__<<__LINE__<<"创建DumpCrashes文件夹失败!";}}//创建Dump文件QString dumpFileName = logFilePath + QDateTime::currentDateTime().toString("yyyyMMddhhmmss") + ".dmp";HANDLE hDumpFile = CreateFile((LPCWSTR)(dumpFileName.toStdWString().c_str()), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);if(hDumpFile != INVALID_HANDLE_VALUE){//Dump信息MINIDUMP_EXCEPTION_INFORMATION dumpInfo;dumpInfo.ExceptionPointers = pException;dumpInfo.ThreadId = GetCurrentThreadId();dumpInfo.ClientPointers = TRUE;//写入Dump文件内容MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, (MINIDUMP_TYPE)(MiniDumpWithDataSegs | MiniDumpWithProcessThreadData | MiniDumpWithUnloadedModules), &dumpInfo, NULL, NULL);}//这里弹出一个错误对话框并退出程序EXCEPTION_RECORD* record = pException->ExceptionRecord;QString errCode(QString::number(record->ExceptionCode,16));QString errAddr(QString::number((uint)record->ExceptionAddress,16));QMessageBox::critical(NULL,"错误",QString("程序异常崩溃捕获!\\nerrCode:%1 \\nerrAddr:%2").arg(errCode.toStdString().c_str()).arg(errAddr.toStdString().c_str()));return EXCEPTION_EXECUTE_HANDLER;
}//使用
int main(int argc, char *argv[])
{QApplication a(argc, argv);//注冊异常捕获函数SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)ApplicationCrashHandler);   //捕获构造时的异常Widget w;w.show();SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)ApplicationCrashHandler);   //捕获构造完成之后的异常return a.exec();
}

四、示例完整代码

1.MyDump.pro

QT       += core guigreaterThan(QT_MAJOR_VERSION, 4): QT += widgets#CONFIG += c++11DEFINES += QT_DEPRECATED_WARNINGSSOURCES += \\main.cpp \\widget.cppHEADERS += \\widget.hFORMS += \\widget.ui# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target#设置字符
contains( CONFIG,"msvc" ):QMAKE_CXXFLAGS += /source-charset:utf-8 /execution-charset:utf-8
contains( CONFIG,"msvc" ):QMAKE_CFLAGS +=/source-charset:utf-8 /execution-charset:utf-8#使用其中之一的话,将另外一个屏蔽即可
#for qBreakpad
#qBreakpad中需要使用到network模块
QT += network#启用多线程、异常、RTTI、STL支持
CONFIG += thread exceptions rtti stl#没有c++11和AppKit库编译器不能解决符号的地址
CONFIG += c++11
macx: LIBS += -framework AppKit#release版程序带上调试信息
QMAKE_CXXFLAGS_RELEASE = $$QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO
QMAKE_LFLAGS_RELEASE = $$QMAKE_LFLAGS_RELEASE_WITH_DEBUGINFO#配置头文件搜索路径和链接库路径
INCLUDEPATH += $$PWD/qBreakpad/includeCONFIG(debug, debug|release) {
LIBS += -L$$PWD/qBreakpad/lib/debug -lqBreakpad
} else {
LIBS += -L$$PWD/qBreakpad/lib/release -lqBreakpad
}#for DbgHelp
#方便生成DUMP调试
LIBS += -lDbgHelp
QMAKE_LFLAGS_RELEASE = /INCREMENTAL:NO /DEBUG#release版程序带上调试信息
#QMAKE_CXXFLAGS_RELEASE = $$QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO
#QMAKE_LFLAGS_RELEASE = $$QMAKE_LFLAGS_RELEASE_WITH_DEBUGINFO

2.main.cpp

#include "widget.h"#include <QApplication>//logfile
#include <QDir>
#include <QDebug>//qBreakpad
#include "qBreakpad/include/QBreakpadHandler.h"//DbgHelp
#include <windows.h>
#include <DbgHelp.h>
#include <QDateTime>
#include <QMessageBox>//自定义消息处理
void outputMessage(QtMsgType type,const QMessageLogContext &context,const QString &msg)
{static QMutex mutex;mutex.lock();//初始化log文件夹QString logFilePath = QCoreApplication::applicationDirPath() + "/DebugLog/";QDir dstDir(logFilePath);if(!dstDir.exists()){if(!dstDir.mkpath(logFilePath)){qDebug()<<__FILE__<<__LINE__<<"创建DebugLog文件夹失败!";}}//获取输出内容QString debugDateTime = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss ddd");QString debugMsg = QString("%1 \\r\\n%2").arg(debugDateTime).arg(msg);//保存文件QString logFileName = logFilePath + "log_" + QDate::currentDate().toString("yyyyMMdd") + ".txt";QFile file(logFileName);file.open(QIODevice::WriteOnly | QIODevice::Append);QTextStream textStream(&file);textStream << debugMsg << "\\r\\n \\r\\n";file.flush();file.close();mutex.unlock();
}//程序异常捕获
LONG ApplicationCrashHandler(EXCEPTION_POINTERS *pException)
{//初始化dump文件夹QString logFilePath = QCoreApplication::applicationDirPath() + "/DumpCrashes/";QDir dstDir(logFilePath);if(!dstDir.exists()){if(!dstDir.mkpath(logFilePath)){qDebug()<<__FILE__<<__LINE__<<"创建DumpCrashes文件夹失败!";}}//创建Dump文件QString dumpFileName = logFilePath + QDateTime::currentDateTime().toString("yyyyMMddhhmmss") + ".dmp";HANDLE hDumpFile = CreateFile((LPCWSTR)(dumpFileName.toStdWString().c_str()), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);if(hDumpFile != INVALID_HANDLE_VALUE){//Dump信息MINIDUMP_EXCEPTION_INFORMATION dumpInfo;dumpInfo.ExceptionPointers = pException;dumpInfo.ThreadId = GetCurrentThreadId();dumpInfo.ClientPointers = TRUE;//写入Dump文件内容MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, (MINIDUMP_TYPE)(MiniDumpWithDataSegs | MiniDumpWithProcessThreadData | MiniDumpWithUnloadedModules), &dumpInfo, NULL, NULL);}//这里弹出一个错误对话框并退出程序EXCEPTION_RECORD* record = pException->ExceptionRecord;QString errCode(QString::number(record->ExceptionCode,16));QString errAddr(QString::number((uint)record->ExceptionAddress,16));QMessageBox::critical(NULL,"错误",QString("程序异常崩溃捕获!\\nerrCode:%1 \\nerrAddr:%2").arg(errCode.toStdString().c_str()).arg(errAddr.toStdString().c_str()));return EXCEPTION_EXECUTE_HANDLER;
}int main(int argc, char *argv[])
{QApplication a(argc, argv);//生成输出日志qInstallMessageHandler(outputMessage);//二选一,将异常捕获函数放在此处可捕获构造时的异常//QBreakpadInstance.setDumpPath("BreakCrashes");   //设置生成dump文件路径SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)ApplicationCrashHandler);   //注冊异常捕获函数Widget w;w.show();//二选一,将异常捕获函数放在此处可捕获构造完成之后的异常//QBreakpadInstance.setDumpPath("BreakCrashes");SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)ApplicationCrashHandler);return a.exec();
}

3.widget.h

#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>
#include <QLabel>
#include <QDebug>QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACEclass Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();private slots:void on_pb_crash_clicked();private:Ui::Widget *ui;
};
#endif // WIDGET_H

4.widget.cpp

#include "widget.h"
#include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);qDebug()<<__FILE__<<__LINE__<<"widget structure!";//异常测试1//QLabel *label_1;//label_1->setText("hello world!");//label_1->show();
}Widget::~Widget()
{delete ui;
}void Widget::on_pb_crash_clicked()
{qDebug()<<__FILE__<<__LINE__<<"pb_crash clicked!";//异常测试2QLabel *label_2;label_2->setText("hello world!");label_2->show();
}

5.widget.ui
Windows下Release版本Qt程序生成日志和dump文件(用于程序异常崩溃检测)

五、下载链接

我的示例百度网盘链接:https://pan.baidu.com/s/19eGJH2OnLACxc_C4VDMtWg
提取码:xxcj


总结

这里日志的话比较方便,但很多时候无法定位到具体出错的代码行,你不可能在每一行代码后面都加输出吧?所以用dump文件配合在编译该程序时生成的pdb文件,可以准确定位到调用堆栈、代码行,这样能很好的解决bug啦!
dump文件,后缀*.dmp,是程序崩溃时的内存转储文件;
pdb文件,后缀*.pdb,是程序的符号文件。
关于dump文件和pdb文件的使用,这里我就不做具体介绍了,参考博客中的介绍很详细,更多的可以查看参考博客。


hello:
共同学习,共同进步,如果还有相关问题,可在评论区留言进行讨论。

参考博客:
Qt 之 qInstallMessageHandler(输出详细日志)
Windows下Qt生成dump文件并定位bug(基于qBreakpad)
Qt下使用DbgHelp和SetUnhandledExceptionFilter来获取Crash log/dump文件