/** * @file SentryWrapper.cpp * @brief Sentry crash reporting wrapper implementation for cross-platform support. * * This implementation provides a unified API for Sentry integration. * When SLIC3R_SENTRY is not defined, all functions become no-ops. */ #include "SentryWrapper.hpp" #ifdef SLIC3R_SENTRY #include "sentry.h" #endif #ifdef _WIN32 #include #include #include #include #pragma comment(lib, "iphlpapi.lib") #endif #ifdef __APPLE__ #include #include #include #include #endif #include #include #include #include #include "common_func/common_func.hpp" #include namespace Slic3r { #ifdef SLIC3R_SENTRY #define SENTRY_EVENT_TRACE "trace" #define SENTRY_EVENT_DEBUG "info" #define SENTRY_EVENT_INFO "debug" #define SENTRY_EVENT_WARNING "warning" #define SENTRY_EVENT_ERROR "error" #define SENTRY_EVENT_FATAL "fatal" #define MACHINE_MODULE "Moonraker_Mqtt" #define SENTRY_KEY_LEVEL "level" static sentry_value_t on_crash_callback(const sentry_ucontext_t* uctx, sentry_value_t event, void* closure) { (void) uctx; (void) closure; // tell the backend to retain the event return event; } static sentry_value_t before_send_log(sentry_value_t log, void* user_dataa) { return log; } void initSentryEx() { sentry_options_t* options = sentry_options_new(); std::string dsn = std::string("https://282935326eecb9758e7f84a2ad3ae0ab@o4508125599563776.ingest.us.sentry.io/4510425163956224"); { sentry_options_set_dsn(options, dsn.c_str()); std::string handlerDir = ""; std::string dataBaseDir = ""; #ifdef __APPLE__ char exe_path[PATH_MAX] = {0}; uint32_t buf_size = PATH_MAX; if (_NSGetExecutablePath(exe_path, &buf_size) != 0) { throw std::runtime_error("Buffer too small for executable path"); } // Get the directory containing the executable, not the executable path itself // Use dirname() to get parent directory (need to copy string as dirname may modify it) char exe_path_copy[PATH_MAX]; strncpy(exe_path_copy, exe_path, PATH_MAX); char* exe_dir = dirname(exe_path_copy); handlerDir = std::string(exe_dir) + "/crashpad_handler"; const char* home_env = getenv("HOME"); dataBaseDir = home_env; dataBaseDir = dataBaseDir + "/Library/Application Support/Snapmaker_Orca/SentryData"; #elif _WIN32 // Use extended path length support for Windows (up to 32767 characters) const DWORD MAX_PATH_EXTENDED = 32767; wchar_t exeDir[MAX_PATH_EXTENDED]; DWORD pathLen = ::GetModuleFileNameW(nullptr, exeDir, MAX_PATH_EXTENDED); // GetModuleFileNameW returns 0 on error, or the number of characters written (excluding null terminator) // If return value equals buffer size, the path was truncated if (pathLen == 0) { // Failed to get module path, use fallback DWORD lastError = GetLastError(); std::cout<< "Failed to get module file name, error: " << lastError; handlerDir = ""; dataBaseDir = ""; } else if (pathLen >= MAX_PATH_EXTENDED) { // Path was truncated, which shouldn't happen with MAX_PATH_EXTENDED std::cout<< "Module file path too long or truncated, length: " << pathLen; handlerDir = ""; dataBaseDir = ""; } else { // Ensure null termination (GetModuleFileNameW should do this, but be safe) exeDir[pathLen] = L'\0'; std::wstring wsExeDir(exeDir, pathLen); size_t nPos = wsExeDir.find_last_of(L'\\'); if (nPos == std::wstring::npos) { // No backslash found, use current directory as fallback std::cout<< "No backslash found in executable path, using current directory"; nPos = 0; } // Ensure nPos + 1 doesn't exceed string length if (nPos + 1 > wsExeDir.length()) { std::cout<< "Invalid path position, using full path"; nPos = wsExeDir.length(); } std::wstring wsDmpDir = wsExeDir.substr(0, nPos + 1); std::wstring desDir = wsDmpDir + L"crashpad_handler.exe"; wsDmpDir += L"dump"; auto wstringTostring = [](const std::wstring& wTmpStr) -> std::string { if (wTmpStr.empty()) return std::string(); int len = WideCharToMultiByte(CP_UTF8, 0, wTmpStr.c_str(), -1, nullptr, 0, nullptr, nullptr); if (len <= 0) { std::cout<< "WideCharToMultiByte failed, error: " << GetLastError(); return std::string(); } // Allocate buffer with size len (includes null terminator) std::string desStr; desStr.resize(len - 1); // Reserve space excluding null terminator int result = WideCharToMultiByte(CP_UTF8, 0, wTmpStr.c_str(), -1, &desStr[0], len, nullptr, nullptr); if (result == 0 || result != len) { std::cout<< "WideCharToMultiByte conversion failed, error: " << GetLastError(); return std::string(); } // Remove null terminator if present (safely check before accessing) if (!desStr.empty() && desStr.back() == '\0') desStr.pop_back(); return desStr; }; handlerDir = wstringTostring(desDir); } // Get LocalAppData folder path PWSTR pszPath = nullptr; char* path = nullptr; size_t pathLength = 0; HRESULT hr = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &pszPath); if (SUCCEEDED(hr) && pszPath != nullptr) { // Calculate required buffer size first size_t wcsLen = wcslen(pszPath); if (wcsLen > 0 && wcsLen < SIZE_MAX / 3) { // Check for overflow // Allocate buffer with extra space for safety size_t requiredSize = wcsLen * 3 + 1; // UTF-8 can be up to 3 bytes per wchar path = new (std::nothrow) char[requiredSize](); if (path != nullptr) { errno_t err = wcstombs_s(&pathLength, path, requiredSize, pszPath, _TRUNCATE); if (err != 0) { std::cout<< "wcstombs_s failed, error: " << err; delete[] path; path = nullptr; } } else { std::cout<< "Failed to allocate memory for path conversion"; } } else if (wcsLen == 0) { std::cout<< "SHGetKnownFolderPath returned empty path"; } else { std::cout<< "Path length overflow detected: " << wcsLen; } // Always free the path returned by SHGetKnownFolderPath CoTaskMemFree(pszPath); pszPath = nullptr; } else { std::cout<< "SHGetKnownFolderPath failed, hr: " << std::hex << hr; // Ensure pszPath is freed even on failure (though it should be nullptr) if (pszPath != nullptr) { CoTaskMemFree(pszPath); pszPath = nullptr; } } if (path != nullptr) { std::string filePath = path; std::string appName = "\\" + std::string("Snapmaker_Orca\\"); dataBaseDir = filePath + appName; delete[] path; path = nullptr; } else { // Fallback: use temp directory char tempPath[MAX_PATH]; if (GetTempPathA(MAX_PATH, tempPath) != 0) { dataBaseDir = std::string(tempPath) + "Snapmaker_Orca\\"; std::cout<< "Using temp directory as fallback for Sentry data: " << dataBaseDir; } else { dataBaseDir = ""; std::cout<< "Failed to get temp path, Sentry data directory will be empty"; } } #endif if (!handlerDir.empty()) sentry_options_set_handler_path(options, handlerDir.c_str()); if (!dataBaseDir.empty()) sentry_options_set_database_path(options, dataBaseDir.c_str()); #if defined(_DEBUG) || !defined(NDEBUG) sentry_options_set_debug(options, 1); #else sentry_options_set_debug(options, 0); #endif //sentry_options_set_environment(options, "develop"); sentry_options_set_environment(options, "Release"); sentry_options_set_auto_session_tracking(options, 0); sentry_options_set_symbolize_stacktraces(options, 1); sentry_options_set_on_crash(options, on_crash_callback, NULL); sentry_options_set_sample_rate(options, 1.0); sentry_options_set_traces_sample_rate(options, 1.0); sentry_options_set_enable_logs(options, 1); sentry_options_set_before_send_log(options, before_send_log, NULL); sentry_options_set_logs_with_attributes(options, true); // Set release version for symbolication // This must match the release used when uploading symbols sentry_options_set_release(options, Snapmaker_VERSION); sentry_init(options); sentry_start_session(); sentry_set_tag("snapmaker_version", Snapmaker_VERSION); std::string flutterVersion = common::get_flutter_version(); if (!flutterVersion.empty()) sentry_set_tag("flutter_version", flutterVersion.c_str()); std::string machineID = common::getMachineId(); if (!machineID.empty()) sentry_set_tag("machine_id", machineID.c_str()); std::string pcName = common::get_pc_name(); if (!pcName.empty()) sentry_set_tag("pc_name", pcName.c_str()); } } void exitSentryEx() { sentry_close(); } void sentryReportLogEx(SENTRY_LOG_LEVEL logLevel, const std::string& logContent, const std::string& funcModule, const std::string& logTagKey, const std::string& logTagValue, const std::string& logTraceId) { if (!get_privacy_policy()) { return; } sentry_level_t sentry_msg_level; sentry_value_t tags = sentry_value_new_object(); if (!funcModule.empty()) { sentry_value_t attr = sentry_value_new_attribute(sentry_value_new_string(funcModule.c_str()), NULL); sentry_value_set_by_key(tags, "function_module", attr); } if (!logTraceId.empty()) { sentry_value_t attr = sentry_value_new_attribute(sentry_value_new_string(logTraceId.c_str()), NULL); sentry_value_set_by_key(tags, "snapmaker_trace_id", attr); } if (!logTagKey.empty()) { sentry_value_t attr = sentry_value_new_attribute(sentry_value_new_string(logTagValue.c_str()), NULL); sentry_value_set_by_key(tags, logTagKey.c_str(), attr); } sentry_value_t attr = sentry_value_new_attribute(sentry_value_new_string(Snapmaker_VERSION), NULL); sentry_value_set_by_key(tags, "snapmaker_version", attr); std::string flutterVersion = common::get_flutter_version(); if (!flutterVersion.empty()) { sentry_value_t attr = sentry_value_new_attribute(sentry_value_new_string(flutterVersion.c_str()), NULL); sentry_value_set_by_key(tags, "flutter_version", attr); } std::string pcName = common::get_pc_name(); if (!pcName.empty()) { sentry_value_t attr = sentry_value_new_attribute(sentry_value_new_string(pcName.c_str()), NULL); sentry_value_set_by_key(tags, "pc_name", attr); } static std::string machineID = ""; if (machineID.empty()) machineID = common::getMachineId(); if (!machineID.empty()) { sentry_value_t attr = sentry_value_new_attribute(sentry_value_new_string(machineID.c_str()), NULL); sentry_value_set_by_key(tags, "machine_id", attr); } static std::string currentLanguage = ""; if (currentLanguage.empty()) currentLanguage = common::getLanguage(); if (!currentLanguage.empty()) { sentry_value_t attr = sentry_value_new_attribute(sentry_value_new_string(currentLanguage.c_str()), NULL); sentry_value_set_by_key(tags, "current_language", attr); } static std::string localArea = ""; if (localArea.empty()) localArea = common::getLocalArea(); if (!localArea.empty()) { sentry_value_t attr = sentry_value_new_attribute(sentry_value_new_string(localArea.c_str()), NULL); sentry_value_set_by_key(tags, "local_area", attr); } switch (logLevel) { case SENTRY_LOG_TRACE: { sentry_msg_level = SENTRY_LEVEL_TRACE; sentry_value_t attr = sentry_value_new_attribute(sentry_value_new_string("snapmaker_bury_point"), NULL); sentry_value_set_by_key(tags, BURY_POINT, attr); sentry_log_trace(logContent.c_str(), tags); } break; case SENTRY_LOG_DEBUG: { sentry_msg_level = SENTRY_LEVEL_DEBUG; sentry_log_debug(logContent.c_str(), tags); } break; case SENTRY_LOG_INFO: { sentry_msg_level = SENTRY_LEVEL_INFO; sentry_log_info(logContent.c_str(), tags); } break; case SENTRY_LOG_WARNING: { sentry_msg_level = SENTRY_LEVEL_WARNING; sentry_log_warn(logContent.c_str(), tags); } break; case SENTRY_LOG_ERROR: { sentry_msg_level = SENTRY_LEVEL_ERROR; sentry_log_error(logContent.c_str(), tags); } break; case SENTRY_LOG_FATAL: { sentry_msg_level = SENTRY_LEVEL_FATAL; sentry_log_fatal(logContent.c_str(), tags); } break; default: return; } } #else // SLIC3R_SENTRY not defined - provide no-op implementations #endif // SLIC3R_SENTRY void initSentry() { #ifdef SLIC3R_SENTRY initSentryEx(); #endif } void exitSentry() { #ifdef SLIC3R_SENTRY exitSentryEx(); #endif } void sentryReportLog(SENTRY_LOG_LEVEL logLevel, const std::string& logContent, const std::string& funcModule, const std::string& logTagKey, const std::string& logTagValue, const std::string& logTraceId) { #ifdef SLIC3R_SENTRY sentryReportLogEx(logLevel, logContent, funcModule, logTagKey, logTagValue, logTraceId); #endif } void set_sentry_tags(const std::string& tag_key, const std::string& tag_value) { #ifdef SLIC3R_SENTRY if (!tag_key.empty()) sentry_set_tag(tag_key.c_str(), tag_value.c_str()); #endif } } // namespace Slic3r