sentry: various fixes. Upload last log lines with the minidump
This commit is contained in:
parent
4d77ce5a66
commit
1e0a875fa3
|
@ -23,7 +23,8 @@ option(USE_OPENGL "Use OpenGL API" ON)
|
||||||
option(USE_VIDEOCORE "RPI: use the legacy Broadcom GLES libraries" OFF)
|
option(USE_VIDEOCORE "RPI: use the legacy Broadcom GLES libraries" OFF)
|
||||||
option(APPLE_BREAKPAD "macOS: Build breakpad client and dump symbols" OFF)
|
option(APPLE_BREAKPAD "macOS: Build breakpad client and dump symbols" OFF)
|
||||||
option(ENABLE_GDB_SERVER "Build with GDB debugging support" OFF)
|
option(ENABLE_GDB_SERVER "Build with GDB debugging support" OFF)
|
||||||
option(SENTRY_UPLOAD_URL "Sentry upload URL" "")
|
|
||||||
|
set(SENTRY_UPLOAD_URL "" CACHE STRING "Sentry upload URL")
|
||||||
|
|
||||||
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/shell/cmake")
|
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/shell/cmake")
|
||||||
|
|
||||||
|
@ -195,8 +196,8 @@ if(IOS)
|
||||||
GLES_SILENCE_DEPRECATION)
|
GLES_SILENCE_DEPRECATION)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT ${SENTRY_UPLOAD_URL} STREQUAL "")
|
if(NOT "${SENTRY_UPLOAD_URL}" STREQUAL "")
|
||||||
target_compile_definitions(${PROJECT_NAME} PRIVATE SENTRY_UPLOAD=${SENTRY_UPLOAD_URL})
|
target_compile_definitions(${PROJECT_NAME} PRIVATE SENTRY_UPLOAD="${SENTRY_UPLOAD_URL}")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_include_directories(${PROJECT_NAME} PRIVATE core core/deps core/deps/stb core/khronos core/deps/json)
|
target_include_directories(${PROJECT_NAME} PRIVATE core core/deps core/deps/stb core/khronos core/deps/json)
|
||||||
|
|
|
@ -298,7 +298,7 @@ std::vector<std::string> find_system_data_dirs()
|
||||||
static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context, bool succeeded)
|
static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context, bool succeeded)
|
||||||
{
|
{
|
||||||
if (succeeded)
|
if (succeeded)
|
||||||
registerCrash(descriptor.directory(), descriptor.path());
|
registerCrash(descriptor.directory().c_str(), descriptor.path());
|
||||||
|
|
||||||
return succeeded;
|
return succeeded;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
Copyright 2022 flyinghead
|
||||||
|
|
||||||
|
This file is part of Flycast.
|
||||||
|
|
||||||
|
Flycast is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Flycast is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "LogManager.h"
|
||||||
|
#include <deque>
|
||||||
|
#include <mutex>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class InMemoryListener : public LogListener
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
InMemoryListener() {
|
||||||
|
instance = this;
|
||||||
|
}
|
||||||
|
~InMemoryListener() override {
|
||||||
|
instance = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Log(LogTypes::LOG_LEVELS, const char* msg) override
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mutex);
|
||||||
|
lines.emplace_back(msg);
|
||||||
|
if (lines.size() > MaxLines)
|
||||||
|
lines.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> getLog()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mutex);
|
||||||
|
std::vector<std::string> v(lines.begin(), lines.end());
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
static InMemoryListener *getInstance() {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::mutex mutex;
|
||||||
|
std::deque<std::string> lines;
|
||||||
|
|
||||||
|
static constexpr int MaxLines = 10;
|
||||||
|
static InMemoryListener *instance;
|
||||||
|
};
|
|
@ -14,6 +14,7 @@
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
#include "ConsoleListener.h"
|
#include "ConsoleListener.h"
|
||||||
|
#include "InMemoryListener.h"
|
||||||
#include "Log.h"
|
#include "Log.h"
|
||||||
#include "StringUtil.h"
|
#include "StringUtil.h"
|
||||||
#include "cfg/cfg.h"
|
#include "cfg/cfg.h"
|
||||||
|
@ -150,6 +151,8 @@ LogManager::LogManager()
|
||||||
}
|
}
|
||||||
EnableListener(LogListener::CONSOLE_LISTENER, cfgLoadBool("log", "LogToConsole", true));
|
EnableListener(LogListener::CONSOLE_LISTENER, cfgLoadBool("log", "LogToConsole", true));
|
||||||
// EnableListener(LogListener::LOG_WINDOW_LISTENER, Config::Get(LOGGER_WRITE_TO_WINDOW));
|
// EnableListener(LogListener::LOG_WINDOW_LISTENER, Config::Get(LOGGER_WRITE_TO_WINDOW));
|
||||||
|
RegisterListener(LogListener::IN_MEMORY_LISTENER, new InMemoryListener());
|
||||||
|
EnableListener(LogListener::IN_MEMORY_LISTENER, true);
|
||||||
|
|
||||||
for (LogContainer& container : m_log)
|
for (LogContainer& container : m_log)
|
||||||
{
|
{
|
||||||
|
@ -164,6 +167,7 @@ LogManager::~LogManager()
|
||||||
// The log window listener pointer is owned by the GUI code.
|
// The log window listener pointer is owned by the GUI code.
|
||||||
delete m_listeners[LogListener::CONSOLE_LISTENER];
|
delete m_listeners[LogListener::CONSOLE_LISTENER];
|
||||||
delete m_listeners[LogListener::FILE_LISTENER];
|
delete m_listeners[LogListener::FILE_LISTENER];
|
||||||
|
delete m_listeners[LogListener::IN_MEMORY_LISTENER];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the current time formatted as Minutes:Seconds:Milliseconds
|
// Return the current time formatted as Minutes:Seconds:Milliseconds
|
||||||
|
@ -265,3 +269,6 @@ void LogManager::Shutdown()
|
||||||
delete s_log_manager;
|
delete s_log_manager;
|
||||||
s_log_manager = nullptr;
|
s_log_manager = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Another singleton
|
||||||
|
InMemoryListener *InMemoryListener::instance;
|
||||||
|
|
|
@ -22,6 +22,7 @@ public:
|
||||||
FILE_LISTENER = 0,
|
FILE_LISTENER = 0,
|
||||||
CONSOLE_LISTENER,
|
CONSOLE_LISTENER,
|
||||||
LOG_WINDOW_LISTENER,
|
LOG_WINDOW_LISTENER,
|
||||||
|
IN_MEMORY_LISTENER,
|
||||||
|
|
||||||
NUMBER_OF_LISTENERS // Must be last
|
NUMBER_OF_LISTENERS // Must be last
|
||||||
};
|
};
|
||||||
|
|
|
@ -153,17 +153,41 @@ std::string getBiosFontPath()
|
||||||
|
|
||||||
#include "rend/boxart/http_client.h"
|
#include "rend/boxart/http_client.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
#include "log/InMemoryListener.h"
|
||||||
|
|
||||||
#define FLYCAST_CRASH_LIST "flycast-crashes.txt"
|
#define FLYCAST_CRASH_LIST "flycast-crashes.txt"
|
||||||
|
|
||||||
void registerCrash(const std::string& directory, const char *path)
|
void registerCrash(const char *directory, const char *path)
|
||||||
{
|
{
|
||||||
FILE *f = nowide::fopen((directory + "/" FLYCAST_CRASH_LIST).c_str(), "at");
|
char list[256];
|
||||||
|
// Register .dmp in crash list
|
||||||
|
snprintf(list, sizeof(list), "%s/%s", directory, FLYCAST_CRASH_LIST);
|
||||||
|
FILE *f = nowide::fopen(list, "at");
|
||||||
if (f != nullptr)
|
if (f != nullptr)
|
||||||
{
|
{
|
||||||
fprintf(f, "%s", path);
|
fprintf(f, "%s\n", path);
|
||||||
fclose(f);
|
fclose(f);
|
||||||
}
|
}
|
||||||
|
// Save last log lines
|
||||||
|
InMemoryListener *listener = InMemoryListener::getInstance();
|
||||||
|
if (listener != nullptr)
|
||||||
|
{
|
||||||
|
strncpy(list, path, sizeof(list) - 1);
|
||||||
|
list[sizeof(list) - 1] = '\0';
|
||||||
|
char *p = strrchr(list, '.');
|
||||||
|
if (p != nullptr && (p - list) < (int)sizeof(list) - 4)
|
||||||
|
{
|
||||||
|
strcpy(p + 1, "log");
|
||||||
|
FILE *f = nowide::fopen(list, "wt");
|
||||||
|
if (f != nullptr)
|
||||||
|
{
|
||||||
|
std::vector<std::string> log = listener->getLog();
|
||||||
|
for (const auto& line : log)
|
||||||
|
fprintf(f, "%s", line.c_str());
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void uploadCrashes(const std::string& directory)
|
void uploadCrashes(const std::string& directory)
|
||||||
|
@ -181,26 +205,34 @@ void uploadCrashes(const std::string& directory)
|
||||||
*p = '\0';
|
*p = '\0';
|
||||||
if (file_exists(line))
|
if (file_exists(line))
|
||||||
{
|
{
|
||||||
|
std::string dmpfile(line);
|
||||||
|
std::string logfile = get_file_basename(dmpfile) + ".log";
|
||||||
#ifdef SENTRY_UPLOAD
|
#ifdef SENTRY_UPLOAD
|
||||||
#define STRINGIZE(x) #x
|
|
||||||
if (config::UploadCrashLogs)
|
if (config::UploadCrashLogs)
|
||||||
{
|
{
|
||||||
NOTICE_LOG(COMMON, "Uploading minidump %s", line);
|
NOTICE_LOG(COMMON, "Uploading minidump %s", line);
|
||||||
std::vector<http::PostField> fields;
|
std::vector<http::PostField> fields;
|
||||||
fields.emplace_back("upload_file_minidump", std::string(line), "application/octet-stream");
|
fields.emplace_back("upload_file_minidump", dmpfile, "application/octet-stream");
|
||||||
fields.emplace_back("flycast_version", std::string(GIT_VERSION));
|
fields.emplace_back("flycast_version", std::string(GIT_VERSION));
|
||||||
// TODO log, config, gpu/driver
|
if (file_exists(logfile))
|
||||||
int rc = http::post(STRINGIZE(SENTRY_UPLOAD), fields);
|
fields.emplace_back("flycast_log", logfile, "text/plain");
|
||||||
if (rc >= 200 && rc < 300)
|
// TODO config, gpu/driver, ...
|
||||||
nowide::remove(line);
|
int rc = http::post(SENTRY_UPLOAD, fields);
|
||||||
|
if (rc >= 200 && rc < 300) {
|
||||||
|
nowide::remove(dmpfile.c_str());
|
||||||
|
nowide::remove(logfile.c_str());
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
NOTICE_LOG(COMMON, "Upload failed: HTTP error %d", rc);
|
||||||
uploadFailure = true;
|
uploadFailure = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
#undef STRINGIZE
|
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
nowide::remove(line);
|
nowide::remove(dmpfile.c_str());
|
||||||
|
nowide::remove(logfile.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -212,7 +244,7 @@ void uploadCrashes(const std::string& directory)
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
void registerCrash(const std::string& directory, const char *path) {}
|
void registerCrash(const char *directory, const char *path) {}
|
||||||
void uploadCrashes(const std::string& directory) {}
|
void uploadCrashes(const std::string& directory) {}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -135,5 +135,5 @@ static inline void freeAligned(void *p)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void registerCrash(const std::string& directory, const char *path);
|
void registerCrash(const char *directory, const char *path);
|
||||||
void uploadCrashes(const std::string& directory);
|
void uploadCrashes(const std::string& directory);
|
||||||
|
|
|
@ -739,7 +739,7 @@ static bool dumpCallback(const wchar_t* dump_path,
|
||||||
if (path.convert(minidump_id))
|
if (path.convert(minidump_id))
|
||||||
{
|
{
|
||||||
std::string fullPath = directory + '\\' + std::string(path.c_str()) + ".dmp";
|
std::string fullPath = directory + '\\' + std::string(path.c_str()) + ".dmp";
|
||||||
registerCrash(directory, fullPath.c_str());
|
registerCrash(directory.c_str(), fullPath.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -148,7 +148,7 @@ static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor,
|
||||||
if (succeeded)
|
if (succeeded)
|
||||||
{
|
{
|
||||||
__android_log_print(ANDROID_LOG_ERROR, "Flycast", "Minidump saved to '%s'\n", descriptor.path());
|
__android_log_print(ANDROID_LOG_ERROR, "Flycast", "Minidump saved to '%s'\n", descriptor.path());
|
||||||
registerCrash(descriptor.directory(), descriptor.path());
|
registerCrash(descriptor.directory().c_str(), descriptor.path());
|
||||||
}
|
}
|
||||||
return succeeded;
|
return succeeded;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue