From 4d090bcec2d78f65f44536964cc54f2aa460701d Mon Sep 17 00:00:00 2001 From: Yuriy Levchenko Date: Fri, 29 Aug 2025 15:44:30 +0300 Subject: [PATCH 1/4] Add Sentry logger for Win32 plugin --- .../AppleSentryPlugin/AppleSentryLogger.h | 25 ++++++ .../AppleSentryPlugin/AppleSentryLogger.mm | 88 +++++++++++++++++++ .../AppleSentryPlugin/AppleSentryService.h | 6 +- .../AppleSentryPlugin/AppleSentryService.mm | 32 ++++++- src/Plugins/AppleSentryPlugin/CMakeLists.txt | 4 +- src/Plugins/Win32SentryPlugin/CMakeLists.txt | 6 +- .../Win32SentryPlugin/Win32SentryLogger.cpp | 86 ++++++++++++++++++ .../Win32SentryPlugin/Win32SentryLogger.h | 25 ++++++ .../Win32SentryPlugin/Win32SentryService.cpp | 28 ++++++ .../Win32SentryPlugin/Win32SentryService.h | 1 + 10 files changed, 295 insertions(+), 6 deletions(-) create mode 100644 src/Plugins/AppleSentryPlugin/AppleSentryLogger.h create mode 100644 src/Plugins/AppleSentryPlugin/AppleSentryLogger.mm create mode 100644 src/Plugins/Win32SentryPlugin/Win32SentryLogger.cpp create mode 100644 src/Plugins/Win32SentryPlugin/Win32SentryLogger.h diff --git a/src/Plugins/AppleSentryPlugin/AppleSentryLogger.h b/src/Plugins/AppleSentryPlugin/AppleSentryLogger.h new file mode 100644 index 0000000000..3af17aad2f --- /dev/null +++ b/src/Plugins/AppleSentryPlugin/AppleSentryLogger.h @@ -0,0 +1,25 @@ +#pragma once + +#include "Kernel/LoggerBase.h" + +namespace Mengine +{ + ////////////////////////////////////////////////////////////////////////// + class AppleSentryLogger + : public LoggerBase + { + public: + AppleSentryLogger(); + ~AppleSentryLogger() override; + + protected: + bool _initializeLogger() override; + void _finalizeLogger() override; + + protected: + void _log( const LoggerRecordInterfacePtr & _record ) override; + }; + ////////////////////////////////////////////////////////////////////////// + typedef IntrusivePtr AppleSentryLoggerPtr; + ////////////////////////////////////////////////////////////////////////// +} diff --git a/src/Plugins/AppleSentryPlugin/AppleSentryLogger.mm b/src/Plugins/AppleSentryPlugin/AppleSentryLogger.mm new file mode 100644 index 0000000000..fd5fb74402 --- /dev/null +++ b/src/Plugins/AppleSentryPlugin/AppleSentryLogger.mm @@ -0,0 +1,88 @@ +#include "AppleSentryLogger.h" + +#include "Interface/LoggerRecordInterface.h" +#include "Kernel/LoggerMessage.h" + +#import + +namespace Mengine +{ + ////////////////////////////////////////////////////////////////////////// + AppleSentryLogger::AppleSentryLogger() + { + } + ////////////////////////////////////////////////////////////////////////// + AppleSentryLogger::~AppleSentryLogger() + { + } + ////////////////////////////////////////////////////////////////////////// + bool AppleSentryLogger::_initializeLogger() + { + return true; + } + ////////////////////////////////////////////////////////////////////////// + void AppleSentryLogger::_finalizeLogger() + { + //Empty + } + ////////////////////////////////////////////////////////////////////////// + static SentryLevel s_getSentryLevel( ELoggerLevel _level ) + { + switch( _level ) + { + case LM_FATAL: + return kSentryLevelFatal; + case LM_ERROR: + return kSentryLevelError; + case LM_WARNING: + return kSentryLevelWarning; + case LM_INFO: + case LM_MESSAGE: + case LM_MESSAGE_RELEASE: + return kSentryLevelInfo; + case LM_DEBUG: + case LM_VERBOSE: + return kSentryLevelDebug; + default: + break; + } + + return kSentryLevelInfo; + } + ////////////////////////////////////////////////////////////////////////// + void AppleSentryLogger::_log( const LoggerRecordInterfacePtr & _record ) + { + LoggerMessage message; + _record->getMessage( &message ); + + NSString * nsMessage = [NSString stringWithUTF8String:message.data]; + NSString * nsCategory = [NSString stringWithUTF8String:message.category]; + NSString * nsThread = [NSString stringWithUTF8String:message.thread.c_str()]; + NSString * nsFile = message.file != nullptr ? [NSString stringWithUTF8String:message.file] : nil; + NSString * nsFunction = message.function != nullptr ? [NSString stringWithUTF8String:message.function] : nil; + + SentryLevel level = s_getSentryLevel( message.level ); + + [SentrySDK captureMessage:nsMessage withScope:^(SentryScope * _Nonnull scope) { + scope.level = level; + [scope setExtraValue:nsCategory forKey:@"log.category"]; + [scope setExtraValue:nsThread forKey:@"log.thread"]; + + if( nsFile != nil ) + { + [scope setExtraValue:nsFile forKey:@"log.file"]; + } + + if( nsFunction != nil ) + { + [scope setExtraValue:nsFunction forKey:@"log.function"]; + } + + if( message.line != 0 ) + { + [scope setExtraValue:@(message.line) forKey:@"log.line"]; + } + }]; + } + ////////////////////////////////////////////////////////////////////////// +} diff --git a/src/Plugins/AppleSentryPlugin/AppleSentryService.h b/src/Plugins/AppleSentryPlugin/AppleSentryService.h index 94004b31e1..3a722da128 100644 --- a/src/Plugins/AppleSentryPlugin/AppleSentryService.h +++ b/src/Plugins/AppleSentryPlugin/AppleSentryService.h @@ -5,6 +5,7 @@ #include "Kernel/ServiceBase.h" #include "Kernel/AssertionLevel.h" #include "Kernel/ErrorLevel.h" +#include "Interface/LoggerInterface.h" namespace Mengine { @@ -23,6 +24,9 @@ namespace Mengine void notifyCreateApplication_(); void notifyAssertion_( const Char * _category, EAssertionLevel _level, const Char * _test, const Char * _file, int32_t _line, const Char * _message ); void notifyError_( const Char * _category, EErrorLevel _level, const Char * _file, int32_t _line, const Char * _message ); - void notifyEngineStop_(); + void notifyEngineStop_(); + + protected: + LoggerInterfacePtr m_logger; }; } diff --git a/src/Plugins/AppleSentryPlugin/AppleSentryService.mm b/src/Plugins/AppleSentryPlugin/AppleSentryService.mm index 464f51b443..f2372bb725 100644 --- a/src/Plugins/AppleSentryPlugin/AppleSentryService.mm +++ b/src/Plugins/AppleSentryPlugin/AppleSentryService.mm @@ -19,6 +19,8 @@ #include "Kernel/NotificationHelper.h" #include "Kernel/ConfigHelper.h" #include "Kernel/TimestampHelper.h" +#include "Kernel/FactorableUnique.h" +#include "AppleSentryLogger.h" #include "Config/StdString.h" #include "Config/StdIO.h" @@ -45,7 +47,25 @@ NOTIFICATION_ADDOBSERVERMETHOD_THIS( NOTIFICATOR_ASSERTION, &AppleSentryService::notifyAssertion_, MENGINE_DOCUMENT_FACTORABLE ); NOTIFICATION_ADDOBSERVERMETHOD_THIS( NOTIFICATOR_ERROR, &AppleSentryService::notifyError_, MENGINE_DOCUMENT_FACTORABLE ); NOTIFICATION_ADDOBSERVERMETHOD_THIS( NOTIFICATOR_ENGINE_STOP, &AppleSentryService::notifyEngineStop_, MENGINE_DOCUMENT_FACTORABLE ); - + + AppleSentryLoggerPtr loggerSentry = Helper::makeFactorableUnique( MENGINE_DOCUMENT_FACTORABLE ); + + bool debugMode = Helper::isDebugMode(); + + if( debugMode == false ) + { + loggerSentry->setVerboseLevel( LM_WARNING ); + } + + uint32_t loggerFilter = MAKE_LOGGER_FILTER( LFILTER_PROTECTED ); + loggerSentry->setVerboseFilter( loggerFilter ); + + if( LOGGER_SERVICE() + ->registerLogger( loggerSentry ) == true ) + { + m_logger = loggerSentry; + } + return true; } ////////////////////////////////////////////////////////////////////////// @@ -55,7 +75,15 @@ NOTIFICATION_REMOVEOBSERVER_THIS( NOTIFICATOR_ASSERTION ); NOTIFICATION_REMOVEOBSERVER_THIS( NOTIFICATOR_ERROR ); NOTIFICATION_REMOVEOBSERVER_THIS( NOTIFICATOR_ENGINE_STOP ); - + + if( m_logger != nullptr ) + { + LOGGER_SERVICE() + ->unregisterLogger( m_logger ); + + m_logger = nullptr; + } + [SentrySDK close]; } ////////////////////////////////////////////////////////////////////////// diff --git a/src/Plugins/AppleSentryPlugin/CMakeLists.txt b/src/Plugins/AppleSentryPlugin/CMakeLists.txt index 1afe5a627e..7dee647f98 100644 --- a/src/Plugins/AppleSentryPlugin/CMakeLists.txt +++ b/src/Plugins/AppleSentryPlugin/CMakeLists.txt @@ -6,9 +6,11 @@ src AppleSentryPlugin.h AppleSentryPlugin.mm - + AppleSentryService.h AppleSentryService.mm + AppleSentryLogger.h + AppleSentryLogger.mm ) if(MENGINE_TARGET_IOS) diff --git a/src/Plugins/Win32SentryPlugin/CMakeLists.txt b/src/Plugins/Win32SentryPlugin/CMakeLists.txt index 0c8700ca60..899ee65683 100644 --- a/src/Plugins/Win32SentryPlugin/CMakeLists.txt +++ b/src/Plugins/Win32SentryPlugin/CMakeLists.txt @@ -7,9 +7,11 @@ src Win32SentryPlugin.h Win32SentryPlugin.def Win32SentryPlugin.cpp - + Win32SentryService.h Win32SentryService.cpp + Win32SentryLogger.h + Win32SentryLogger.cpp ) add_definitions(-DSENTRY_BUILD_STATIC) @@ -35,4 +37,4 @@ TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${THIRDPARTY_LIB_DIR}/${MENGINE_LIB_PREFIX TARGET_LINK_LIBRARIES(${PROJECT_NAME} Winhttp.lib) TARGET_LINK_LIBRARIES(${PROJECT_NAME} Rpcrt4.lib) TARGET_LINK_LIBRARIES(${PROJECT_NAME} Dbghelp.lib) -TARGET_LINK_LIBRARIES(${PROJECT_NAME} Version.lib) \ No newline at end of file +TARGET_LINK_LIBRARIES(${PROJECT_NAME} Version.lib) diff --git a/src/Plugins/Win32SentryPlugin/Win32SentryLogger.cpp b/src/Plugins/Win32SentryPlugin/Win32SentryLogger.cpp new file mode 100644 index 0000000000..b85fbd3394 --- /dev/null +++ b/src/Plugins/Win32SentryPlugin/Win32SentryLogger.cpp @@ -0,0 +1,86 @@ +#include "Win32SentryLogger.h" + +#include "Interface/LoggerRecordInterface.h" +#include "Kernel/LoggerMessage.h" + +#include "sentry.h" + +namespace Mengine +{ + ////////////////////////////////////////////////////////////////////////// + Win32SentryLogger::Win32SentryLogger() + { + } + ////////////////////////////////////////////////////////////////////////// + Win32SentryLogger::~Win32SentryLogger() + { + } + ////////////////////////////////////////////////////////////////////////// + bool Win32SentryLogger::_initializeLogger() + { + return true; + } + ////////////////////////////////////////////////////////////////////////// + void Win32SentryLogger::_finalizeLogger() + { + //Empty + } + ////////////////////////////////////////////////////////////////////////// + static sentry_level_t s_getSentryLevel( ELoggerLevel _level ) + { + switch( _level ) + { + case LM_FATAL: + return SENTRY_LEVEL_FATAL; + case LM_ERROR: + return SENTRY_LEVEL_ERROR; + case LM_WARNING: + return SENTRY_LEVEL_WARNING; + case LM_INFO: + case LM_MESSAGE: + case LM_MESSAGE_RELEASE: + return SENTRY_LEVEL_INFO; + case LM_DEBUG: + case LM_VERBOSE: + return SENTRY_LEVEL_DEBUG; + default: + break; + } + + return SENTRY_LEVEL_INFO; + } + ////////////////////////////////////////////////////////////////////////// + void Win32SentryLogger::_log( const LoggerRecordInterfacePtr & _record ) + { + LoggerMessage message; + _record->getMessage( &message ); + + sentry_level_t level = s_getSentryLevel( message.level ); + + sentry_value_t event = sentry_value_new_message_event( level, "log", message.data ); + + sentry_value_t extra = sentry_value_new_object(); + sentry_value_set_by_key( extra, "log.category", sentry_value_new_string( message.category ) ); + sentry_value_set_by_key( extra, "log.thread", sentry_value_new_string( message.thread.c_str() ) ); + + if( message.file != nullptr ) + { + sentry_value_set_by_key( extra, "log.file", sentry_value_new_string( message.file ) ); + } + + if( message.function != nullptr ) + { + sentry_value_set_by_key( extra, "log.function", sentry_value_new_string( message.function ) ); + } + + if( message.line != 0 ) + { + sentry_value_set_by_key( extra, "log.line", sentry_value_new_int32( message.line ) ); + } + + sentry_value_set_by_key( event, "extra", extra ); + + sentry_capture_event( event ); + } + ////////////////////////////////////////////////////////////////////////// +} diff --git a/src/Plugins/Win32SentryPlugin/Win32SentryLogger.h b/src/Plugins/Win32SentryPlugin/Win32SentryLogger.h new file mode 100644 index 0000000000..9bf2761962 --- /dev/null +++ b/src/Plugins/Win32SentryPlugin/Win32SentryLogger.h @@ -0,0 +1,25 @@ +#pragma once + +#include "Kernel/LoggerBase.h" + +namespace Mengine +{ + ////////////////////////////////////////////////////////////////////////// + class Win32SentryLogger + : public LoggerBase + { + public: + Win32SentryLogger(); + ~Win32SentryLogger() override; + + protected: + bool _initializeLogger() override; + void _finalizeLogger() override; + + protected: + void _log( const LoggerRecordInterfacePtr & _record ) override; + }; + ////////////////////////////////////////////////////////////////////////// + typedef IntrusivePtr Win32SentryLoggerPtr; + ////////////////////////////////////////////////////////////////////////// +} diff --git a/src/Plugins/Win32SentryPlugin/Win32SentryService.cpp b/src/Plugins/Win32SentryPlugin/Win32SentryService.cpp index 6c562cd197..e35e27750c 100644 --- a/src/Plugins/Win32SentryPlugin/Win32SentryService.cpp +++ b/src/Plugins/Win32SentryPlugin/Win32SentryService.cpp @@ -25,6 +25,8 @@ #include "Kernel/FileLogger.h" #include "Kernel/FilePathHelper.h" #include "Kernel/ContentHelper.h" +#include "Kernel/FactorableUnique.h" +#include "Win32SentryLogger.h" #include "Config/StdString.h" #include "Config/StdIO.h" @@ -313,6 +315,24 @@ namespace Mengine NOTIFICATION_ADDOBSERVERMETHOD_THIS( NOTIFICATOR_ASSERTION, &Win32SentryService::notifyAssertion_, MENGINE_DOCUMENT_FACTORABLE ); NOTIFICATION_ADDOBSERVERMETHOD_THIS( NOTIFICATOR_ERROR, &Win32SentryService::notifyError_, MENGINE_DOCUMENT_FACTORABLE ); + Win32SentryLoggerPtr loggerSentry = Helper::makeFactorableUnique( MENGINE_DOCUMENT_FACTORABLE ); + + bool debugMode = Helper::isDebugMode(); + + if( debugMode == false ) + { + loggerSentry->setVerboseLevel( LM_WARNING ); + } + + uint32_t loggerFilter = MAKE_LOGGER_FILTER( LFILTER_PROTECTED ); + loggerSentry->setVerboseFilter( loggerFilter ); + + if( LOGGER_SERVICE() + ->registerLogger( loggerSentry ) == true ) + { + m_logger = loggerSentry; + } + return true; } ////////////////////////////////////////////////////////////////////////// @@ -326,6 +346,14 @@ namespace Mengine } #endif + if( m_logger != nullptr ) + { + LOGGER_SERVICE() + ->unregisterLogger( m_logger ); + + m_logger = nullptr; + } + if( m_sentryLogger != nullptr ) { LOGGER_SERVICE() diff --git a/src/Plugins/Win32SentryPlugin/Win32SentryService.h b/src/Plugins/Win32SentryPlugin/Win32SentryService.h index 3bdcfcc980..3a01a21fdd 100644 --- a/src/Plugins/Win32SentryPlugin/Win32SentryService.h +++ b/src/Plugins/Win32SentryPlugin/Win32SentryService.h @@ -28,5 +28,6 @@ namespace Mengine protected: LoggerInterfacePtr m_sentryLogger; + LoggerInterfacePtr m_logger; }; } \ No newline at end of file From 1c943d42ba92634aa896f6964a48c6756f5f7c51 Mon Sep 17 00:00:00 2001 From: Yuriy Levchenko Date: Sat, 30 Aug 2025 13:35:06 +0300 Subject: [PATCH 2/4] Add Android Sentry logger --- .../Plugin/Sentry/MengineSentryPlugin.java | 50 ++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/gradle/plugins/Sentry/src/main/java/org/Mengine/Plugin/Sentry/MengineSentryPlugin.java b/gradle/plugins/Sentry/src/main/java/org/Mengine/Plugin/Sentry/MengineSentryPlugin.java index 682ad5f17a..66a3f4c9ad 100644 --- a/gradle/plugins/Sentry/src/main/java/org/Mengine/Plugin/Sentry/MengineSentryPlugin.java +++ b/gradle/plugins/Sentry/src/main/java/org/Mengine/Plugin/Sentry/MengineSentryPlugin.java @@ -11,6 +11,9 @@ import org.Mengine.Base.MengineListenerEngine; import org.Mengine.Base.MengineListenerTransparencyConsent; import org.Mengine.Base.MengineListenerUser; +import org.Mengine.Base.MengineListenerLogger; +import org.Mengine.Base.MengineLog; +import org.Mengine.Base.MengineParamLoggerMessage; import org.Mengine.Base.MengineService; import org.Mengine.Base.MengineServiceInvalidInitializeException; import org.Mengine.Base.MengineParamTransparencyConsent; @@ -21,8 +24,12 @@ import io.sentry.Sentry; import io.sentry.android.core.SentryAndroid; import io.sentry.protocol.User; +import io.sentry.SentryLogLevel; +import io.sentry.SentryLogParameters; +import io.sentry.SentryAttribute; +import io.sentry.SentryAttributes; -public class MengineSentryPlugin extends MengineService implements MengineListenerApplication, MengineListenerEngine, MengineListenerTransparencyConsent, MengineListenerUser { +public class MengineSentryPlugin extends MengineService implements MengineListenerApplication, MengineListenerEngine, MengineListenerTransparencyConsent, MengineListenerUser, MengineListenerLogger { public static final String SERVICE_NAME = "Sentry"; public static final boolean SERVICE_EMBEDDING = true; @@ -152,6 +159,27 @@ public void onMengineRemoveUserData(@NonNull MengineApplication application) { Sentry.setUser(null); } + @Override + public void onMengineLog(@NonNull MengineApplication application, @NonNull MengineParamLoggerMessage message) { + if (BuildConfig.DEBUG == false) { + if (message.MESSAGE_LEVEL != MengineLog.LM_WARNING && message.MESSAGE_LEVEL != MengineLog.LM_ERROR && message.MESSAGE_LEVEL != MengineLog.LM_FATAL) { + return; + } + } + + SentryLogLevel level = MengineSentryPlugin.getSentryLogLevel(message); + + SentryAttributes attributes = SentryAttributes.of( + SentryAttribute.stringAttribute("log.category", message.MESSAGE_CATEGORY.toString()), + SentryAttribute.stringAttribute("log.thread", message.MESSAGE_THREAD), + SentryAttribute.stringAttribute("log.file", message.MESSAGE_FILE), + SentryAttribute.integerAttribute("log.line", message.MESSAGE_LINE), + SentryAttribute.stringAttribute("log.function", message.MESSAGE_FUNCTION) + ); + + Sentry.logger().log(level, SentryLogParameters.create(attributes), message.MESSAGE_DATA); + } + @Override public void onMengineCaughtException(@NonNull MengineApplication application, Throwable throwable) { this.recordException(throwable); @@ -162,6 +190,26 @@ public void onAppState(@NonNull MengineApplication application, String name, Obj this.setCustomKey("." + name, value); } + public static SentryLogLevel getSentryLogLevel(MengineParamLoggerMessage message) { + switch (message.MESSAGE_LEVEL) { + case MengineLog.LM_FATAL: + return SentryLogLevel.FATAL; + case MengineLog.LM_ERROR: + return SentryLogLevel.ERROR; + case MengineLog.LM_WARNING: + return SentryLogLevel.WARNING; + case MengineLog.LM_INFO: + case MengineLog.LM_MESSAGE: + case MengineLog.LM_MESSAGE_RELEASE: + return SentryLogLevel.INFO; + case MengineLog.LM_DEBUG: + case MengineLog.LM_VERBOSE: + return SentryLogLevel.DEBUG; + default: + return SentryLogLevel.INFO; + } + } + public void setCustomKey(String key, Object value) { if (value == null) { Sentry.setExtra(key, "null"); From 49a3adf1bb4c44e5d3fdcd7b4ed506834b3856ce Mon Sep 17 00:00:00 2001 From: Yuriy Levchenko Date: Sat, 30 Aug 2025 14:39:03 +0300 Subject: [PATCH 3/4] Refine Sentry loggers --- .../Plugin/Sentry/MengineSentryPlugin.java | 157 +++++++++++------- .../AppleSentryPlugin/AppleSentryLogger.mm | 24 ++- .../Win32SentryPlugin/Win32SentryLogger.cpp | 14 +- 3 files changed, 118 insertions(+), 77 deletions(-) diff --git a/gradle/plugins/Sentry/src/main/java/org/Mengine/Plugin/Sentry/MengineSentryPlugin.java b/gradle/plugins/Sentry/src/main/java/org/Mengine/Plugin/Sentry/MengineSentryPlugin.java index 66a3f4c9ad..a3e74d4d57 100644 --- a/gradle/plugins/Sentry/src/main/java/org/Mengine/Plugin/Sentry/MengineSentryPlugin.java +++ b/gradle/plugins/Sentry/src/main/java/org/Mengine/Plugin/Sentry/MengineSentryPlugin.java @@ -13,12 +13,15 @@ import org.Mengine.Base.MengineListenerUser; import org.Mengine.Base.MengineListenerLogger; import org.Mengine.Base.MengineLog; +import org.Mengine.Base.MengineParamLoggerException; import org.Mengine.Base.MengineParamLoggerMessage; import org.Mengine.Base.MengineService; import org.Mengine.Base.MengineServiceInvalidInitializeException; import org.Mengine.Base.MengineParamTransparencyConsent; import org.Mengine.Base.MengineUtils; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import io.sentry.Sentry; @@ -38,6 +41,52 @@ public class MengineSentryPlugin extends MengineService implements MengineListen public static boolean m_passMeasurementGDPR = false; + private static SentryLogLevel getSentryLogLevel(@NonNull MengineParamLoggerMessage message) { + switch (message.MESSAGE_LEVEL) { + case MengineLog.LM_FATAL: + return SentryLogLevel.FATAL; + case MengineLog.LM_ERROR: + return SentryLogLevel.ERROR; + case MengineLog.LM_WARNING: + return SentryLogLevel.WARNING; + case MengineLog.LM_INFO: + case MengineLog.LM_MESSAGE: + case MengineLog.LM_MESSAGE_RELEASE: + return SentryLogLevel.INFO; + case MengineLog.LM_DEBUG: + case MengineLog.LM_VERBOSE: + return SentryLogLevel.DEBUG; + default: + return SentryLogLevel.INFO; + } + } + + private void setCustomKey(String key, Object value) { + if (value == null) { + Sentry.setExtra(key, "null"); + } else { + Sentry.setExtra(key, String.valueOf(value)); + } + } + + private void recordException(Throwable throwable) { + this.logInfo("recordException throwable: %s", + throwable.getMessage() + ); + + throwable.printStackTrace(System.err); + + Sentry.captureException(throwable); + } + + public void testCrash() { + this.logMessage("testCrash"); + + this.setCustomKey("test.crash", true); + + throw new RuntimeException("Sentry Test Crash"); + } + @Override public void onAppInit(MengineApplication application, boolean isMainProcess) throws MengineServiceInvalidInitializeException { if (isMainProcess == false) { @@ -46,24 +95,24 @@ public void onAppInit(MengineApplication application, boolean isMainProcess) thr String MengineSentryPlugin_DSN = this.getResourceString(METADATA_DSN); - this.logInfo("%s: %s" - , METADATA_DSN - , MengineUtils.getRedactedValue(MengineSentryPlugin_DSN) + this.logInfo("%s: %s", + METADATA_DSN, + MengineUtils.getRedactedValue(MengineSentryPlugin_DSN) ); boolean MengineSentryPlugin_EnableUncaughtExceptionHandler = this.getResourceBoolean(METADATA_ENABLE_UNCAUGHT_EXCEPTION_HANDLER); - this.logInfo("%s: %b" - , METADATA_ENABLE_UNCAUGHT_EXCEPTION_HANDLER - , MengineSentryPlugin_EnableUncaughtExceptionHandler + this.logInfo("%s: %b", + METADATA_ENABLE_UNCAUGHT_EXCEPTION_HANDLER, + MengineSentryPlugin_EnableUncaughtExceptionHandler ); MengineParamTransparencyConsent tcParam = application.makeTransparencyConsentParam(); m_passMeasurementGDPR = tcParam.getConsentMeasurement(); - this.logInfo("GDPR measurement: %s" - , m_passMeasurementGDPR + this.logInfo("GDPR measurement: %s", + m_passMeasurementGDPR ); tcParam.getConsentAnalyticsStorage(); @@ -149,8 +198,8 @@ public void onMengineChangeUserId(@NonNull MengineApplication application, Strin public void onMengineTransparencyConsent(@NonNull MengineApplication application, @NonNull MengineParamTransparencyConsent consent) { m_passMeasurementGDPR = consent.getConsentMeasurement(); - this.logInfo("GDPR measurement: %s" - , m_passMeasurementGDPR + this.logInfo("GDPR measurement: %s", + m_passMeasurementGDPR ); } @@ -169,17 +218,44 @@ public void onMengineLog(@NonNull MengineApplication application, @NonNull Mengi SentryLogLevel level = MengineSentryPlugin.getSentryLogLevel(message); - SentryAttributes attributes = SentryAttributes.of( - SentryAttribute.stringAttribute("log.category", message.MESSAGE_CATEGORY.toString()), - SentryAttribute.stringAttribute("log.thread", message.MESSAGE_THREAD), - SentryAttribute.stringAttribute("log.file", message.MESSAGE_FILE), - SentryAttribute.integerAttribute("log.line", message.MESSAGE_LINE), - SentryAttribute.stringAttribute("log.function", message.MESSAGE_FUNCTION) - ); + List attributesList = new ArrayList<>(); + attributesList.add(SentryAttribute.stringAttribute("log.category", message.MESSAGE_CATEGORY.toString())); + attributesList.add(SentryAttribute.stringAttribute("log.thread", message.MESSAGE_THREAD)); + + if (message.MESSAGE_FILE != null && message.MESSAGE_FILE.isEmpty() == false) { + attributesList.add(SentryAttribute.stringAttribute("log.file", message.MESSAGE_FILE)); + attributesList.add(SentryAttribute.integerAttribute("log.line", message.MESSAGE_LINE)); + } + + if (message.MESSAGE_FUNCTION != null && message.MESSAGE_FUNCTION.isEmpty() == false) { + attributesList.add(SentryAttribute.stringAttribute("log.function", message.MESSAGE_FUNCTION)); + } + + SentryAttributes attributes = SentryAttributes.of(attributesList.toArray(new SentryAttribute[0])); Sentry.logger().log(level, SentryLogParameters.create(attributes), message.MESSAGE_DATA); } + @Override + public void onMengineException(@NonNull MengineApplication application, @NonNull MengineParamLoggerException exception) { + Sentry.withScope(scope -> { + scope.setExtra("log.category", exception.EXCEPTION_CATEGORY.toString()); + + for (Map.Entry entry : exception.EXCEPTION_ATTRIBUTES.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + + if (value == null) { + scope.setExtra(key, "null"); + } else { + scope.setExtra(key, String.valueOf(value)); + } + } + + this.recordException(exception.EXCEPTION_THROWABLE); + }); + } + @Override public void onMengineCaughtException(@NonNull MengineApplication application, Throwable throwable) { this.recordException(throwable); @@ -189,50 +265,5 @@ public void onMengineCaughtException(@NonNull MengineApplication application, Th public void onAppState(@NonNull MengineApplication application, String name, Object value) { this.setCustomKey("." + name, value); } - - public static SentryLogLevel getSentryLogLevel(MengineParamLoggerMessage message) { - switch (message.MESSAGE_LEVEL) { - case MengineLog.LM_FATAL: - return SentryLogLevel.FATAL; - case MengineLog.LM_ERROR: - return SentryLogLevel.ERROR; - case MengineLog.LM_WARNING: - return SentryLogLevel.WARNING; - case MengineLog.LM_INFO: - case MengineLog.LM_MESSAGE: - case MengineLog.LM_MESSAGE_RELEASE: - return SentryLogLevel.INFO; - case MengineLog.LM_DEBUG: - case MengineLog.LM_VERBOSE: - return SentryLogLevel.DEBUG; - default: - return SentryLogLevel.INFO; - } - } - - public void setCustomKey(String key, Object value) { - if (value == null) { - Sentry.setExtra(key, "null"); - } else { - Sentry.setExtra(key, String.valueOf(value)); - } - } - - public void recordException(Throwable throwable) { - this.logInfo("recordException throwable: %s" - , throwable.getMessage() - ); - - throwable.printStackTrace(System.err); - - Sentry.captureException(throwable); - } - - public void testCrash() { - this.logMessage("testCrash"); - - this.setCustomKey("test.crash", true); - - throw new RuntimeException("Sentry Test Crash"); - } } + diff --git a/src/Plugins/AppleSentryPlugin/AppleSentryLogger.mm b/src/Plugins/AppleSentryPlugin/AppleSentryLogger.mm index fd5fb74402..50cce41d18 100644 --- a/src/Plugins/AppleSentryPlugin/AppleSentryLogger.mm +++ b/src/Plugins/AppleSentryPlugin/AppleSentryLogger.mm @@ -58,8 +58,18 @@ static SentryLevel s_getSentryLevel( ELoggerLevel _level ) NSString * nsMessage = [NSString stringWithUTF8String:message.data]; NSString * nsCategory = [NSString stringWithUTF8String:message.category]; NSString * nsThread = [NSString stringWithUTF8String:message.thread.c_str()]; - NSString * nsFile = message.file != nullptr ? [NSString stringWithUTF8String:message.file] : nil; - NSString * nsFunction = message.function != nullptr ? [NSString stringWithUTF8String:message.function] : nil; + + NSString * nsFile = nil; + if( message.file != nullptr && message.file[0] != '\0' ) + { + nsFile = [NSString stringWithUTF8String:message.file]; + } + + NSString * nsFunction = nil; + if( message.function != nullptr && message.function[0] != '\0' ) + { + nsFunction = [NSString stringWithUTF8String:message.function]; + } SentryLevel level = s_getSentryLevel( message.level ); @@ -71,17 +81,17 @@ static SentryLevel s_getSentryLevel( ELoggerLevel _level ) if( nsFile != nil ) { [scope setExtraValue:nsFile forKey:@"log.file"]; + + if( message.line != 0 ) + { + [scope setExtraValue:@(message.line) forKey:@"log.line"]; + } } if( nsFunction != nil ) { [scope setExtraValue:nsFunction forKey:@"log.function"]; } - - if( message.line != 0 ) - { - [scope setExtraValue:@(message.line) forKey:@"log.line"]; - } }]; } ////////////////////////////////////////////////////////////////////////// diff --git a/src/Plugins/Win32SentryPlugin/Win32SentryLogger.cpp b/src/Plugins/Win32SentryPlugin/Win32SentryLogger.cpp index b85fbd3394..c6f304d2b2 100644 --- a/src/Plugins/Win32SentryPlugin/Win32SentryLogger.cpp +++ b/src/Plugins/Win32SentryPlugin/Win32SentryLogger.cpp @@ -63,19 +63,19 @@ namespace Mengine sentry_value_set_by_key( extra, "log.category", sentry_value_new_string( message.category ) ); sentry_value_set_by_key( extra, "log.thread", sentry_value_new_string( message.thread.c_str() ) ); - if( message.file != nullptr ) + if( message.file != nullptr && message.file[0] != '\0' ) { sentry_value_set_by_key( extra, "log.file", sentry_value_new_string( message.file ) ); - } - if( message.function != nullptr ) - { - sentry_value_set_by_key( extra, "log.function", sentry_value_new_string( message.function ) ); + if( message.line != 0 ) + { + sentry_value_set_by_key( extra, "log.line", sentry_value_new_int32( message.line ) ); + } } - if( message.line != 0 ) + if( message.function != nullptr && message.function[0] != '\0' ) { - sentry_value_set_by_key( extra, "log.line", sentry_value_new_int32( message.line ) ); + sentry_value_set_by_key( extra, "log.function", sentry_value_new_string( message.function ) ); } sentry_value_set_by_key( event, "extra", extra ); From c461aeb15f73f2544dbccf5f31cb69dfc7deaea5 Mon Sep 17 00:00:00 2001 From: Yuriy Levchenko Date: Sat, 30 Aug 2025 15:50:47 +0300 Subject: [PATCH 4/4] Align Android Sentry plugin with provided implementation --- .../Plugin/Sentry/MengineSentryPlugin.java | 155 +++++++++--------- 1 file changed, 74 insertions(+), 81 deletions(-) diff --git a/gradle/plugins/Sentry/src/main/java/org/Mengine/Plugin/Sentry/MengineSentryPlugin.java b/gradle/plugins/Sentry/src/main/java/org/Mengine/Plugin/Sentry/MengineSentryPlugin.java index a3e74d4d57..01609850cf 100644 --- a/gradle/plugins/Sentry/src/main/java/org/Mengine/Plugin/Sentry/MengineSentryPlugin.java +++ b/gradle/plugins/Sentry/src/main/java/org/Mengine/Plugin/Sentry/MengineSentryPlugin.java @@ -6,12 +6,13 @@ import androidx.annotation.NonNull; import androidx.annotation.StringRes; +import org.Mengine.Base.MengineAnalytics; import org.Mengine.Base.MengineApplication; import org.Mengine.Base.MengineListenerApplication; import org.Mengine.Base.MengineListenerEngine; +import org.Mengine.Base.MengineListenerLogger; import org.Mengine.Base.MengineListenerTransparencyConsent; import org.Mengine.Base.MengineListenerUser; -import org.Mengine.Base.MengineListenerLogger; import org.Mengine.Base.MengineLog; import org.Mengine.Base.MengineParamLoggerException; import org.Mengine.Base.MengineParamLoggerMessage; @@ -20,19 +21,18 @@ import org.Mengine.Base.MengineParamTransparencyConsent; import org.Mengine.Base.MengineUtils; -import java.util.ArrayList; -import java.util.List; import java.util.Map; +import io.sentry.JsonSerializable; import io.sentry.Sentry; -import io.sentry.android.core.SentryAndroid; -import io.sentry.protocol.User; -import io.sentry.SentryLogLevel; -import io.sentry.SentryLogParameters; import io.sentry.SentryAttribute; import io.sentry.SentryAttributes; +import io.sentry.SentryLogLevel; +import io.sentry.android.core.SentryAndroid; +import io.sentry.logger.SentryLogParameters; +import io.sentry.protocol.User; -public class MengineSentryPlugin extends MengineService implements MengineListenerApplication, MengineListenerEngine, MengineListenerTransparencyConsent, MengineListenerUser, MengineListenerLogger { +public class MengineSentryPlugin extends MengineService implements MengineListenerApplication, MengineListenerEngine, MengineListenerTransparencyConsent, MengineListenerLogger, MengineListenerUser { public static final String SERVICE_NAME = "Sentry"; public static final boolean SERVICE_EMBEDDING = true; @@ -41,52 +41,6 @@ public class MengineSentryPlugin extends MengineService implements MengineListen public static boolean m_passMeasurementGDPR = false; - private static SentryLogLevel getSentryLogLevel(@NonNull MengineParamLoggerMessage message) { - switch (message.MESSAGE_LEVEL) { - case MengineLog.LM_FATAL: - return SentryLogLevel.FATAL; - case MengineLog.LM_ERROR: - return SentryLogLevel.ERROR; - case MengineLog.LM_WARNING: - return SentryLogLevel.WARNING; - case MengineLog.LM_INFO: - case MengineLog.LM_MESSAGE: - case MengineLog.LM_MESSAGE_RELEASE: - return SentryLogLevel.INFO; - case MengineLog.LM_DEBUG: - case MengineLog.LM_VERBOSE: - return SentryLogLevel.DEBUG; - default: - return SentryLogLevel.INFO; - } - } - - private void setCustomKey(String key, Object value) { - if (value == null) { - Sentry.setExtra(key, "null"); - } else { - Sentry.setExtra(key, String.valueOf(value)); - } - } - - private void recordException(Throwable throwable) { - this.logInfo("recordException throwable: %s", - throwable.getMessage() - ); - - throwable.printStackTrace(System.err); - - Sentry.captureException(throwable); - } - - public void testCrash() { - this.logMessage("testCrash"); - - this.setCustomKey("test.crash", true); - - throw new RuntimeException("Sentry Test Crash"); - } - @Override public void onAppInit(MengineApplication application, boolean isMainProcess) throws MengineServiceInvalidInitializeException { if (isMainProcess == false) { @@ -95,24 +49,24 @@ public void onAppInit(MengineApplication application, boolean isMainProcess) thr String MengineSentryPlugin_DSN = this.getResourceString(METADATA_DSN); - this.logInfo("%s: %s", - METADATA_DSN, - MengineUtils.getRedactedValue(MengineSentryPlugin_DSN) + this.logInfo("%s: %s" + , METADATA_DSN + , MengineUtils.getRedactedValue(MengineSentryPlugin_DSN) ); boolean MengineSentryPlugin_EnableUncaughtExceptionHandler = this.getResourceBoolean(METADATA_ENABLE_UNCAUGHT_EXCEPTION_HANDLER); - this.logInfo("%s: %b", - METADATA_ENABLE_UNCAUGHT_EXCEPTION_HANDLER, - MengineSentryPlugin_EnableUncaughtExceptionHandler + this.logInfo("%s: %b" + , METADATA_ENABLE_UNCAUGHT_EXCEPTION_HANDLER + , MengineSentryPlugin_EnableUncaughtExceptionHandler ); MengineParamTransparencyConsent tcParam = application.makeTransparencyConsentParam(); m_passMeasurementGDPR = tcParam.getConsentMeasurement(); - this.logInfo("GDPR measurement: %s", - m_passMeasurementGDPR + this.logInfo("GDPR measurement: %s" + , m_passMeasurementGDPR ); tcParam.getConsentAnalyticsStorage(); @@ -198,8 +152,8 @@ public void onMengineChangeUserId(@NonNull MengineApplication application, Strin public void onMengineTransparencyConsent(@NonNull MengineApplication application, @NonNull MengineParamTransparencyConsent consent) { m_passMeasurementGDPR = consent.getConsentMeasurement(); - this.logInfo("GDPR measurement: %s", - m_passMeasurementGDPR + this.logInfo("GDPR measurement: %s" + , m_passMeasurementGDPR ); } @@ -208,6 +162,34 @@ public void onMengineRemoveUserData(@NonNull MengineApplication application) { Sentry.setUser(null); } + @Override + public void onMengineCaughtException(@NonNull MengineApplication application, Throwable throwable) { + this.recordException(throwable); + } + + @Override + public void onAppState(@NonNull MengineApplication application, String name, Object value) { + this.setCustomKey("." + name, value); + } + + static private SentryLogLevel getSentryLogLevel(@NonNull MengineParamLoggerMessage message) { + switch (message.MESSAGE_LEVEL) { + case MengineLog.LM_DEBUG: + return SentryLogLevel.DEBUG; + case MengineLog.LM_INFO: + return SentryLogLevel.INFO; + case MengineLog.LM_MESSAGE: + case MengineLog.LM_MESSAGE_RELEASE: + return SentryLogLevel.INFO; + case MengineLog.LM_WARNING: + return SentryLogLevel.WARN; + case MengineLog.LM_FATAL: + return SentryLogLevel.FATAL; + } + + return SentryLogLevel.TRACE; + } + @Override public void onMengineLog(@NonNull MengineApplication application, @NonNull MengineParamLoggerMessage message) { if (BuildConfig.DEBUG == false) { @@ -218,21 +200,19 @@ public void onMengineLog(@NonNull MengineApplication application, @NonNull Mengi SentryLogLevel level = MengineSentryPlugin.getSentryLogLevel(message); - List attributesList = new ArrayList<>(); - attributesList.add(SentryAttribute.stringAttribute("log.category", message.MESSAGE_CATEGORY.toString())); - attributesList.add(SentryAttribute.stringAttribute("log.thread", message.MESSAGE_THREAD)); + SentryAttributes attributes = SentryAttributes.of(null); + attributes.add(SentryAttribute.stringAttribute("log.category", message.MESSAGE_CATEGORY.toString())); + attributes.add(SentryAttribute.stringAttribute("log.thread", message.MESSAGE_THREAD)); - if (message.MESSAGE_FILE != null && message.MESSAGE_FILE.isEmpty() == false) { - attributesList.add(SentryAttribute.stringAttribute("log.file", message.MESSAGE_FILE)); - attributesList.add(SentryAttribute.integerAttribute("log.line", message.MESSAGE_LINE)); + if (message.MESSAGE_FILE != null) { + attributes.add(SentryAttribute.stringAttribute("log.file", message.MESSAGE_FILE)); + attributes.add(SentryAttribute.integerAttribute("log.line", message.MESSAGE_LINE)); } - if (message.MESSAGE_FUNCTION != null && message.MESSAGE_FUNCTION.isEmpty() == false) { - attributesList.add(SentryAttribute.stringAttribute("log.function", message.MESSAGE_FUNCTION)); + if (message.MESSAGE_FUNCTION != null) { + attributes.add(SentryAttribute.stringAttribute("log.function", message.MESSAGE_FUNCTION)); } - SentryAttributes attributes = SentryAttributes.of(attributesList.toArray(new SentryAttribute[0])); - Sentry.logger().log(level, SentryLogParameters.create(attributes), message.MESSAGE_DATA); } @@ -256,14 +236,27 @@ public void onMengineException(@NonNull MengineApplication application, @NonNull }); } - @Override - public void onMengineCaughtException(@NonNull MengineApplication application, Throwable throwable) { - this.recordException(throwable); + public void setCustomKey(String key, Object value) { + if (value == null) { + Sentry.setExtra(key, "null"); + } else { + Sentry.setExtra(key, String.valueOf(value)); + } } - @Override - public void onAppState(@NonNull MengineApplication application, String name, Object value) { - this.setCustomKey("." + name, value); + public void recordException(Throwable throwable) { + this.logInfo("recordException throwable: %s" + , throwable.getMessage() + ); + + Sentry.captureException(throwable); } -} + public void testCrash() { + this.logMessage("testCrash"); + + this.setCustomKey("test.crash", true); + + throw new RuntimeException("Sentry Test Crash"); + } +}