From 8c908f84eec8aeb7a1eb4925cdd56d6607d112fa Mon Sep 17 00:00:00 2001 From: dismine Date: Thu, 20 Nov 2014 14:18:43 +0200 Subject: [PATCH] Save log to file. Send or collect after crash (only Windows). --HG-- branch : develop --- src/app/core/vapplication.cpp | 213 +++++++++++++++++++++++++++++++--- src/app/core/vapplication.h | 15 ++- src/app/main.cpp | 91 +-------------- 3 files changed, 216 insertions(+), 103 deletions(-) diff --git a/src/app/core/vapplication.cpp b/src/app/core/vapplication.cpp index 37847fe83..2a2d1044a 100644 --- a/src/app/core/vapplication.cpp +++ b/src/app/core/vapplication.cpp @@ -43,6 +43,97 @@ #include #include #include +#include +#include +#include + +//--------------------------------------------------------------------------------------------------------------------- +inline void noisyFailureMsgHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) +{ + // Why on earth didn't Qt want to make failed signal/slot connections qWarning? + if ((type == QtDebugMsg) && msg.contains("::connect")) + { + type = QtWarningMsg; + } + + // this is another one that doesn't make sense as just a debug message. pretty serious + // sign of a problem + // http://www.developer.nokia.com/Community/Wiki/QPainter::begin:Paint_device_returned_engine_%3D%3D_0_(Known_Issue) + if ((type == QtDebugMsg) && msg.contains("QPainter::begin") && msg.contains("Paint device returned engine")) + { + type = QtWarningMsg; + } + + // This qWarning about "Cowardly refusing to send clipboard message to hung application..." + // is something that can easily happen if you are debugging and the application is paused. + // As it is so common, not worth popping up a dialog. + if ((type == QtWarningMsg) && QString(msg).contains("QClipboard::event") + && QString(msg).contains("Cowardly refusing")) + { + type = QtDebugMsg; + } + + // only the GUI thread should display message boxes. If you are + // writing a multithreaded application and the error happens on + // a non-GUI thread, you'll have to queue the message to the GUI + QCoreApplication *instance = QCoreApplication::instance(); + const bool isGuiThread = instance && (QThread::currentThread() == instance->thread()); + + if (isGuiThread) + { + QString debugdate = QDateTime::currentDateTime().toString("yyyy.MM.dd hh:mm:ss"); + QMessageBox messageBox; + switch (type) + { + case QtDebugMsg: + debugdate += QString(" [Debug] %1: %2 (%3:%4, %5)").arg(context.category).arg(msg).arg(context.file) + .arg(context.line).arg(context.function); + break; + case QtWarningMsg: + debugdate += QString(" [Warning] %1: %2 (%3:%4, %5)").arg(context.category).arg(msg).arg(context.file) + .arg(context.line).arg(context.function); + messageBox.setIcon(QMessageBox::Warning); + messageBox.setInformativeText(msg); + messageBox.setStandardButtons(QMessageBox::Ok); + messageBox.exec(); + break; + case QtCriticalMsg: + debugdate += QString(" [Critical] %1: %2 (%3:%4, %5)").arg(context.category).arg(msg).arg(context.file) + .arg(context.line).arg(context.function); + messageBox.setIcon(QMessageBox::Critical); + messageBox.setInformativeText(msg); + messageBox.setStandardButtons(QMessageBox::Ok); + messageBox.exec(); + break; + case QtFatalMsg: + debugdate += QString(" [Fatal] %1: %2 (%3:%4, %5)").arg(context.category).arg(msg).arg(context.file) + .arg(context.line).arg(context.function); + messageBox.setIcon(QMessageBox::Critical); + messageBox.setInformativeText(msg); + messageBox.setStandardButtons(QMessageBox::Ok); + messageBox.exec(); + break; + default: + break; + } + + (*qApp->LogFile()) << debugdate << endl; + + if (QtFatalMsg == type) + { + abort(); + } + } + else + { + if (type != QtDebugMsg) + { + abort(); // be NOISY unless overridden! + } + } +} + +//--------------------------------------------------------------------------------------------------------------------- const qreal VApplication::PrintDPI = 96.0; @@ -64,8 +155,8 @@ VApplication::VApplication(int &argc, char **argv) guiTexts(QMap()), descriptions(QMap()), variables(QMap()), functions(QMap()), postfixOperators(QMap()), stDescriptions(QMap()), - undoStack(nullptr), sceneView(nullptr), currentScene(nullptr), - autoSaveTimer(nullptr), mainWindow(nullptr), openingPattern(false), settings(nullptr), doc(nullptr) + undoStack(nullptr), sceneView(nullptr), currentScene(nullptr), autoSaveTimer(nullptr), mainWindow(nullptr), + openingPattern(false), settings(nullptr), doc(nullptr), log(nullptr), out(nullptr) { undoStack = new QUndoStack(this); @@ -77,6 +168,19 @@ VApplication::VApplication(int &argc, char **argv) InitSTDescriptions(); } +//--------------------------------------------------------------------------------------------------------------------- +VApplication::~VApplication() +{ + qInstallMessageHandler(0); // Resore the message handler + delete out; + + if (log != nullptr) + { + log->close(); + delete log; + } +} + //--------------------------------------------------------------------------------------------------------------------- /** * @brief NewValentina start Valentina in new process, send path to pattern file in argument. @@ -295,6 +399,25 @@ void VApplication::InitMeasurement(const QString &name, const VTranslation &m, c descriptions.insert(name, d); } +//--------------------------------------------------------------------------------------------------------------------- +QString VApplication::LogDirPath() const +{ +#if defined(Q_OS_WIN) || defined(Q_OS_OSX) + const QString logDirPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QString(), + QStandardPaths::LocateDirectory) + "Valentina"; +#else + const QString logDirPath = QStandardPaths::locate(QStandardPaths::ConfigLocation, QString(), + QStandardPaths::LocateDirectory) + organizationName(); +#endif + return logDirPath; +} + +//--------------------------------------------------------------------------------------------------------------------- +QString VApplication::LogPath() const +{ + return LogDirPath() + "/valentina.log"; +} + //--------------------------------------------------------------------------------------------------------------------- void VApplication::InitMeasurements() { @@ -1854,6 +1977,34 @@ bool VApplication::SafeCopy(const QString &source, const QString &destination, Q return result; } +//--------------------------------------------------------------------------------------------------------------------- +void VApplication::StartLogging() +{ + QDir logDir(LogDirPath()); + if (logDir.exists() == false) + { + logDir.mkpath("."); // Create directory for log if need + } + + log = new QFile(LogPath()); + if (log->open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) + { + out = new QTextStream(log); + qInstallMessageHandler(noisyFailureMsgHandler); + } + else + { + delete log; + qDebug() << "Error opening log file '" << LogPath() << "'. All debug output redirected to console."; + } +} + +//--------------------------------------------------------------------------------------------------------------------- +QTextStream *VApplication::LogFile() +{ + return out; +} + #if defined(Q_OS_WIN) && defined(Q_CC_GNU) //--------------------------------------------------------------------------------------------------------------------- // Catch exception and create report. Use if program build with Mingw compiler. @@ -1905,12 +2056,16 @@ void VApplication::CollectReport(const QString &reportName) const } const QDateTime now = QDateTime::currentDateTime(); - const QString timestamp = now.toString(QLatin1String("yyyyMMdd-hhmmsszzz")); - const QString filename = QString("%1/reports/crash-%2.RPT").arg(qApp->applicationDirPath()).arg(timestamp); + const QString timestamp = now.toString(QLatin1String("yyyy.MM.dd-hh_mm_ss")); + QString filename = QString("%1/reports/crash-%2.RPT").arg(qApp->applicationDirPath()).arg(timestamp); QFile reportFile(reportName); reportFile.copy(filename); // Collect new crash reportFile.remove(); // Clear after yourself + + filename = QString("%1/reports/log-%2.log").arg(qApp->applicationDirPath()).arg(timestamp); + QFile logFile(LogPath()); + logFile.copy(filename); // Collect log } //--------------------------------------------------------------------------------------------------------------------- @@ -1928,23 +2083,22 @@ void VApplication::SendReport(const QString &reportName) const { QString content; QFile reportFile(reportName); - if (!reportFile.open(QIODevice::ReadOnly | QIODevice::Text)) + if (reportFile.open(QIODevice::ReadOnly | QIODevice::Text)) { - return; + content = ReadFileForSending(reportFile); + reportFile.close(); } - - QTextStream in(&reportFile); - while (!in.atEnd()) + else { - content.append(in.readLine()+"\r\n");// Windows end of line + content = reportFile.errorString() + "\r\n"; } - reportFile.close(); // Additional information content.append(QString("-------------------------------")+"\r\n"); content.append(QString("Version:%1").arg(APP_VERSION)+"\r\n"); content.append(QString("Based on Qt %2 (32 bit)").arg(QT_VERSION_STR)+"\r\n"); content.append(QString("Built on %3 at %4").arg(__DATE__).arg(__TIME__)+"\r\n"); + content.append("\r\n"); // Creating json with report // Example: @@ -1952,7 +2106,7 @@ void VApplication::SendReport(const QString &reportName) const // "description":"Crash report", // "public":"true", // "files":{ - // "file1.txt":{ + // "valentina.RPT":{ // "content":"Report text here" // } // } @@ -1967,12 +2121,29 @@ void VApplication::SendReport(const QString &reportName) const reportObject.insert(QStringLiteral("description"), QJsonValue(report)); reportObject.insert(QStringLiteral("public"), QJsonValue(QString("true"))); - QJsonObject contentObject; - contentObject.insert(QStringLiteral("content"), QJsonValue(content)); + content.append("\r\n"); + content.append(QString("-------------------------------")+"\r\n"); + content.append(QString("Log:")+"\r\n"); + QFile logFile(LogPath()); + if (logFile.open(QIODevice::ReadOnly | QIODevice::Text)) + { + content.append(ReadFileForSending(logFile)); + logFile.close(); + } + else + { + content = logFile.errorString() + "\r\n"; + } + + const QString contentSection = QStringLiteral("content"); + QJsonObject contentObject; + contentObject.insert(contentSection, QJsonValue(content)); + + const QString filesSection = QStringLiteral("files"); QJsonObject fileObject; fileObject.insert(QFileInfo(reportName).fileName(), QJsonValue(contentObject)); - reportObject.insert(QStringLiteral("files"), QJsonValue(fileObject)); + reportObject.insert(filesSection, QJsonValue(fileObject)); QFile gistFile(GistFileName); if (!gistFile.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) @@ -2001,4 +2172,16 @@ void VApplication::SendReport(const QString &reportName) const CollectReport(reportName); } } + +//--------------------------------------------------------------------------------------------------------------------- +QString VApplication::ReadFileForSending(QFile &file) const +{ + QString content; + QTextStream in(&file); + while (!in.atEnd()) + { + content.append(in.readLine()+"\r\n");// Windows end of line + } + return content; +} #endif //defined(Q_OS_WIN) && defined(Q_CC_GNU) diff --git a/src/app/core/vapplication.h b/src/app/core/vapplication.h index eeb1138cc..037ae225d 100644 --- a/src/app/core/vapplication.h +++ b/src/app/core/vapplication.h @@ -40,6 +40,7 @@ class QUndoStack; class VMainGraphicsView; class VMainGraphicsScene; class VPattern; +class QFile; #if defined(qApp) #undef qApp @@ -54,7 +55,7 @@ class VApplication : public QApplication Q_OBJECT public: VApplication(int &argc, char ** argv); - virtual ~VApplication() {} + virtual ~VApplication(); static void NewValentina(const QString &fileName = QString()); static void CheckFactor(qreal &oldFactor, const qreal &Newfactor); virtual bool notify(QObject * receiver, QEvent * event); @@ -103,13 +104,19 @@ public: static QStringList LabelLanguages(); QString STDescription(const QString &id)const; static bool SafeCopy(const QString &source, const QString &destination, QString &error); + void StartLogging(); + QTextStream *LogFile(); #if defined(Q_OS_WIN) && defined(Q_CC_GNU) static void DrMingw(); void CollectReports() const; +#endif // defined(Q_OS_WIN) && defined(Q_CC_GNU) + private slots: +#if defined(Q_OS_WIN) && defined(Q_CC_GNU) void CleanGist() const; #endif // defined(Q_OS_WIN) && defined(Q_CC_GNU) + private: Q_DISABLE_COPY(VApplication) Unit _patternUnit; @@ -143,6 +150,8 @@ private: QSettings *settings; VPattern *doc; + QFile *log; + QTextStream *out; void InitLineWidth(); void InitMeasurements(); void InitVariables(); @@ -166,7 +175,11 @@ private: void CollectReport(const QString &reportName) const; void SendReport(const QString &reportName) const; + QString ReadFileForSending(QFile &file)const; #endif // defined(Q_OS_WIN) && defined(Q_CC_GNU) + + QString LogDirPath()const; + QString LogPath()const; }; //--------------------------------------------------------------------------------------------------------------------- diff --git a/src/app/main.cpp b/src/app/main.cpp index 101bca7d4..44034d012 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -43,85 +43,6 @@ #include "tablewindow.h" #include "version.h" -//--------------------------------------------------------------------------------------------------------------------- -inline void noisyFailureMsgHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) -{ - // Why on earth didn't Qt want to make failed signal/slot connections qWarning? - if ((type == QtDebugMsg) && msg.contains("::connect")) - { - type = QtWarningMsg; - } - - // this is another one that doesn't make sense as just a debug message. pretty serious - // sign of a problem - // http://www.developer.nokia.com/Community/Wiki/QPainter::begin:Paint_device_returned_engine_%3D%3D_0_(Known_Issue) - if ((type == QtDebugMsg) && msg.contains("QPainter::begin") && msg.contains("Paint device returned engine")) - { - type = QtWarningMsg; - } - - // This qWarning about "Cowardly refusing to send clipboard message to hung application..." - // is something that can easily happen if you are debugging and the application is paused. - // As it is so common, not worth popping up a dialog. - if ((type == QtWarningMsg) && QString(msg).contains("QClipboard::event") - && QString(msg).contains("Cowardly refusing")) - { - type = QtDebugMsg; - } - - // only the GUI thread should display message boxes. If you are - // writing a multithreaded application and the error happens on - // a non-GUI thread, you'll have to queue the message to the GUI - QCoreApplication *instance = QCoreApplication::instance(); - const bool isGuiThread = instance && (QThread::currentThread() == instance->thread()); - - if (isGuiThread) - { - QByteArray localMsg = msg.toLocal8Bit(); - QMessageBox messageBox; - switch (type) - { - case QtDebugMsg: - fprintf(stderr, "Debug: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, - context.function); - return; - case QtWarningMsg: - messageBox.setIcon(QMessageBox::Warning); - messageBox.setInformativeText(msg); - messageBox.setStandardButtons(QMessageBox::Ok); - fprintf(stderr, "Warning: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, - context.function); - messageBox.exec(); - break; - case QtCriticalMsg: - messageBox.setIcon(QMessageBox::Critical); - messageBox.setInformativeText(msg); - messageBox.setStandardButtons(QMessageBox::Ok); - fprintf(stderr, "Critical: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, - context.function); - messageBox.exec(); - abort(); - case QtFatalMsg: - messageBox.setIcon(QMessageBox::Critical); - messageBox.setInformativeText(msg); - messageBox.setStandardButtons(QMessageBox::Ok); - fprintf(stderr, "Fatal: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, - context.function); - messageBox.exec(); - abort(); - default: - break; - } - } - else - { - if (type != QtDebugMsg) - { - abort(); // be NOISY unless overridden! - } - } -} - //--------------------------------------------------------------------------------------------------------------------- int main(int argc, char *argv[]) { @@ -134,14 +55,6 @@ int main(int argc, char *argv[]) VApplication app(argc, argv); -#ifdef QT_DEBUG - // Because our "noisy" message handler uses the GUI subsystem for message - // boxes, we can't install it until after the QApplication is constructed. But it - // is good to be the very next thing to run, to start catching warnings ASAP. - { - qInstallMessageHandler(noisyFailureMsgHandler); - } -#endif app.setApplicationDisplayName(VER_PRODUCTNAME_STR); app.setApplicationName(VER_INTERNALNAME_STR); app.setOrganizationName(VER_COMPANYNAME_STR); @@ -152,10 +65,14 @@ int main(int argc, char *argv[]) app.OpenSettings(); #if defined(Q_OS_WIN) && defined(Q_CC_GNU) + // Catch and send report VApplication::DrMingw(); app.CollectReports(); #endif + // Run creation log after sending crash report + app.StartLogging(); + QString checkedLocale = qApp->getSettings()->value("configuration/locale", QLocale::system().name()).toString(); QTranslator qtTranslator;