diff --git a/CHANGELOG.md b/CHANGELOG.md index e1370b61d..9a87344ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,8 @@ ### Fixes -- Fixed input handling in samples to work with old and new input system ([#2319](https://github.com/getsentry/sentry-unity/pull/2319)) +- The SDK now captures exceptions on WebGL through the logging integration instead of the incompatible log handler, providing better stack trace support . ([#2322](https://github.com/getsentry/sentry-unity/pull/2322)) +- Fixed input handling in samples to work with old and new input system. ([#2319](https://github.com/getsentry/sentry-unity/pull/2319)) ### Dependencies diff --git a/src/Sentry.Unity.Editor/WebGL/BuildPreProcess.cs b/src/Sentry.Unity.Editor/WebGL/BuildPreProcess.cs index 44891e2f9..e1edad4e2 100644 --- a/src/Sentry.Unity.Editor/WebGL/BuildPreProcess.cs +++ b/src/Sentry.Unity.Editor/WebGL/BuildPreProcess.cs @@ -18,15 +18,21 @@ public void OnPreprocessBuild(BuildReport report) return; } + var (options, _) = SentryScriptableObject.ConfiguredBuildTimeOptions(); + if (options?.Enabled is not true) + { + return; + } + if (PlayerSettings.WebGL.exceptionSupport == WebGLExceptionSupport.None) { throw new BuildFailedException("WebGL exception support is set to None. The Sentry SDK requires exception " + "support to function properly. Please change the WebGL exception support setting in Player Settings " + "or disable the Sentry SDK."); } - else if (PlayerSettings.WebGL.exceptionSupport != WebGLExceptionSupport.FullWithStacktrace) + + if (PlayerSettings.WebGL.exceptionSupport != WebGLExceptionSupport.FullWithStacktrace) { - var (options, _) = SentryScriptableObject.ConfiguredBuildTimeOptions(); var logger = options?.DiagnosticLogger ?? new UnityLogger(options ?? new SentryUnityOptions()); logger.LogWarning("The SDK requires the Exception Support to be set " + diff --git a/src/Sentry.Unity/Integrations/UnityApplicationLoggingIntegration.cs b/src/Sentry.Unity/Integrations/UnityApplicationLoggingIntegration.cs index e8aa101b0..11c4ac0f7 100644 --- a/src/Sentry.Unity/Integrations/UnityApplicationLoggingIntegration.cs +++ b/src/Sentry.Unity/Integrations/UnityApplicationLoggingIntegration.cs @@ -1,4 +1,6 @@ +using Sentry.Extensibility; using Sentry.Integrations; +using Sentry.Protocol; using UnityEngine; namespace Sentry.Unity.Integrations; @@ -6,6 +8,7 @@ namespace Sentry.Unity.Integrations; internal class UnityApplicationLoggingIntegration : ISdkIntegration { private readonly IApplication _application; + private readonly bool _captureExceptions; private ErrorTimeDebounce? _errorTimeDebounce; private LogTimeDebounce? _logTimeDebounce; private WarningTimeDebounce? _warningTimeDebounce; @@ -13,8 +16,9 @@ internal class UnityApplicationLoggingIntegration : ISdkIntegration private IHub? _hub; private SentryUnityOptions? _options; - internal UnityApplicationLoggingIntegration(IApplication? application = null) + internal UnityApplicationLoggingIntegration(bool captureExceptions = false, IApplication? application = null) { + _captureExceptions = captureExceptions; _application = application ?? ApplicationAdapter.Instance; } @@ -49,7 +53,8 @@ internal void OnLogMessageReceived(string message, string stacktrace, LogType lo } // LogType.Exception are getting handled by the UnityLogHandlerIntegration - if (logType is LogType.Exception) + // Unless we're configured to handle them - i.e. WebGL + if (logType is LogType.Exception && !_captureExceptions) { return; } @@ -58,6 +63,7 @@ internal void OnLogMessageReceived(string message, string stacktrace, LogType lo { var debounced = logType switch { + LogType.Exception => _errorTimeDebounce?.Debounced(), LogType.Error or LogType.Assert => _errorTimeDebounce?.Debounced(), LogType.Log => _logTimeDebounce?.Debounced(), LogType.Warning => _warningTimeDebounce?.Debounced(), @@ -70,14 +76,19 @@ internal void OnLogMessageReceived(string message, string stacktrace, LogType lo } } - if (logType is LogType.Error && _options?.CaptureLogErrorEvents is true) + if (logType is LogType.Exception) + { + var ule = new UnityErrorLogException(message, stacktrace, _options); + _hub.CaptureException(ule); + } + else if (logType is LogType.Error && _options?.CaptureLogErrorEvents is true) { if (_options?.AttachStacktrace is true && !string.IsNullOrEmpty(stacktrace)) { var ule = new UnityErrorLogException(message, stacktrace, _options); - var evt = new SentryEvent(ule) { Level = SentryLevel.Error }; + var sentryEvent = new SentryEvent(ule) { Level = SentryLevel.Error }; - _hub.CaptureEvent(evt); + _hub.CaptureEvent(sentryEvent); } else { diff --git a/src/Sentry.Unity/SentryUnityOptions.cs b/src/Sentry.Unity/SentryUnityOptions.cs index df74eed1d..69954a83d 100644 --- a/src/Sentry.Unity/SentryUnityOptions.cs +++ b/src/Sentry.Unity/SentryUnityOptions.cs @@ -333,8 +333,13 @@ internal SentryUnityOptions(IApplication? application = null, AddTransactionProcessor(processor); AddExceptionProcessor(new UnityExceptionProcessor()); - AddIntegration(new UnityLogHandlerIntegration(this)); - AddIntegration(new UnityApplicationLoggingIntegration()); + // UnityLogHandlerIntegration is not compatible with WebGL, so it's added conditionally + if (application.Platform != RuntimePlatform.WebGLPlayer) + { + AddIntegration(new UnityLogHandlerIntegration(this)); + AddIntegration(new UnityApplicationLoggingIntegration()); + } + AddIntegration(new StartupTracingIntegration()); AddIntegration(new AnrIntegration(behaviour)); AddIntegration(new UnityScopeIntegration(application, unityInfo)); diff --git a/src/Sentry.Unity/SentryUnitySdk.cs b/src/Sentry.Unity/SentryUnitySdk.cs index 03c1a86ad..098d9b862 100644 --- a/src/Sentry.Unity/SentryUnitySdk.cs +++ b/src/Sentry.Unity/SentryUnitySdk.cs @@ -94,8 +94,17 @@ public SentrySdk.CrashedLastRun CrashedLastRun() { if (_options.CrashedLastRun is null) { - _options.DiagnosticLogger?.LogDebug("The SDK does not have a 'CrashedLastRun' set. " + - "This might be due to a missing or disabled native integration."); + if (ApplicationAdapter.Instance.Platform == RuntimePlatform.WebGLPlayer) + { + _options.DiagnosticLogger?.LogDebug("Currently, the Sentry SDK for Unity provides no native support for WebGL." + + "LastRunState is `Unknown`."); + } + else + { + _options.DiagnosticLogger?.LogDebug("The SDK does not have a 'CrashedLastRun' set. " + + "This might be due to a missing or disabled native integration."); + } + return SentrySdk.CrashedLastRun.Unknown; } diff --git a/src/Sentry.Unity/WebGL/SentryWebGL.cs b/src/Sentry.Unity/WebGL/SentryWebGL.cs index 3407ef0e4..1777f0aca 100644 --- a/src/Sentry.Unity/WebGL/SentryWebGL.cs +++ b/src/Sentry.Unity/WebGL/SentryWebGL.cs @@ -1,4 +1,5 @@ using Sentry.Extensibility; +using Sentry.Unity.Integrations; using UnityEngine.Analytics; namespace Sentry.Unity.WebGL; @@ -16,6 +17,9 @@ public static void Configure(SentryUnityOptions options) { options.DiagnosticLogger?.LogDebug("Updating configuration for Unity WebGL."); + // Due to special exception handling on WebGL we capture these through `LogMessageReceived` too + options.AddIntegration(new UnityApplicationLoggingIntegration(captureExceptions: true)); + // Note: we need to use a custom background worker which actually doesn't work in the background // because Unity doesn't support async (multithreading) yet. This may change in the future so let's watch // https://docs.unity3d.com/2019.4/Documentation/ScriptReference/PlayerSettings.WebGL-threadsSupport.html @@ -26,7 +30,7 @@ public static void Configure(SentryUnityOptions options) // "An abnormal situation has occurred: the PlayerLoop internal function has been called recursively. // Please contact Customer Support with a sample project so that we can reproduce the problem and troubleshoot it." // Maybe we could write a file when this error occurs and recognize it on the next start. Like unity-native. - options.CrashedLastRun = () => false; + options.CrashedLastRun = null; // Disable async when accessing files (e.g. FileStream(useAsync: true)) because it throws on WebGL. options.UseAsyncFileIO = false; @@ -34,8 +38,15 @@ public static void Configure(SentryUnityOptions options) if (options.AttachScreenshot) { options.AttachScreenshot = false; - options.DiagnosticLogger?.LogWarning("Attaching screenshots on WebGL is disabled - " + - "it currently produces blank screenshots mid-frame."); + options.DiagnosticLogger?.LogWarning("Attaching screenshots is unsupported on WebGL - disabling. " + + "Currently, it produces blank screenshots mid-frame."); + } + + // On WebGL, the IL2CPP backend does not provide the API required to make the IL2CPP Event Processor work + if (options.Il2CppLineNumberSupportEnabled) + { + options.Il2CppLineNumberSupportEnabled = false; + options.DiagnosticLogger?.LogWarning("IL2CPP line number support is unsupported on WebGL - disabling."); } // Use AnalyticsSessionInfo.userId as the default UserID diff --git a/test/Scripts.Integration.Test/Scripts/SmokeTester.cs b/test/Scripts.Integration.Test/Scripts/SmokeTester.cs index 8879f4fed..0de0ead59 100644 --- a/test/Scripts.Integration.Test/Scripts/SmokeTester.cs +++ b/test/Scripts.Integration.Test/Scripts/SmokeTester.cs @@ -101,7 +101,13 @@ private IEnumerator SmokeTestCoroutine() #if !UNITY_EDITOR var crashed = CrashedLastRun(); + +#if UNITY_WEBGL + // On WebGL CrashedLastRun returns `Unknown` + t.Expect($"options.CrashedLastRun ({crashed}) == unknown (-2)", crashed == -2); +#else t.Expect($"options.CrashedLastRun ({crashed}) == false (0)", crashed == 0); +#endif #endif var currentMessage = 0; t.ExpectMessage(currentMessage, "'type':'session'"); diff --git a/test/Sentry.Unity.Tests/UnityApplicationLoggingIntegrationTests.cs b/test/Sentry.Unity.Tests/UnityApplicationLoggingIntegrationTests.cs index cfd8ee4e4..03f948a24 100644 --- a/test/Sentry.Unity.Tests/UnityApplicationLoggingIntegrationTests.cs +++ b/test/Sentry.Unity.Tests/UnityApplicationLoggingIntegrationTests.cs @@ -13,10 +13,12 @@ private class Fixture public TestHub Hub { get; set; } = null!; public SentryUnityOptions SentryOptions { get; set; } = null!; + public bool CaptureExceptions { get; set; } = false; + public UnityApplicationLoggingIntegration GetSut() { var application = new TestApplication(); - var integration = new UnityApplicationLoggingIntegration(application); + var integration = new UnityApplicationLoggingIntegration(CaptureExceptions, application); integration.Register(Hub, SentryOptions); return integration; } @@ -161,5 +163,17 @@ public void OnLogMessageReceived_AddAsBreadcrumbDisabled_NotAddedAsBreadcrumb(Lo Assert.IsFalse(_fixture.Hub.ConfigureScopeCalls.Count > 0); } + + [Test] + public void OnLogMessageReceived_LogTypeException_CaptureExceptionsEnabled_EventCaptured() + { + _fixture.CaptureExceptions = true; + var sut = _fixture.GetSut(); + var message = TestContext.CurrentContext.Test.Name; + + sut.OnLogMessageReceived(message, "stacktrace", LogType.Exception); + + Assert.AreEqual(1, _fixture.Hub.CapturedEvents.Count); + } } }