aboutsummaryrefslogtreecommitdiff
path: root/src/zencore/sentryintegration.cpp
diff options
context:
space:
mode:
authorDan Engelbrecht <[email protected]>2025-05-06 16:50:57 +0200
committerGitHub Enterprise <[email protected]>2025-05-06 16:50:57 +0200
commit6d9ff7e404a22ed1cc7e529cfa77ef7d593d9547 (patch)
tree5cfea359c44b02fe72ab5b166e9b03900444fcba /src/zencore/sentryintegration.cpp
parentcleanup changelog (diff)
downloadzen-6d9ff7e404a22ed1cc7e529cfa77ef7d593d9547.tar.xz
zen-6d9ff7e404a22ed1cc7e529cfa77ef7d593d9547.zip
add sentry for zen command (#373)
* refactor sentry integration and add to zen command line tool * move add_ldflags("-framework Security")
Diffstat (limited to 'src/zencore/sentryintegration.cpp')
-rw-r--r--src/zencore/sentryintegration.cpp327
1 files changed, 327 insertions, 0 deletions
diff --git a/src/zencore/sentryintegration.cpp b/src/zencore/sentryintegration.cpp
new file mode 100644
index 000000000..d08fb7f1d
--- /dev/null
+++ b/src/zencore/sentryintegration.cpp
@@ -0,0 +1,327 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include <zencore/sentryintegration.h>
+
+#include <zencore/config.h>
+#include <zencore/logging.h>
+#include <zencore/session.h>
+#include <zencore/uid.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+
+#if ZEN_PLATFORM_LINUX
+# include <pwd.h>
+#endif
+
+#if ZEN_PLATFORM_MAC
+# include <pwd.h>
+#endif
+
+ZEN_THIRD_PARTY_INCLUDES_START
+#include <spdlog/spdlog.h>
+ZEN_THIRD_PARTY_INCLUDES_END
+
+#if ZEN_USE_SENTRY
+# define SENTRY_BUILD_STATIC 1
+ZEN_THIRD_PARTY_INCLUDES_START
+# include <sentry.h>
+# include <spdlog/sinks/base_sink.h>
+ZEN_THIRD_PARTY_INCLUDES_END
+
+namespace sentry {
+
+struct SentryAssertImpl : zen::AssertImpl
+{
+ virtual void ZEN_FORCENOINLINE ZEN_DEBUG_SECTION
+ OnAssert(const char* Filename, int LineNumber, const char* FunctionName, const char* Msg, zen::CallstackFrames* Callstack) override;
+};
+
+class sentry_sink final : public spdlog::sinks::base_sink<spdlog::details::null_mutex>
+{
+public:
+ sentry_sink();
+ ~sentry_sink();
+
+protected:
+ void sink_it_(const spdlog::details::log_msg& msg) override;
+ void flush_() override;
+};
+
+//////////////////////////////////////////////////////////////////////////
+
+static constexpr sentry_level_t MapToSentryLevel[spdlog::level::level_enum::n_levels] = {SENTRY_LEVEL_DEBUG,
+ SENTRY_LEVEL_DEBUG,
+ SENTRY_LEVEL_INFO,
+ SENTRY_LEVEL_WARNING,
+ SENTRY_LEVEL_ERROR,
+ SENTRY_LEVEL_FATAL,
+ SENTRY_LEVEL_DEBUG};
+
+sentry_sink::sentry_sink()
+{
+}
+sentry_sink::~sentry_sink()
+{
+}
+
+void
+sentry_sink::sink_it_(const spdlog::details::log_msg& msg)
+{
+ if (msg.level != spdlog::level::err && msg.level != spdlog::level::critical)
+ {
+ return;
+ }
+ try
+ {
+ std::string Message = fmt::format("{}\n{}({}) [{}]", msg.payload, msg.source.filename, msg.source.line, msg.source.funcname);
+ sentry_value_t event = sentry_value_new_message_event(
+ /* level */ MapToSentryLevel[msg.level],
+ /* logger */ nullptr,
+ /* message */ Message.c_str());
+ sentry_event_value_add_stacktrace(event, NULL, 0);
+ sentry_capture_event(event);
+ }
+ catch (const std::exception&)
+ {
+ // If our logging with Message formatting fails we do a non-allocating version and just post the msg.payload raw
+ char TmpBuffer[256];
+ size_t MaxCopy = zen::Min<size_t>(msg.payload.size(), size_t(255));
+ memcpy(TmpBuffer, msg.payload.data(), MaxCopy);
+ TmpBuffer[MaxCopy] = '\0';
+ sentry_value_t event = sentry_value_new_message_event(
+ /* level */ SENTRY_LEVEL_ERROR,
+ /* logger */ nullptr,
+ /* message */ TmpBuffer);
+ sentry_event_value_add_stacktrace(event, NULL, 0);
+ sentry_capture_event(event);
+ }
+}
+void
+sentry_sink::flush_()
+{
+}
+
+void
+SentryAssertImpl::OnAssert(const char* Filename, int LineNumber, const char* FunctionName, const char* Msg, zen::CallstackFrames* Callstack)
+{
+ // Sentry will provide its own callstack
+ ZEN_UNUSED(Callstack);
+ try
+ {
+ std::string Message = fmt::format("ASSERT {}:({}) [{}]\n\"{}\"", Filename, LineNumber, FunctionName, Msg);
+ sentry_value_t event = sentry_value_new_message_event(
+ /* level */ SENTRY_LEVEL_ERROR,
+ /* logger */ nullptr,
+ /* message */ Message.c_str());
+ sentry_event_value_add_stacktrace(event, NULL, 0);
+ sentry_capture_event(event);
+ }
+ catch (const std::exception&)
+ {
+ // If our logging with Message formatting fails we do a non-allocating version and just post the Msg raw
+ sentry_value_t event = sentry_value_new_message_event(
+ /* level */ SENTRY_LEVEL_ERROR,
+ /* logger */ nullptr,
+ /* message */ Msg);
+ sentry_event_value_add_stacktrace(event, NULL, 0);
+ sentry_capture_event(event);
+ }
+}
+
+} // namespace sentry
+
+namespace zen {
+
+# if ZEN_USE_SENTRY
+static void
+SentryLogFunction(sentry_level_t Level, const char* Message, va_list Args, [[maybe_unused]] void* Userdata)
+{
+ char LogMessageBuffer[160];
+ std::string LogMessage;
+ const char* MessagePtr = LogMessageBuffer;
+
+ int n = vsnprintf(LogMessageBuffer, sizeof LogMessageBuffer, Message, Args);
+
+ if (n >= int(sizeof LogMessageBuffer))
+ {
+ LogMessage.resize(n + 1);
+
+ n = vsnprintf(LogMessage.data(), LogMessage.size(), Message, Args);
+
+ MessagePtr = LogMessage.c_str();
+ }
+
+ switch (Level)
+ {
+ case SENTRY_LEVEL_DEBUG:
+ ZEN_CONSOLE_DEBUG("sentry: {}", MessagePtr);
+ break;
+
+ case SENTRY_LEVEL_INFO:
+ ZEN_CONSOLE_INFO("sentry: {}", MessagePtr);
+ break;
+
+ case SENTRY_LEVEL_WARNING:
+ ZEN_CONSOLE_WARN("sentry: {}", MessagePtr);
+ break;
+
+ case SENTRY_LEVEL_ERROR:
+ ZEN_CONSOLE_ERROR("sentry: {}", MessagePtr);
+ break;
+
+ case SENTRY_LEVEL_FATAL:
+ ZEN_CONSOLE_CRITICAL("sentry: {}", MessagePtr);
+ break;
+ }
+}
+# endif
+
+SentryIntegration::SentryIntegration()
+{
+}
+
+SentryIntegration::~SentryIntegration()
+{
+ if (m_IsInitialized && m_SentryErrorCode == 0)
+ {
+ logging::SetErrorLog("");
+ m_SentryAssert.reset();
+ sentry_close();
+ }
+}
+
+void
+SentryIntegration::Initialize(std::string SentryDatabasePath,
+ std::string SentryAttachmentsPath,
+ bool AllowPII,
+ const std::string& CommandLine)
+{
+ m_AllowPII = AllowPII;
+
+ if (SentryDatabasePath.starts_with("\\\\?\\"))
+ {
+ SentryDatabasePath = SentryDatabasePath.substr(4);
+ }
+ sentry_options_t* SentryOptions = sentry_options_new();
+ sentry_options_set_dsn(SentryOptions, "https://[email protected]/5919284");
+ sentry_options_set_database_path(SentryOptions, SentryDatabasePath.c_str());
+ sentry_options_set_logger(SentryOptions, SentryLogFunction, this);
+ if (!SentryAttachmentsPath.empty())
+ {
+ if (SentryAttachmentsPath.starts_with("\\\\?\\"))
+ {
+ SentryAttachmentsPath = SentryAttachmentsPath.substr(4);
+ }
+ sentry_options_add_attachment(SentryOptions, SentryAttachmentsPath.c_str());
+ }
+ sentry_options_set_release(SentryOptions, ZEN_CFG_VERSION);
+
+ // sentry_options_set_debug(SentryOptions, 1);
+
+ m_SentryErrorCode = sentry_init(SentryOptions);
+
+ if (m_SentryErrorCode == 0)
+ {
+ sentry_value_t SentryUserObject = sentry_value_new_object();
+
+ if (m_AllowPII)
+ {
+# if ZEN_PLATFORM_WINDOWS
+ CHAR Buffer[511 + 1];
+ DWORD BufferLength = sizeof(Buffer) / sizeof(CHAR);
+ BOOL OK = GetUserNameA(Buffer, &BufferLength);
+ if (OK && BufferLength)
+ {
+ m_SentryUserName = std::string(Buffer, BufferLength - 1);
+ }
+ BufferLength = sizeof(Buffer) / sizeof(CHAR);
+ OK = GetComputerNameA(Buffer, &BufferLength);
+ if (OK && BufferLength)
+ {
+ m_SentryHostName = std::string(Buffer, BufferLength);
+ }
+ else
+ {
+ m_SentryHostName = "unknown";
+ }
+# endif // ZEN_PLATFORM_WINDOWS
+
+# if (ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC)
+ uid_t uid = geteuid();
+ struct passwd* pw = getpwuid(uid);
+ if (pw)
+ {
+ m_SentryUserName = std::string(pw->pw_name);
+ }
+ else
+ {
+ m_SentryUserName = "unknown";
+ }
+ char HostNameBuffer[1023 + 1];
+ int err = gethostname(HostNameBuffer, sizeof(HostNameBuffer));
+ if (err == 0)
+ {
+ m_SentryHostName = std::string(HostNameBuffer);
+ }
+ else
+ {
+ m_SentryHostName = "unknown";
+ }
+# endif
+ m_SentryId = fmt::format("{}@{}", m_SentryUserName, m_SentryHostName);
+ sentry_value_set_by_key(SentryUserObject, "id", sentry_value_new_string(m_SentryId.c_str()));
+ sentry_value_set_by_key(SentryUserObject, "username", sentry_value_new_string(m_SentryUserName.c_str()));
+ sentry_value_set_by_key(SentryUserObject, "ip_address", sentry_value_new_string("{{auto}}"));
+ }
+
+ sentry_value_set_by_key(SentryUserObject, "cmd", sentry_value_new_string(CommandLine.c_str()));
+
+ const std::string SessionId(GetSessionIdString());
+ sentry_value_set_by_key(SentryUserObject, "session", sentry_value_new_string(SessionId.c_str()));
+
+ sentry_set_user(SentryUserObject);
+
+ m_SentryLogger = spdlog::create<sentry::sentry_sink>("sentry");
+ logging::SetErrorLog("sentry");
+
+ m_SentryAssert = std::make_unique<sentry::SentryAssertImpl>();
+ }
+
+ m_IsInitialized = true;
+}
+
+void
+SentryIntegration::LogStartupInformation()
+{
+ if (m_IsInitialized)
+ {
+ if (m_SentryErrorCode == 0)
+ {
+ if (m_AllowPII)
+ {
+ ZEN_INFO("sentry initialized, username: '{}', hostname: '{}', id: '{}'", m_SentryUserName, m_SentryHostName, m_SentryId);
+ }
+ else
+ {
+ ZEN_INFO("sentry initialized with anonymous reports");
+ }
+ }
+ else
+ {
+ ZEN_WARN(
+ "sentry_init returned failure! (error code: {}) note that sentry expects crashpad_handler to exist alongside the running "
+ "executable",
+ m_SentryErrorCode);
+ }
+ }
+}
+
+void
+SentryIntegration::ClearCaches()
+{
+ sentry_clear_modulecache();
+}
+
+} // namespace zen
+#endif