diff --git a/CMakeLists.txt b/CMakeLists.txt index b4bcc7c8d..68e2d32d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,7 +23,8 @@ option(USE_OPENGL "Use OpenGL API" ON) option(USE_VIDEOCORE "RPI: use the legacy Broadcom GLES libraries" OFF) option(APPLE_BREAKPAD "macOS: Build breakpad client and dump symbols" 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") @@ -195,8 +196,8 @@ if(IOS) GLES_SILENCE_DEPRECATION) endif() -if(NOT ${SENTRY_UPLOAD_URL} STREQUAL "") - target_compile_definitions(${PROJECT_NAME} PRIVATE SENTRY_UPLOAD=${SENTRY_UPLOAD_URL}) +if(NOT "${SENTRY_UPLOAD_URL}" STREQUAL "") + target_compile_definitions(${PROJECT_NAME} PRIVATE SENTRY_UPLOAD="${SENTRY_UPLOAD_URL}") endif() target_include_directories(${PROJECT_NAME} PRIVATE core core/deps core/deps/stb core/khronos core/deps/json) diff --git a/core/linux-dist/main.cpp b/core/linux-dist/main.cpp index be6c6a195..129bf1822 100644 --- a/core/linux-dist/main.cpp +++ b/core/linux-dist/main.cpp @@ -298,7 +298,7 @@ std::vector find_system_data_dirs() static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context, bool succeeded) { if (succeeded) - registerCrash(descriptor.directory(), descriptor.path()); + registerCrash(descriptor.directory().c_str(), descriptor.path()); return succeeded; } diff --git a/core/log/InMemoryListener.h b/core/log/InMemoryListener.h new file mode 100644 index 000000000..5e67288c8 --- /dev/null +++ b/core/log/InMemoryListener.h @@ -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 . +*/ +#pragma once + +#include "LogManager.h" +#include +#include +#include + +class InMemoryListener : public LogListener +{ +public: + InMemoryListener() { + instance = this; + } + ~InMemoryListener() override { + instance = nullptr; + } + + void Log(LogTypes::LOG_LEVELS, const char* msg) override + { + std::lock_guard lock(mutex); + lines.emplace_back(msg); + if (lines.size() > MaxLines) + lines.pop_front(); + } + + std::vector getLog() + { + std::lock_guard lock(mutex); + std::vector v(lines.begin(), lines.end()); + + return v; + } + + static InMemoryListener *getInstance() { + return instance; + } + +private: + std::mutex mutex; + std::deque lines; + + static constexpr int MaxLines = 10; + static InMemoryListener *instance; +}; diff --git a/core/log/LogManager.cpp b/core/log/LogManager.cpp index 49b7b5749..cd7be720b 100644 --- a/core/log/LogManager.cpp +++ b/core/log/LogManager.cpp @@ -14,6 +14,7 @@ #include #include "ConsoleListener.h" +#include "InMemoryListener.h" #include "Log.h" #include "StringUtil.h" #include "cfg/cfg.h" @@ -150,6 +151,8 @@ LogManager::LogManager() } EnableListener(LogListener::CONSOLE_LISTENER, cfgLoadBool("log", "LogToConsole", true)); // 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) { @@ -164,6 +167,7 @@ LogManager::~LogManager() // The log window listener pointer is owned by the GUI code. delete m_listeners[LogListener::CONSOLE_LISTENER]; delete m_listeners[LogListener::FILE_LISTENER]; + delete m_listeners[LogListener::IN_MEMORY_LISTENER]; } // Return the current time formatted as Minutes:Seconds:Milliseconds @@ -265,3 +269,6 @@ void LogManager::Shutdown() delete s_log_manager; s_log_manager = nullptr; } + +// Another singleton +InMemoryListener *InMemoryListener::instance; diff --git a/core/log/LogManager.h b/core/log/LogManager.h index 14111d1f0..0cbd92255 100644 --- a/core/log/LogManager.h +++ b/core/log/LogManager.h @@ -22,6 +22,7 @@ public: FILE_LISTENER = 0, CONSOLE_LISTENER, LOG_WINDOW_LISTENER, + IN_MEMORY_LISTENER, NUMBER_OF_LISTENERS // Must be last }; diff --git a/core/oslib/oslib.cpp b/core/oslib/oslib.cpp index 1239ed996..12e492dc6 100644 --- a/core/oslib/oslib.cpp +++ b/core/oslib/oslib.cpp @@ -153,17 +153,41 @@ std::string getBiosFontPath() #include "rend/boxart/http_client.h" #include "version.h" +#include "log/InMemoryListener.h" #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) { - fprintf(f, "%s", path); + fprintf(f, "%s\n", path); 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 log = listener->getLog(); + for (const auto& line : log) + fprintf(f, "%s", line.c_str()); + fclose(f); + } + } + } } void uploadCrashes(const std::string& directory) @@ -181,26 +205,34 @@ void uploadCrashes(const std::string& directory) *p = '\0'; if (file_exists(line)) { + std::string dmpfile(line); + std::string logfile = get_file_basename(dmpfile) + ".log"; #ifdef SENTRY_UPLOAD -#define STRINGIZE(x) #x if (config::UploadCrashLogs) { NOTICE_LOG(COMMON, "Uploading minidump %s", line); std::vector 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)); - // TODO log, config, gpu/driver - int rc = http::post(STRINGIZE(SENTRY_UPLOAD), fields); - if (rc >= 200 && rc < 300) - nowide::remove(line); + if (file_exists(logfile)) + fields.emplace_back("flycast_log", logfile, "text/plain"); + // TODO config, gpu/driver, ... + int rc = http::post(SENTRY_UPLOAD, fields); + if (rc >= 200 && rc < 300) { + nowide::remove(dmpfile.c_str()); + nowide::remove(logfile.c_str()); + } else + { + NOTICE_LOG(COMMON, "Upload failed: HTTP error %d", rc); uploadFailure = true; + } } else -#undef STRINGIZE #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 -void registerCrash(const std::string& directory, const char *path) {} +void registerCrash(const char *directory, const char *path) {} void uploadCrashes(const std::string& directory) {} #endif diff --git a/core/oslib/oslib.h b/core/oslib/oslib.h index c9bc2c2e1..124c639be 100644 --- a/core/oslib/oslib.h +++ b/core/oslib/oslib.h @@ -135,5 +135,5 @@ static inline void freeAligned(void *p) #endif } -void registerCrash(const std::string& directory, const char *path); +void registerCrash(const char *directory, const char *path); void uploadCrashes(const std::string& directory); diff --git a/core/windows/winmain.cpp b/core/windows/winmain.cpp index 6738645ca..a2c4647be 100644 --- a/core/windows/winmain.cpp +++ b/core/windows/winmain.cpp @@ -739,7 +739,7 @@ static bool dumpCallback(const wchar_t* dump_path, if (path.convert(minidump_id)) { std::string fullPath = directory + '\\' + std::string(path.c_str()) + ".dmp"; - registerCrash(directory, fullPath.c_str()); + registerCrash(directory.c_str(), fullPath.c_str()); } } } diff --git a/shell/android-studio/flycast/src/main/jni/src/Android.cpp b/shell/android-studio/flycast/src/main/jni/src/Android.cpp index f0fac0f97..32844d9a9 100644 --- a/shell/android-studio/flycast/src/main/jni/src/Android.cpp +++ b/shell/android-studio/flycast/src/main/jni/src/Android.cpp @@ -148,7 +148,7 @@ static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, if (succeeded) { __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; }