diff --git a/ext/libclementine-common/core/logging.cpp b/ext/libclementine-common/core/logging.cpp index d98a71af5..60b468c4d 100644 --- a/ext/libclementine-common/core/logging.cpp +++ b/ext/libclementine-common/core/logging.cpp @@ -25,8 +25,11 @@ #include #endif +#include #include #include +#include +#include #include #include @@ -36,15 +39,32 @@ namespace logging { -static Level sDefaultLevel = Level_Debug; -static QMap* sClassLevels = NULL; -static QIODevice* sNullDevice = NULL; - const char* kDefaultLogLevels = "GstEnginePipeline:2,*:3"; -static const char* kMessageHandlerMagic = "__logging_message__"; -static const int kMessageHandlerMagicLength = strlen(kMessageHandlerMagic); -static QtMsgHandler sOriginalMessageHandler = NULL; +namespace { +Level sDefaultLevel = Level_Debug; +QMap* sClassLevels = NULL; +QIODevice* sNullDevice = NULL; + +const char* kMessageHandlerMagic = "__logging_message__"; +const int kMessageHandlerMagicLength = strlen(kMessageHandlerMagic); +QtMsgHandler sOriginalMessageHandler = NULL; + +QList sOutputDevices; + +// QIODevice doesn't have a flush() method, but its subclasses do. This picks +// the right method to call depending on the runtime type of the device. +void FlushDevice(QIODevice* device) { + if (QFile* file = qobject_cast(device)) { + file->flush(); + } else if (QAbstractSocket* socket = qobject_cast(device)) { + socket->flush(); + } else if (QLocalSocket* socket = qobject_cast(device)) { + socket->flush(); + } +} + +} // namespace void GLog(const char* domain, int level, const char* message, void* user_data) { @@ -63,7 +83,16 @@ void GLog(const char* domain, int level, const char* message, void* user_data) { static void MessageHandler(QtMsgType type, const char* message) { if (strncmp(kMessageHandlerMagic, message, kMessageHandlerMagicLength) == 0) { + // Output to stderr. fprintf(stderr, "%s\n", message + kMessageHandlerMagicLength); + + // Output to all the configured output devices. + foreach (QIODevice* device, sOutputDevices) { + device->write(message + kMessageHandlerMagicLength); + device->write("\n", 1); + FlushDevice(device); + } + return; } @@ -99,6 +128,10 @@ void Init() { } } +void AddOutputDevice(QIODevice* device) { + sOutputDevices << device; +} + void SetLevels(const QString& levels) { if (!sClassLevels) return; diff --git a/ext/libclementine-common/core/logging.h b/ext/libclementine-common/core/logging.h index d2303c794..012859bc3 100644 --- a/ext/libclementine-common/core/logging.h +++ b/ext/libclementine-common/core/logging.h @@ -48,6 +48,7 @@ namespace logging { void Init(); void SetLevels(const QString& levels); + void AddOutputDevice(QIODevice* device); void DumpStackTrace(); diff --git a/src/core/crashreporting.cpp b/src/core/crashreporting.cpp index b5465d89f..0095fea7e 100644 --- a/src/core/crashreporting.cpp +++ b/src/core/crashreporting.cpp @@ -24,17 +24,18 @@ const char* CrashReporting::kSendCrashReportOption = "--send-crash-report"; char* CrashReporting::sPath = NULL; +char* CrashReporting::sLogFilename = NULL; bool CrashReporting::SendCrashReport(int argc, char** argv) { #ifdef HAVE_BREAKPAD - if (argc != 3 || strcmp(argv[1], kSendCrashReportOption) != 0) { + if (argc != 4 || strcmp(argv[1], kSendCrashReportOption) != 0) { return false; } QApplication a(argc, argv); - CrashSender sender(argv[2]); + CrashSender sender(argv[2], argv[3]); if (sender.Start()) { a.exec(); } @@ -48,3 +49,7 @@ bool CrashReporting::SendCrashReport(int argc, char** argv) { void CrashReporting::SetApplicationPath(const QString& path) { sPath = strdup(path.toLocal8Bit().constData()); } + +void CrashReporting::SetLogFilename(const QString& path) { + sLogFilename = strdup(path.toLocal8Bit().constData()); +} diff --git a/src/core/crashreporting.h b/src/core/crashreporting.h index e16f05981..a367d1f43 100644 --- a/src/core/crashreporting.h +++ b/src/core/crashreporting.h @@ -46,15 +46,21 @@ public: // --send-crash-report when a crash happens. static void SetApplicationPath(const QString& path); + // If this is set then the contents of this file is sent along with any + // crash report. + static void SetLogFilename(const QString& path); + // Prints the message to stdout without using libc. static void Print(const char* message); static const char* application_path() { return sPath; } + static const char* log_filename() { return sLogFilename; } private: Q_DISABLE_COPY(CrashReporting); static char* sPath; + static char* sLogFilename; boost::scoped_ptr handler_; }; diff --git a/src/core/crashreporting_linux.cpp b/src/core/crashreporting_linux.cpp index 95188c17a..bd7c00ad4 100644 --- a/src/core/crashreporting_linux.cpp +++ b/src/core/crashreporting_linux.cpp @@ -42,6 +42,7 @@ bool Handler(const google_breakpad::MinidumpDescriptor& dump, CrashReporting::application_path(), CrashReporting::kSendCrashReportOption, dump.path(), + CrashReporting::log_filename(), NULL }; diff --git a/src/core/crashsender.cpp b/src/core/crashsender.cpp index ca7bd038c..d41fdfa7c 100644 --- a/src/core/crashsender.cpp +++ b/src/core/crashsender.cpp @@ -21,7 +21,6 @@ #include "core/logging.h" #include -#include #include #include #include @@ -36,19 +35,28 @@ const char* CrashSender::kUploadURL = "http://" CRASHREPORTING_HOSTNAME "/upload/crash"; -CrashSender::CrashSender(const QString& path) +CrashSender::CrashSender(const QString& minidump_filename, + const QString& log_filename) : network_(new QNetworkAccessManager(this)), - path_(path), - file_(new QFile(path_, this)), + minidump_filename_(minidump_filename), + log_filename_(log_filename), + minidump_(new QFile(minidump_filename_, this)), + log_(new QFile(log_filename_, this)), progress_(NULL) { } bool CrashSender::Start() { - if (!file_->open(QIODevice::ReadOnly)) { - qLog(Warning) << "Failed to open crash report" << path_; + if (!minidump_->open(QIODevice::ReadOnly)) { + qLog(Warning) << "Failed to open crash report" << minidump_filename_; return false; } + if (!log_filename_.isEmpty()) { + if (!log_->open(QIODevice::ReadOnly)) { + qLog(Warning) << "Failed to open log file" << log_filename_; + } + } + // No tr() here. QMessageBox prompt(QMessageBox::Warning, "Clementine has crashed!", "Clementine has crashed! A crash report has been created and saved to " @@ -90,14 +98,23 @@ void CrashSender::RedirectFinished() { printf("Uploading crash report to %s\n", url.toEncoded().constData()); QNetworkRequest req(url); - // Create the HTTP part for the crash report file - QHttpPart part; - part.setHeader(QNetworkRequest::ContentDispositionHeader, - "form-data; name=\"data\"; filename=\"data.dmp\""); - part.setBodyDevice(file_); - QHttpMultiPart* multi_part = new QHttpMultiPart(QHttpMultiPart::FormDataType); - multi_part->append(part); + + // Create the HTTP part for the crash report file + QHttpPart minidump_part; + minidump_part.setHeader(QNetworkRequest::ContentDispositionHeader, + "form-data; name=\"data\"; filename=\"data.dmp\""); + minidump_part.setBodyDevice(minidump_); + multi_part->append(minidump_part); + + // Create the HTTP part for the log file. + if (log_->isOpen()) { + QHttpPart log_part; + log_part.setHeader(QNetworkRequest::ContentDispositionHeader, + "form-data; name=\"log\"; filename=\"log.txt\""); + log_part.setBodyDevice(log_); + multi_part->append(log_part); + } // Get some information about the thing that crashed and add that to the // request as well. @@ -143,16 +160,6 @@ QList CrashSender::ClientInfo() const { ret.append(ClientInfoPair("version", CLEMENTINE_VERSION_DISPLAY)); ret.append(ClientInfoPair("qt_version", qVersion())); - // Hash the binary - QFile executable(QCoreApplication::applicationFilePath()); - if (executable.open(QIODevice::ReadOnly)) { - QCryptographicHash hash(QCryptographicHash::Md5); - while (!executable.atEnd()) { - hash.addData(executable.read(4096)); - } - ret.append(ClientInfoPair("exe_md5", hash.result().toHex())); - } - // Get the OS version #if defined(Q_OS_MAC) ret.append(ClientInfoPair("os", "mac")); diff --git a/src/core/crashsender.h b/src/core/crashsender.h index 236f6a182..5d4f14aba 100644 --- a/src/core/crashsender.h +++ b/src/core/crashsender.h @@ -33,7 +33,7 @@ class CrashSender : public QObject { Q_OBJECT public: - CrashSender(const QString& path); + CrashSender(const QString& minidump_filename, const QString& log_filename); // Returns false if the user doesn't want to send the crash report (caller // should exit), or true if he does (caller should start the Qt event loop). @@ -54,8 +54,10 @@ private: QNetworkAccessManager* network_; - QString path_; - QFile* file_; + QString minidump_filename_; + QString log_filename_; + QFile* minidump_; + QFile* log_; QProgressDialog* progress_; }; diff --git a/src/internet/somafmurlhandler.cpp b/src/internet/somafmurlhandler.cpp index e724eb6f9..561b07843 100644 --- a/src/internet/somafmurlhandler.cpp +++ b/src/internet/somafmurlhandler.cpp @@ -26,7 +26,6 @@ #include #include #include -#include SomaFMUrlHandler::SomaFMUrlHandler(Application* app, SomaFMService* service, QObject* parent) diff --git a/src/main.cpp b/src/main.cpp index 56d6cb3bc..14c79375e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -60,6 +60,7 @@ #include #include #include +#include #include #include #include @@ -305,6 +306,15 @@ int main(int argc, char *argv[]) { logging::SetLevels(options.log_levels()); g_log_set_default_handler(reinterpret_cast(&logging::GLog), NULL); + // Log to a temporary file that gets included with crash reports. + QTemporaryFile temp_log(QDir::tempPath() + "/clementine-log-XXXXXX.txt"); + if (temp_log.open()) { + logging::AddOutputDevice(&temp_log); + CrashReporting::SetLogFilename(temp_log.fileName()); + + qLog(Info) << "Logging to" << temp_log.fileName(); + } + // Output the version, so when people attach log output to bug reports they // don't have to tell us which version they're using. qLog(Info) << "Clementine" << CLEMENTINE_VERSION_DISPLAY;