Include the log in crash reports

This commit is contained in:
David Sansome 2012-12-30 16:07:21 +11:00
parent e1323e9cf4
commit 64bae947cc
9 changed files with 100 additions and 36 deletions

View File

@ -25,8 +25,11 @@
#include <execinfo.h>
#endif
#include <QAbstractSocket>
#include <QCoreApplication>
#include <QDateTime>
#include <QFile>
#include <QLocalSocket>
#include <QStringList>
#include <glib.h>
@ -36,15 +39,32 @@
namespace logging {
static Level sDefaultLevel = Level_Debug;
static QMap<QString, Level>* 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<QString, Level>* sClassLevels = NULL;
QIODevice* sNullDevice = NULL;
const char* kMessageHandlerMagic = "__logging_message__";
const int kMessageHandlerMagicLength = strlen(kMessageHandlerMagic);
QtMsgHandler sOriginalMessageHandler = NULL;
QList<QIODevice*> 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<QFile*>(device)) {
file->flush();
} else if (QAbstractSocket* socket = qobject_cast<QAbstractSocket*>(device)) {
socket->flush();
} else if (QLocalSocket* socket = qobject_cast<QLocalSocket*>(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;

View File

@ -48,6 +48,7 @@ namespace logging {
void Init();
void SetLevels(const QString& levels);
void AddOutputDevice(QIODevice* device);
void DumpStackTrace();

View File

@ -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());
}

View File

@ -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<google_breakpad::ExceptionHandler> handler_;
};

View File

@ -42,6 +42,7 @@ bool Handler(const google_breakpad::MinidumpDescriptor& dump,
CrashReporting::application_path(),
CrashReporting::kSendCrashReportOption,
dump.path(),
CrashReporting::log_filename(),
NULL
};

View File

@ -21,7 +21,6 @@
#include "core/logging.h"
#include <QCoreApplication>
#include <QCryptographicHash>
#include <QFile>
#include <QHttpMultiPart>
#include <QMessageBox>
@ -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::ClientInfoPair> 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"));

View File

@ -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_;
};

View File

@ -26,7 +26,6 @@
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QSettings>
#include <QTemporaryFile>
SomaFMUrlHandler::SomaFMUrlHandler(Application* app, SomaFMService* service,
QObject* parent)

View File

@ -60,6 +60,7 @@
#include <QSslSocket>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QTemporaryFile>
#include <QTextCodec>
#include <QTranslator>
#include <QtConcurrentRun>
@ -305,6 +306,15 @@ int main(int argc, char *argv[]) {
logging::SetLevels(options.log_levels());
g_log_set_default_handler(reinterpret_cast<GLogFunc>(&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;