Files
OrcaSlicer/src/sentry_wrapper/SentryWrapper.cpp
2026-01-30 17:13:59 +08:00

426 lines
15 KiB
C++

/**
* @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 <Windows.h>
#include <shlobj.h>
#include <stdlib.h>
#include <iphlpapi.h>
#pragma comment(lib, "iphlpapi.lib")
#endif
#ifdef __APPLE__
#include <unistd.h>
#include <mach-o/dyld.h>
#include <libgen.h>
#include <string.h>
#endif
#include <cstdlib>
#include <atomic>
#include <random>
#include <mutex>
#include "common_func/common_func.hpp"
#include <iostream>
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