diff --git a/obs-studio-server/source/nodeobs_api.cpp b/obs-studio-server/source/nodeobs_api.cpp index 570081273..527b2868e 100644 --- a/obs-studio-server/source/nodeobs_api.cpp +++ b/obs-studio-server/source/nodeobs_api.cpp @@ -543,7 +543,7 @@ static void node_obs_log(int log_level, const char *msg, va_list args, void *par } // Internal Log - logReport.push(newmsg, log_level); + logReport.push(newmsg); // Std Out / Std Err /// Why fwrite and not std::cout and std::cerr? @@ -885,7 +885,7 @@ void OBS_API::OBS_API_initAPI(void *data, const int64_t id, const std::vectorlogStream.is_open()) { logParam.reset(); - util::CrashManager::AddWarning("Error on log file, failed to open: " + log_path); + util::CrashManager::AddServerWarning("Error on log file, failed to open: " + log_path); std::cerr << "Failed to open log file" << std::endl; } base_set_log_handler(node_obs_log, (logParam) ? logParam.release() : nullptr); @@ -964,7 +964,7 @@ void OBS_API::OBS_API_initAPI(void *data, const int64_t id, const std::vector &OBS_API::getOBSLogErrors() +std::deque OBS_API::snapshotOBSLogGeneral() { - return logReport.errors; -} + std::unique_lock lock(logMutex, std::try_to_lock); + if (!lock.owns_lock()) + return {}; -const std::vector &OBS_API::getOBSLogWarnings() -{ - return logReport.warnings; -} - -std::queue &OBS_API::getOBSLogGeneral() -{ - return logReport.general; + std::deque snapshot = std::move(logReport.general); + logReport.general.clear(); + return snapshot; } std::string OBS_API::getCurrentVersion() diff --git a/obs-studio-server/source/nodeobs_api.h b/obs-studio-server/source/nodeobs_api.h index bfcd189a5..30a92eb58 100644 --- a/obs-studio-server/source/nodeobs_api.h +++ b/obs-studio-server/source/nodeobs_api.h @@ -28,7 +28,7 @@ #include #include #include -#include +#include #include "nodeobs_configManager.hpp" #include "nodeobs_service.h" #include "util-osx.hpp" @@ -44,27 +44,17 @@ class OBS_API { public: struct LogReport { - static const int MaximumGeneralMessages = 150; + static const int MaximumMessages = 150; - void push(std::string message, int logLevel) + void push(std::string message) { - general.push(message); - if (general.size() >= MaximumGeneralMessages) { - general.pop(); - } - - if (logLevel == LOG_ERROR) { - errors.push_back(message); - } - - if (logLevel == LOG_WARNING) { - warnings.push_back(message); + general.push_back(message); + if (general.size() > MaximumMessages) { + general.pop_front(); } } - std::vector errors; - std::vector warnings; - std::queue general; + std::deque general; }; struct OutputStats { @@ -130,9 +120,10 @@ class OBS_API { static double getMemoryUsage(); static void getCurrentOutputStats(obs_output_t *output, OBS_API::OutputStats &outputStats); - static const std::vector &getOBSLogErrors(); - static const std::vector &getOBSLogWarnings(); - static std::queue &getOBSLogGeneral(); + // Snapshot (and clear) the general log tail under logMutex. Uses try_lock — the crash + // handler calls this and must never block on the logging thread. Returns oldest-first, + // empty if the lock can't be acquired. + static std::deque snapshotOBSLogGeneral(); static std::string getCurrentVersion(); static std::string getUsername(); diff --git a/obs-studio-server/source/nodeobs_service.cpp b/obs-studio-server/source/nodeobs_service.cpp index 587166bdc..8ecee5806 100644 --- a/obs-studio-server/source/nodeobs_service.cpp +++ b/obs-studio-server/source/nodeobs_service.cpp @@ -1203,7 +1203,7 @@ void OBS_service::setupRecordingAudioEncoder(void) nameStream.str().c_str(), i)) { std::ostringstream errorStream; errorStream << "audio encoder failed id: " << id << nameStream.str(); - util::CrashManager::AddWarning(errorStream.str()); + util::CrashManager::AddServerWarning(errorStream.str()); throw std::runtime_error("Failed to create audio encoder (advanced output)"); } obs_encoder_set_audio(AdvancedRecordingAudioTracks[i], obs_get_audio()); @@ -3577,7 +3577,7 @@ void WaitForAllOutputsToStop() const std::string crashMessage = "Timed out waiting for outputs to stop during shutdown: " + busyOutputs; blog(LOG_ERROR, "%s", crashMessage.c_str()); - util::CrashManager::AddWarning(crashMessage); + util::CrashManager::AddServerWarning(crashMessage); #ifdef WIN32 util::CrashManager::GetMetricsProvider()->BlameServer(); #endif diff --git a/obs-studio-server/source/util-crashmanager.cpp b/obs-studio-server/source/util-crashmanager.cpp index cde3e2a3a..04f7be3ca 100644 --- a/obs-studio-server/source/util-crashmanager.cpp +++ b/obs-studio-server/source/util-crashmanager.cpp @@ -19,12 +19,14 @@ #include "util-crashmanager.h" #include "util-metricsprovider.h" +#include #include #include #include #include #include #include +#include #include #include #include @@ -73,9 +75,9 @@ ////////////////////// // STATIC VARIABLES // ////////////////////// -std::vector breadcrumbs; std::queue> lastActions; -std::vector warnings; +std::deque serverWarnings; +constexpr size_t MaximumServerWarnings = 50; std::mutex messageMutex; #ifdef WIN32 // Global/static variables @@ -717,13 +719,16 @@ void util::CrashManager::HandleCrash(const std::string &_crashInfo, bool callAbo } catch (...) { } + // Annotations attached to the Sentry minidump: + // "OBS log general" — rolling tail of the libOBS log (capped at LogReport::MaximumMessages lines) + // "Last actions" — recent IPC calls received by the server (capped at MaximumActionsRegistered) + // "Server warnings" — server-detected anomalies recorded via AddServerWarning (capped at MaximumServerWarnings) try { - annotations.insert({{"OBS log general", RequestOBSLog(OBSLogType::General).dump(4)}}); + annotations.insert({{"OBS log general", RequestOBSLog().dump(4)}}); annotations.insert({{"Crash reason", _crashInfo}}); annotations.insert({{"Computer name", computerName}}); - annotations.insert({{"Breadcrumbs", ComputeBreadcrumbs().dump(4)}}); annotations.insert({{"Last actions", ComputeActions().dump(4)}}); - annotations.insert({{"Warnings", ComputeWarnings().dump(4)}}); + annotations.insert({{"Server warnings", ComputeServerWarnings().dump(4)}}); } catch (...) { } @@ -1021,47 +1026,14 @@ void RewindCallStack() return; } -nlohmann::json util::CrashManager::RequestOBSLog(OBSLogType type) +nlohmann::json util::CrashManager::RequestOBSLog() { nlohmann::json result; - switch (type) { - case OBSLogType::Errors: { - auto &errors = OBS_API::getOBSLogErrors(); - for (auto &msg : errors) - result.push_back(msg); - break; - } - - case OBSLogType::Warnings: { - auto &warnings = OBS_API::getOBSLogWarnings(); - for (auto &msg : warnings) - result.push_back(msg); - break; - } - - case OBSLogType::General: { - auto &general = OBS_API::getOBSLogGeneral(); - while (!general.empty()) { - result.push_back(general.front()); - general.pop(); - } - - break; - } - } - - std::reverse(result.begin(), result.end()); - - return result; -} - -nlohmann::json util::CrashManager::ComputeBreadcrumbs() -{ - nlohmann::json result = nlohmann::json::array(); - - for (auto &msg : breadcrumbs) - result.push_back(msg); + // snapshotOBSLogGeneral returns oldest-first under logMutex; emit newest-first. + std::deque general = OBS_API::snapshotOBSLogGeneral(); + for (auto it = general.rbegin(); it != general.rend(); ++it) + result.push_back(*it); return result; } @@ -1086,11 +1058,17 @@ nlohmann::json util::CrashManager::ComputeActions() return result; } -nlohmann::json util::CrashManager::ComputeWarnings() +nlohmann::json util::CrashManager::ComputeServerWarnings() { nlohmann::json result; - for (auto &msg : warnings) + // try_lock — the crashing thread may already hold messageMutex (e.g. it crashed inside + // AddServerWarning/RegisterAction); a blocking lock would hang crash reporting. + std::unique_lock lock(messageMutex, std::try_to_lock); + if (!lock.owns_lock()) + return result; + + for (auto &msg : serverWarnings) result.push_back(msg); return result; @@ -1249,10 +1227,13 @@ void util::CrashManager::IPCValuesToData(const std::vector &values, } } -void util::CrashManager::AddWarning(const std::string &warning) +void util::CrashManager::AddServerWarning(const std::string &warning) { std::lock_guard lock(messageMutex); - warnings.push_back(warning); + serverWarnings.push_back(warning); + if (serverWarnings.size() > MaximumServerWarnings) { + serverWarnings.pop_front(); + } } void RegisterAction(const std::string &message) @@ -1265,33 +1246,12 @@ void RegisterAction(const std::string &message) lastActions.back().first++; } else { lastActions.push({0, message}); - if (lastActions.size() >= MaximumActionsRegistered) { + if (lastActions.size() > MaximumActionsRegistered) { lastActions.pop(); } } } -void util::CrashManager::AddBreadcrumb(const nlohmann::json &message) -{ - std::lock_guard lock(messageMutex); - breadcrumbs.push_back(message); -} - -void util::CrashManager::AddBreadcrumb(const std::string &message) -{ - nlohmann::json j = nlohmann::json::array(); - j.push_back({{message}}); - - std::lock_guard lock(messageMutex); - breadcrumbs.push_back(j); -} - -void util::CrashManager::ClearBreadcrumbs() -{ - std::lock_guard lock(messageMutex); - breadcrumbs.clear(); -} - void util::CrashManager::setAppState(const std::string &newState) { appState = newState; @@ -1349,10 +1309,10 @@ void util::CrashManager::ProcessPreServerCall(const std::string &cname, const st void util::CrashManager::ProcessPostServerCall(const std::string &cname, const std::string &fname, const std::vector &args) { if (args.size() == 0) { - AddWarning(std::string("No return params on method ") + fname + std::string(" for class ") + cname); + AddServerWarning(std::string("No return params on method ") + fname + std::string(" for class ") + cname); } else if ((ErrorCode)args[0].value_union.ui64 != ErrorCode::Ok) { - AddWarning(std::string("Server call returned error number ") + std::to_string(args[0].value_union.ui64) + " on method " + fname + - std::string(" for class ") + cname); + AddServerWarning(std::string("Server call returned error number ") + std::to_string(args[0].value_union.ui64) + " on method " + fname + + std::string(" for class ") + cname); } } diff --git a/obs-studio-server/source/util-crashmanager.h b/obs-studio-server/source/util-crashmanager.h index 5efbd94eb..b421c2661 100644 --- a/obs-studio-server/source/util-crashmanager.h +++ b/obs-studio-server/source/util-crashmanager.h @@ -40,19 +40,13 @@ namespace util { class MetricsProvider; class CrashManager { -public: - enum OBSLogType { General, Errors, Warnings }; - public: bool Initialize(char *path, const std::string &app_state_path); void Configure(); void OpenConsole(); static void IPCValuesToData(const std::vector &, nlohmann::json &); - static void AddWarning(const std::string &warning); - static void AddBreadcrumb(const nlohmann::json &message); - static void AddBreadcrumb(const std::string &message); - static void ClearBreadcrumbs(); + static void AddServerWarning(const std::string &warning); static void DisableReports(); static void setAppState(const std::string &newState); static std::string getAppState(); @@ -84,10 +78,9 @@ class CrashManager { #endif private: - static nlohmann::json RequestOBSLog(OBSLogType type); - static nlohmann::json ComputeBreadcrumbs(); + static nlohmann::json RequestOBSLog(); static nlohmann::json ComputeActions(); - static nlohmann::json ComputeWarnings(); + static nlohmann::json ComputeServerWarnings(); static bool SetupCrashpad(); static bool TryHandleCrash(const std::string &format, const std::string &crashMessage); static void HandleExit() noexcept;