// Copyright Epic Games, Inc. All Rights Reserved. #include #include #include #include #include #include #include #if ZEN_PLATFORM_LINUX # include #endif #if ZEN_PLATFORM_MAC # include #endif ZEN_THIRD_PARTY_INCLUDES_START #include ZEN_THIRD_PARTY_INCLUDES_END #if ZEN_USE_SENTRY # define SENTRY_BUILD_STATIC 1 ZEN_THIRD_PARTY_INCLUDES_START # include # include ZEN_THIRD_PARTY_INCLUDES_END namespace sentry { namespace { static const std::string DefaultDsn("https://8ba3441bebc941c1ae24b8cd2fd25d55@o10593.ingest.us.sentry.io/5919284"); } struct SentryAssertImpl : zen::AssertImpl { virtual void ZEN_FORCENOINLINE ZEN_DEBUG_SECTION OnAssert(const char* Filename, int LineNumber, const char* FunctionName, const char* Msg, const zen::CallstackFrames* Callstack) override; }; class sentry_sink final : public spdlog::sinks::base_sink { 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(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, const 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 ZEN_DEFINE_LOG_CATEGORY_STATIC(LogSentry, "sentry-sdk"); 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(); } // SentryLogFunction can be called before the logging system is initialized // (during sentry_init which runs before InitializeLogging). Fall back to // console logging when the category logger is not yet available. // // Since we want to default to WARN level but this runs before logging has // been configured, we ignore the callbacks for DEBUG/INFO explicitly here // which means users don't see every possible log message if they're trying // to configure the levels using --log-debug=sentry-sdk if (!TheDefaultLogger) { 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; } return; } switch (Level) { case SENTRY_LEVEL_DEBUG: ZEN_LOG_DEBUG(LogSentry, "sentry: {}", MessagePtr); break; case SENTRY_LEVEL_INFO: ZEN_LOG_INFO(LogSentry, "sentry: {}", MessagePtr); break; case SENTRY_LEVEL_WARNING: ZEN_LOG_WARN(LogSentry, "sentry: {}", MessagePtr); break; case SENTRY_LEVEL_ERROR: ZEN_LOG_ERROR(LogSentry, "sentry: {}", MessagePtr); break; case SENTRY_LEVEL_FATAL: ZEN_LOG_CRITICAL(LogSentry, "sentry: {}", MessagePtr); break; } } # endif SentryIntegration::SentryIntegration() { } SentryIntegration::~SentryIntegration() { if (m_IsInitialized && m_SentryErrorCode == 0) { logging::SetErrorLog(""); m_SentryAssert.reset(); sentry_close(); } } void SentryIntegration::Initialize(const Config& Conf, const std::string& CommandLine) { m_AllowPII = Conf.AllowPII; std::string SentryDatabasePath = Conf.DatabasePath; if (SentryDatabasePath.starts_with("\\\\?\\")) { SentryDatabasePath = SentryDatabasePath.substr(4); } sentry_options_t* SentryOptions = sentry_options_new(); sentry_options_set_dsn(SentryOptions, Conf.Dsn.empty() ? sentry::DefaultDsn.c_str() : Conf.Dsn.c_str()); sentry_options_set_database_path(SentryOptions, SentryDatabasePath.c_str()); sentry_options_set_logger(SentryOptions, SentryLogFunction, this); sentry_options_set_environment(SentryOptions, Conf.Environment.empty() ? "production" : Conf.Environment.c_str()); std::string SentryAttachmentsPath = Conf.AttachmentsPath; 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); if (Conf.Debug) { 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"); logging::SetErrorLog("sentry"); m_SentryAssert = std::make_unique(); } m_IsInitialized = true; } void SentryIntegration::LogStartupInformation() { // Initialize the sentry-sdk log category at Warn level to reduce startup noise. // The level can be overridden via --log-debug=sentry-sdk or --log-info=sentry-sdk LogSentry.Logger().SetLogLevel(logging::level::Warn); if (m_IsInitialized) { if (m_SentryErrorCode == 0) { if (m_AllowPII) { ZEN_LOG_INFO(LogSentry, "sentry initialized, username: '{}', hostname: '{}', id: '{}'", m_SentryUserName, m_SentryHostName, m_SentryId); } else { ZEN_LOG_INFO(LogSentry, "sentry initialized with anonymous reports"); } } else { ZEN_LOG_WARN( LogSentry, "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