aboutsummaryrefslogtreecommitdiff
path: root/src/zencore
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
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')
-rw-r--r--src/zencore/include/zencore/sentryintegration.h50
-rw-r--r--src/zencore/sentryintegration.cpp327
-rw-r--r--src/zencore/xmake.lua21
3 files changed, 398 insertions, 0 deletions
diff --git a/src/zencore/include/zencore/sentryintegration.h b/src/zencore/include/zencore/sentryintegration.h
new file mode 100644
index 000000000..40e22af4e
--- /dev/null
+++ b/src/zencore/include/zencore/sentryintegration.h
@@ -0,0 +1,50 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <zencore/intmath.h>
+#include <zencore/zencore.h>
+
+#if !defined(ZEN_USE_SENTRY)
+# define ZEN_USE_SENTRY 1
+#endif
+
+#if ZEN_USE_SENTRY
+
+# include <memory>
+
+ZEN_THIRD_PARTY_INCLUDES_START
+# include <spdlog/logger.h>
+ZEN_THIRD_PARTY_INCLUDES_END
+
+namespace sentry {
+
+struct SentryAssertImpl;
+
+} // namespace sentry
+
+namespace zen {
+
+class SentryIntegration
+{
+public:
+ SentryIntegration();
+ ~SentryIntegration();
+
+ void Initialize(std::string SentryDatabasePath, std::string SentryAttachmentsPath, bool AllowPII, const std::string& CommandLine);
+ void LogStartupInformation();
+ static void ClearCaches();
+
+private:
+ int m_SentryErrorCode = 0;
+ bool m_IsInitialized = false;
+ bool m_AllowPII = false;
+ std::unique_ptr<sentry::SentryAssertImpl> m_SentryAssert;
+ std::string m_SentryUserName;
+ std::string m_SentryHostName;
+ std::string m_SentryId;
+ std::shared_ptr<spdlog::logger> m_SentryLogger;
+};
+
+} // namespace zen
+#endif
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
diff --git a/src/zencore/xmake.lua b/src/zencore/xmake.lua
index 13611a2e9..b3a33e052 100644
--- a/src/zencore/xmake.lua
+++ b/src/zencore/xmake.lua
@@ -64,6 +64,27 @@ target('zencore')
{public=true}
)
+ if has_config("zensentry") then
+ add_packages("vcpkg::sentry-native")
+
+ if is_plat("windows") then
+ add_links("dbghelp", "winhttp", "version") -- for Sentry
+ end
+
+ if is_plat("linux") then
+ -- As sentry_native uses symbols from breakpad_client, the latter must
+ -- be specified after the former with GCC-like toolchains. xmake however
+ -- is unaware of this and simply globs files from vcpkg's output. The
+ -- line below forces breakpad_client to be to the right of sentry_native
+ add_syslinks("breakpad_client")
+ end
+
+ if is_plat("macosx") then
+ add_syslinks("bsm")
+ end
+
+ end
+
if is_plat("linux") then
add_syslinks("rt")
end