diff --git a/CHANGELOG.md b/CHANGELOG.md index d26aa4946..41ac6856e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Sentry config is now a scriptable object ([#220](https://github.com/getsentry/sentry-unity/pull/220)) - Unity protocol ([#234](https://github.com/getsentry/sentry-unity/pull/234)) +- Release health integration & Event-listener ([#225](https://github.com/getsentry/sentry-unity/pull/225)) ### Fixes diff --git a/samples/Directory.Build.targets b/samples/Directory.Build.targets new file mode 100644 index 000000000..d6ad0385c --- /dev/null +++ b/samples/Directory.Build.targets @@ -0,0 +1,3 @@ + + + diff --git a/src/Sentry.Unity/ApplicationPauseListener.cs b/src/Sentry.Unity/ApplicationPauseListener.cs new file mode 100644 index 000000000..668463018 --- /dev/null +++ b/src/Sentry.Unity/ApplicationPauseListener.cs @@ -0,0 +1,86 @@ +using System; +using UnityEngine; + +namespace Sentry.Unity +{ + /// + /// A MonoBehavior used to forward application focus events to subscribers. + /// + [DefaultExecutionOrder(-900)] + internal class ApplicationPauseListener : MonoBehaviour + { + /// + /// Hook to receive an event when the application gains focus. + /// + /// Listens to OnApplicationFocus for all platforms except Android, where we listen to OnApplicationPause. + /// + /// + public event Action? ApplicationResuming; + + /// + /// Hook to receive an event when the application loses focus. + /// + /// Listens to OnApplicationFocus for all platforms except Android, where we listen to OnApplicationPause. + /// + /// + public event Action? ApplicationPausing; + + // Keeping internal track of running state because OnApplicationPause and OnApplicationFocus get called during startup and would fire false resume events + private bool _isRunning = true; + + /// + /// To receive Leaving/Resuming events on Android. + /// + /// On Android, when the on-screen keyboard is enabled, it causes a OnApplicationFocus(false) event. + /// Additionally, if you press "Home" at the moment the keyboard is enabled, the OnApplicationFocus() event is + /// not called, but OnApplicationPause() is called instead. + /// + /// + /// + private void OnApplicationPause(bool pauseStatus) + { + if (Application.platform != RuntimePlatform.Android) + { + return; + } + + if (pauseStatus && _isRunning) + { + _isRunning = false; + ApplicationPausing?.Invoke(); + } + else if (!pauseStatus && !_isRunning) + { + _isRunning = true; + ApplicationResuming?.Invoke(); + } + } + + /// + /// To receive Leaving/Resuming events on all platforms except Android. + /// + /// + private void OnApplicationFocus(bool hasFocus) + { + // To avoid event duplication on Android since the pause event will be handled via OnApplicationPause + if (Application.platform == RuntimePlatform.Android) + { + return; + } + + if (hasFocus && !_isRunning) + { + _isRunning = true; + ApplicationResuming?.Invoke(); + } + else if (!hasFocus && _isRunning) + { + _isRunning = false; + ApplicationPausing?.Invoke(); + } + } + + // The GameObject has to destroy itself since it was created with HideFlags.HideAndDontSave + private void OnApplicationQuit() => Destroy(gameObject); + } +} diff --git a/src/Sentry.Unity/Integrations/SessionIntegration.cs b/src/Sentry.Unity/Integrations/SessionIntegration.cs new file mode 100644 index 000000000..010349a17 --- /dev/null +++ b/src/Sentry.Unity/Integrations/SessionIntegration.cs @@ -0,0 +1,33 @@ +using Sentry.Extensibility; +using Sentry.Integrations; +using UnityEngine; + +namespace Sentry.Unity.Integrations +{ + public class SessionIntegration : ISdkIntegration + { + public void Register(IHub hub, SentryOptions options) + { + if (!options.AutoSessionTracking) + { + return; + } + + options.DiagnosticLogger?.LogDebug("Registering Session integration."); + + // HideFlags.HideAndDontSave hides the GameObject in the hierarchy and prevents changing of scenes from destroying it + var gameListenerObject = new GameObject("SentryListener") {hideFlags = HideFlags.HideAndDontSave}; + var gameListener = gameListenerObject.AddComponent(); + gameListener.ApplicationResuming += () => + { + options.DiagnosticLogger?.LogDebug("Resuming session."); + hub.ResumeSession(); + }; + gameListener.ApplicationPausing += () => + { + options.DiagnosticLogger?.LogDebug("Pausing session."); + hub.PauseSession(); + }; + } + } +} diff --git a/src/Sentry.Unity/Integrations/UnityApplicationLoggingIntegration.cs b/src/Sentry.Unity/Integrations/UnityApplicationLoggingIntegration.cs index e683980f0..cd61c47ed 100644 --- a/src/Sentry.Unity/Integrations/UnityApplicationLoggingIntegration.cs +++ b/src/Sentry.Unity/Integrations/UnityApplicationLoggingIntegration.cs @@ -80,6 +80,7 @@ private void OnQuitting() // Note: On Windows Store Apps and Windows Phone 8.1 there is no application quit event. Consider using OnApplicationFocus event when focusStatus equals false. // Note: On WebGL it is not possible to implement OnApplicationQuit due to nature of the browser tabs closing. _application.LogMessageReceived -= OnLogMessageReceived; + _hub?.EndSession(); _hub?.FlushAsync(_sentryOptions?.ShutdownTimeout ?? TimeSpan.FromSeconds(1)).GetAwaiter().GetResult(); } diff --git a/src/Sentry.Unity/SentryOptionsUtility.cs b/src/Sentry.Unity/SentryOptionsUtility.cs index 65d0adf94..314ca1296 100644 --- a/src/Sentry.Unity/SentryOptionsUtility.cs +++ b/src/Sentry.Unity/SentryOptionsUtility.cs @@ -1,6 +1,5 @@ -using Sentry.Extensibility; +using System; using Sentry.Unity.Integrations; -using UnityEngine; namespace Sentry.Unity { @@ -12,6 +11,7 @@ public static void SetDefaults(SentryUnityOptions options, IApplication? applica options.Enabled = true; options.Dsn = null; + options.AutoSessionTracking = true; options.CaptureInEditor = true; options.RequestBodyCompressionLevel = CompressionLevelWithAuto.NoCompression; options.AttachStacktrace = false; @@ -26,7 +26,7 @@ public static void SetDefaults(SentryUnityOptions options, IApplication? applica options.CacheDirectoryPath = application.PersistentDataPath; options.Debug = true; - options.DebugOnlyInEditor = true; + options.DebugOnlyInEditor = false; options.DiagnosticLevel = SentryLevel.Warning; TryAttachLogger(options, application); @@ -46,7 +46,7 @@ public static void SetDefaults(ScriptableSentryUnityOptions options) options.EnableOfflineCaching = true; options.Debug = true; - options.DebugOnlyInEditor = true; + options.DebugOnlyInEditor = false; options.DiagnosticLevel = SentryLevel.Warning; } diff --git a/src/Sentry.Unity/SentryUnity.cs b/src/Sentry.Unity/SentryUnity.cs index 2435f324f..e385b733d 100644 --- a/src/Sentry.Unity/SentryUnity.cs +++ b/src/Sentry.Unity/SentryUnity.cs @@ -1,7 +1,6 @@ using System; using System.ComponentModel; using Sentry.Extensibility; -using UnityEngine; namespace Sentry.Unity { diff --git a/src/Sentry.Unity/SentryUnityOptions.cs b/src/Sentry.Unity/SentryUnityOptions.cs index 520a63f11..3971e9cc6 100644 --- a/src/Sentry.Unity/SentryUnityOptions.cs +++ b/src/Sentry.Unity/SentryUnityOptions.cs @@ -76,6 +76,7 @@ public SentryUnityOptions() this.AddIntegration(new UnityApplicationLoggingIntegration()); this.AddIntegration(new UnityBeforeSceneLoadIntegration()); this.AddIntegration(new SceneManagerIntegration()); + this.AddIntegration(new SessionIntegration()); } public override string ToString() diff --git a/src/sentry-dotnet b/src/sentry-dotnet index 9f6943f83..4e835a344 160000 --- a/src/sentry-dotnet +++ b/src/sentry-dotnet @@ -1 +1 @@ -Subproject commit 9f6943f83afca9c2bc8f6cd5759b8db661b1c84a +Subproject commit 4e835a3440ba93c1cb8532eed433545d69e55b7b diff --git a/test/Sentry.Unity.Tests/Stubs/TestHub.cs b/test/Sentry.Unity.Tests/Stubs/TestHub.cs index 1a65a2094..2e62e7fec 100644 --- a/test/Sentry.Unity.Tests/Stubs/TestHub.cs +++ b/test/Sentry.Unity.Tests/Stubs/TestHub.cs @@ -35,20 +35,16 @@ public void CaptureTransaction(Transaction transaction) public void CaptureSession(SessionUpdate sessionUpdate) { - throw new NotImplementedException(); } public Task FlushAsync(TimeSpan timeout) { - throw new NotImplementedException(); + return Task.CompletedTask; } public void ConfigureScope(Action configureScope) => _configureScopeCalls.Add(configureScope); - public Task ConfigureScopeAsync(Func configureScope) - { - throw new NotImplementedException(); - } + public Task ConfigureScopeAsync(Func configureScope) => Task.CompletedTask; public void BindClient(ISentryClient client) { @@ -93,12 +89,22 @@ public void BindException(Exception exception, ISpan span) public void StartSession() { - throw new NotImplementedException(); + // TODO: test sessions + } + + public void PauseSession() + { + // TODO: test sessions + } + + public void ResumeSession() + { + // TODO: test sessions } public void EndSession(SessionEndStatus status = SessionEndStatus.Exited) { - throw new NotImplementedException(); + // TODO: test sessions } } }