diff --git a/Android/BacktraceANRWatchdog.java b/Android/BacktraceANRWatchdog.java index c8a28118..c61fef27 100644 --- a/Android/BacktraceANRWatchdog.java +++ b/Android/BacktraceANRWatchdog.java @@ -21,11 +21,6 @@ public class BacktraceANRWatchdog extends Thread { private final static transient String LOG_TAG = BacktraceANRWatchdog.class.getSimpleName(); - /** - * Default timeout value in milliseconds - */ - private final static transient int DEFAULT_ANR_TIMEOUT = 5000; - /** * Enable debug mode - errors will not be sent if the debugger is connected @@ -62,11 +57,11 @@ public class BacktraceANRWatchdog extends Thread { /** * Initialize new instance of BacktraceANRWatchdog with default timeout */ - public BacktraceANRWatchdog(String gameObjectName, String methodName) { + public BacktraceANRWatchdog(String gameObjectName, String methodName, int anrTimeout) { Log.d(LOG_TAG, "Initializing ANR watchdog"); this.methodName = methodName; this.gameObjectName = gameObjectName; - this.timeout = DEFAULT_ANR_TIMEOUT; + this.timeout = anrTimeout; this.debug = false; BacktraceANRWatchdog._instance = this; this.start(); @@ -77,9 +72,10 @@ public BacktraceANRWatchdog(String gameObjectName, String methodName) { */ @Override public void run() { + Boolean reported = false; + Log.d(LOG_TAG, "Starting ANR watchdog. Anr timeout: " + this.timeout); while (!shouldStop && !isInterrupted()) { String dateTimeNow = Calendar.getInstance().getTime().toString(); - Log.d(LOG_TAG, "ANR WATCHDOG - " + dateTimeNow); final backtrace.io.backtrace_unity_android_plugin.BacktraceThreadWatcher threadWatcher = new backtrace.io.backtrace_unity_android_plugin.BacktraceThreadWatcher(0, 0); mainThreadHandler.post(new Runnable() { @Override @@ -96,7 +92,7 @@ public void run() { threadWatcher.tickPrivateCounter(); if (threadWatcher.getCounter() == threadWatcher.getPrivateCounter()) { - Log.d(LOG_TAG, "ANR is not detected"); + reported = false; continue; } @@ -105,6 +101,12 @@ public void run() { "is on and connected debugger"); continue; } + if (reported) { + // skipping, because we already reported an ANR report for current ANR + continue; + } + reported = true; + Log.d(LOG_TAG, "Detected blocked Java thread. Reporting Java ANR."); NotifyUnityAboutANR(); } } @@ -129,5 +131,6 @@ public static void printStackTrace(StackTraceElement[] stackTrace, PrintWriter p public void stopMonitoring() { Log.d(LOG_TAG, "ANR handler has been disabled."); shouldStop = true; + BacktraceANRWatchdog._instance = null; } } diff --git a/CHANGELOG.md b/CHANGELOG.md index fdf3aee3..ff7bce90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Backtrace Unity Release Notes +## Version 3.7.1 + +New functionality + +- Where not allowed, negative number values in the Backtrace Configuration Asset will automatically be reset to the default value. + +Bugfixes + +- Fixed redundant ANR detection +- Improved ANR configurability. Client settings not allow you to specify how many seconds of a delay constitutes an ANR. This value can be set dynamically based on the the exact devices, choosing higher values for older devices for example. +- Add iOS cleanup for invalid cached reports +- Backward compatibility support for .NET 3.5 + ## Version 3.7.0 - When an ANR/Hang is detected, it is now added to the Breadcrumbs on all the platforms we support ANRs for diff --git a/Editor/BacktraceConfigurationEditor.cs b/Editor/BacktraceConfigurationEditor.cs index 8038ff1c..7183a665 100644 --- a/Editor/BacktraceConfigurationEditor.cs +++ b/Editor/BacktraceConfigurationEditor.cs @@ -32,9 +32,7 @@ public override void OnInspectorGUI() serializedObject.FindProperty("HandleUnhandledExceptions"), new GUIContent(BacktraceConfigurationLabels.LABEL_HANDLE_UNHANDLED_EXCEPTION)); - EditorGUILayout.PropertyField( - serializedObject.FindProperty("ReportPerMin"), - new GUIContent(BacktraceConfigurationLabels.LABEL_REPORT_PER_MIN)); + DrawIntegerTextboxWithDefault("ReportPerMin", BacktraceConfigurationLabels.LABEL_REPORT_PER_MIN, 0, BacktraceConfiguration.DefaultReportPerMin, serializedObject); GUIStyle clientAdvancedSettingsFoldout = new GUIStyle(EditorStyles.foldout); showClientAdvancedSettings = EditorGUILayout.Foldout(showClientAdvancedSettings, "Client advanced settings", clientAdvancedSettingsFoldout); @@ -62,10 +60,7 @@ public override void OnInspectorGUI() } DrawMultiselectDropdown("ReportFilterType", reportFilterType, BacktraceConfigurationLabels.LABEL_REPORT_FILTER, serializedObject); - - EditorGUILayout.PropertyField( - serializedObject.FindProperty("NumberOfLogs"), - new GUIContent(BacktraceConfigurationLabels.LABEL_NUMBER_OF_LOGS)); + DrawIntegerTextboxWithDefault("NumberOfLogs", BacktraceConfigurationLabels.LABEL_NUMBER_OF_LOGS, 0, BacktraceConfiguration.DefaultNumberOfLogs, serializedObject); EditorGUILayout.PropertyField( serializedObject.FindProperty("PerformanceStatistics"), @@ -79,16 +74,11 @@ public override void OnInspectorGUI() serializedObject.FindProperty("Sampling"), new GUIContent(BacktraceConfigurationLabels.LABEL_SAMPLING)); - SerializedProperty gameObjectDepth = serializedObject.FindProperty("GameObjectDepth"); - EditorGUILayout.PropertyField(gameObjectDepth, new GUIContent(BacktraceConfigurationLabels.LABEL_GAME_OBJECT_DEPTH)); + DrawIntegerTextboxWithDefault("GameObjectDepth", BacktraceConfigurationLabels.LABEL_GAME_OBJECT_DEPTH, -1, BacktraceConfiguration.DefaultGameObjectDepth, serializedObject); - if (gameObjectDepth.intValue < -1) - { - EditorGUILayout.HelpBox("Please insert value greater or equal -1", MessageType.Error); - } EditorGUILayout.PropertyField( - serializedObject.FindProperty("DisableInEditor"), - new GUIContent(BacktraceConfigurationLabels.DISABLE_IN_EDITOR)); + serializedObject.FindProperty("DisableInEditor"), + new GUIContent(BacktraceConfigurationLabels.DISABLE_IN_EDITOR)); } #if !UNITY_WEBGL @@ -213,18 +203,12 @@ public override void OnInspectorGUI() serializedObject.FindProperty("GenerateScreenshotOnException"), new GUIContent(BacktraceConfigurationLabels.LABEL_GENERATE_SCREENSHOT_ON_EXCEPTION)); - SerializedProperty maxRecordCount = serializedObject.FindProperty("MaxRecordCount"); - EditorGUILayout.PropertyField(maxRecordCount, new GUIContent(BacktraceConfigurationLabels.LABEL_MAX_REPORT_COUNT)); - - SerializedProperty maxDatabaseSize = serializedObject.FindProperty("MaxDatabaseSize"); - EditorGUILayout.PropertyField(maxDatabaseSize, new GUIContent(BacktraceConfigurationLabels.LABEL_MAX_DATABASE_SIZE)); - - SerializedProperty retryInterval = serializedObject.FindProperty("RetryInterval"); - EditorGUILayout.PropertyField(retryInterval, new GUIContent(BacktraceConfigurationLabels.LABEL_RETRY_INTERVAL)); + DrawIntegerTextboxWithDefault("MaxRecordCount", BacktraceConfigurationLabels.LABEL_MAX_REPORT_COUNT, 1, BacktraceConfiguration.DefaultMaxRecordCount, serializedObject); + DrawIntegerTextboxWithDefault("MaxDatabaseSize", BacktraceConfigurationLabels.LABEL_MAX_DATABASE_SIZE, 0, BacktraceConfiguration.DefaultMaxDatabaseSize, serializedObject); + DrawIntegerTextboxWithDefault("RetryInterval", BacktraceConfigurationLabels.LABEL_RETRY_INTERVAL, 1, BacktraceConfiguration.DefaultRetryInterval, serializedObject); EditorGUILayout.LabelField("Backtrace database require at least one retry."); - SerializedProperty retryLimit = serializedObject.FindProperty("RetryLimit"); - EditorGUILayout.PropertyField(retryLimit, new GUIContent(BacktraceConfigurationLabels.LABEL_RETRY_LIMIT)); + DrawIntegerTextboxWithDefault("RetryLimit", BacktraceConfigurationLabels.LABEL_RETRY_LIMIT, 0, BacktraceConfiguration.DefaultRetryLimit, serializedObject); SerializedProperty retryOrder = serializedObject.FindProperty("RetryOrder"); EditorGUILayout.PropertyField(retryOrder, new GUIContent(BacktraceConfigurationLabels.LABEL_RETRY_ORDER)); @@ -234,6 +218,24 @@ public override void OnInspectorGUI() serializedObject.ApplyModifiedProperties(); } + + /// + /// Draw the textbox control dedicated to unsigned integers and apply default if user passes negative value + /// + /// Backtrace configuration property name + /// Property label + /// Default value + /// Configuration object + private static void DrawIntegerTextboxWithDefault(string propertyName, string label, int minimumValue, int defaultValue, SerializedObject serializedObject) + { + var property = serializedObject.FindProperty(propertyName); + EditorGUILayout.PropertyField(property, new GUIContent(label)); + if (property.intValue < minimumValue) + { + property.intValue = defaultValue; + } + } + /// /// Draw multiselect dropdown. By default PropertyField won't work correctly in Unity 2017/2018 /// if editor has to display multiselect dropdown by using enum flags. This code allows to generate diff --git a/Editor/BacktraceDatabaseConfigurationEditor.cs b/Editor/BacktraceDatabaseConfigurationEditor.cs index bef04be4..3688b10b 100644 --- a/Editor/BacktraceDatabaseConfigurationEditor.cs +++ b/Editor/BacktraceDatabaseConfigurationEditor.cs @@ -23,7 +23,7 @@ public override void OnInspectorGUI() #if UNITY_STANDALONE_WIN settings.MinidumpType = (MiniDumpType)EditorGUILayout.EnumFlagsField(BacktraceConfigurationLabels.LABEL_MINIDUMP_SUPPORT, settings.MinidumpType); #else - settings.MinidumpType = MiniDumpType.None; + settings.MinidumpType = MiniDumpType.None; #endif @@ -37,12 +37,12 @@ public override void OnInspectorGUI() settings.MaxRecordCount = EditorGUILayout.IntField(BacktraceConfigurationLabels.LABEL_MAX_REPORT_COUNT, settings.MaxRecordCount); if (settings.MaxRecordCount < 0) { - settings.MaxRecordCount = 0; + settings.MaxRecordCount = BacktraceConfiguration.DefaultMaxRecordCount; } settings.MaxDatabaseSize = EditorGUILayout.LongField(BacktraceConfigurationLabels.LABEL_MAX_DATABASE_SIZE, settings.MaxDatabaseSize); if (settings.MaxDatabaseSize < 0) { - settings.MaxDatabaseSize = 0; + settings.MaxDatabaseSize = BacktraceConfiguration.DefaultMaxDatabaseSize; } @@ -51,7 +51,7 @@ public override void OnInspectorGUI() settings.RetryLimit = EditorGUILayout.IntField(BacktraceConfigurationLabels.LABEL_RETRY_LIMIT, settings.RetryLimit); if (settings.RetryLimit < 0) { - settings.RetryLimit = 1; + settings.RetryLimit = BacktraceConfiguration.DefaultRetryLimit; } settings.RetryOrder = (RetryOrder)EditorGUILayout.EnumPopup(BacktraceConfigurationLabels.LABEL_RETRY_ORDER, settings.RetryOrder); } diff --git a/Runtime/BacktraceClient.cs b/Runtime/BacktraceClient.cs index 1a94f71f..b45a3ce3 100644 --- a/Runtime/BacktraceClient.cs +++ b/Runtime/BacktraceClient.cs @@ -24,7 +24,7 @@ namespace Backtrace.Unity /// public class BacktraceClient : MonoBehaviour, IBacktraceClient { - public const string VERSION = "3.7.0"; + public const string VERSION = "3.7.1"; internal const string DefaultBacktraceGameObjectName = "BacktraceClient"; public BacktraceConfiguration Configuration; diff --git a/Runtime/Model/BacktraceConfiguration.cs b/Runtime/Model/BacktraceConfiguration.cs index fa3d1094..4c782da8 100644 --- a/Runtime/Model/BacktraceConfiguration.cs +++ b/Runtime/Model/BacktraceConfiguration.cs @@ -28,6 +28,15 @@ public class BacktraceConfiguration : ScriptableObject UnityEngineLogLevel.Info | UnityEngineLogLevel.Warning; + public const int DefaultAnrWatchdogTimeout = 5000; + public const int DefaultRetryLimit = 3; + public const int DefaultReportPerMin = 50; + public const int DefaultGameObjectDepth = -1; + public const int DefaultNumberOfLogs = 10; + public const int DefaultMaxRecordCount = 8; + public const int DefaultMaxDatabaseSize = 0; + public const int DefaultRetryInterval = 60; + /// /// Backtrace server url /// @@ -44,7 +53,7 @@ public class BacktraceConfiguration : ScriptableObject /// Maximum number reports per minute /// [Tooltip("Reports per minute: Limits the number of reports the client will send per minutes. If set to 0, there is no limit. If set to a higher value and the value is reached, the client will not send any reports until the next minute. Default: 50")] - public int ReportPerMin = 50; + public int ReportPerMin = DefaultReportPerMin; /// /// "Disable error reporting integration in editor mode. @@ -96,13 +105,13 @@ public class BacktraceConfiguration : ScriptableObject /// Game object depth in Backtrace report /// [Tooltip("Allows developer to filter number of game object childrens in Backtrace report.")] - public int GameObjectDepth = -1; + public int GameObjectDepth = DefaultGameObjectDepth; /// /// Number of logs collected by Backtrace-Unity /// [Tooltip("Number of logs collected by Backtrace-Unity")] - public uint NumberOfLogs = 10; + public uint NumberOfLogs = DefaultNumberOfLogs; /// /// Flag that allows to include performance statistics in Backtrace report @@ -136,6 +145,11 @@ public class BacktraceConfiguration : ScriptableObject [Tooltip("Capture ANR events - Application not responding")] public bool HandleANR = true; + /// + /// Anr watchdog timeout in ms. Time needed to detect an ANR event + /// + public int AnrWatchdogTimeout = DefaultAnrWatchdogTimeout; + #if UNITY_ANDROID || UNITY_IOS /// /// Send Out of memory exceptions to Backtrace. @@ -261,23 +275,23 @@ public class BacktraceConfiguration : ScriptableObject /// Maximum number of stored reports in Database. If value is equal to zero, then limit not exists /// [Tooltip("This is one of two limits you can impose for controlling the growth of the offline store. This setting is the maximum number of stored reports in database. If value is equal to zero, then limit not exists, When the limit is reached, the database will remove the oldest entries.")] - public int MaxRecordCount = 8; + public int MaxRecordCount = DefaultMaxRecordCount; /// /// Database size in MB /// [Tooltip("This is the second limit you can impose for controlling the growth of the offline store. This setting is the maximum database size in MB. If value is equal to zero, then size is unlimited, When the limit is reached, the database will remove the oldest entries.")] - public long MaxDatabaseSize; + public long MaxDatabaseSize = DefaultMaxDatabaseSize; /// /// How much seconds library should wait before next retry. /// [Tooltip("If the database is unable to send its record, this setting specifies how many seconds the library should wait between retries.")] - public int RetryInterval = 60; + public int RetryInterval = DefaultRetryInterval; /// /// Maximum number of retries [Tooltip("If the database is unable to send its record, this setting specifies the maximum number of retries before the system gives up.")] - public int RetryLimit = 3; + public int RetryLimit = DefaultRetryLimit; /// /// Retry order diff --git a/Runtime/Model/BacktraceResult.cs b/Runtime/Model/BacktraceResult.cs index 544413e1..926917fb 100644 --- a/Runtime/Model/BacktraceResult.cs +++ b/Runtime/Model/BacktraceResult.cs @@ -114,20 +114,26 @@ internal void AddInnerResult(BacktraceResult innerResult) public static BacktraceResult FromJson(string json) { - if (string.IsNullOrEmpty(json)) - { - return new BacktraceResult() - { - Status = BacktraceResultStatus.Empty - }; - } - var rawResult = JsonUtility.FromJson(json); var result = new BacktraceResult() { - response = rawResult.response, - _rxId = rawResult._rxid, - Status = rawResult.response == "ok" ? BacktraceResultStatus.Ok : BacktraceResultStatus.ServerError + Status = string.IsNullOrEmpty(json) ? BacktraceResultStatus.Empty : BacktraceResultStatus.Ok }; + + if (result.Status == BacktraceResultStatus.Empty) + { + return result; + } + + try + { + var rawResult = JsonUtility.FromJson(json); + result.response = rawResult.response; + result._rxId = rawResult._rxid; + } + catch (Exception e) + { + Debug.LogWarning(string.Format("Cannot parse Backtrace JSON response. Error: {0}. Content: {1}", json, e.Message)); + } return result; } diff --git a/Runtime/Model/Database/BacktraceDatabaseSettings.cs b/Runtime/Model/Database/BacktraceDatabaseSettings.cs index 36c934d1..bd050166 100644 --- a/Runtime/Model/Database/BacktraceDatabaseSettings.cs +++ b/Runtime/Model/Database/BacktraceDatabaseSettings.cs @@ -9,6 +9,7 @@ namespace Backtrace.Unity.Model.Database public class BacktraceDatabaseSettings { private readonly BacktraceConfiguration _configuration; + private readonly uint _retryInterval; public BacktraceDatabaseSettings(string databasePath, BacktraceConfiguration configuration) { if (configuration == null || string.IsNullOrEmpty(databasePath)) @@ -18,6 +19,7 @@ public BacktraceDatabaseSettings(string databasePath, BacktraceConfiguration con DatabasePath = databasePath; _configuration = configuration; + _retryInterval = configuration.RetryInterval > 0 ? (uint)_configuration.RetryInterval : BacktraceConfiguration.DefaultRetryInterval; } /// /// Directory path where reports and minidumps are stored @@ -68,7 +70,7 @@ public uint RetryInterval { get { - return Convert.ToUInt32(_configuration.RetryInterval); + return _retryInterval; } } diff --git a/Runtime/Native/Android/NativeClient.cs b/Runtime/Native/Android/NativeClient.cs index 663b69bc..72acda34 100644 --- a/Runtime/Native/Android/NativeClient.cs +++ b/Runtime/Native/Android/NativeClient.cs @@ -119,9 +119,10 @@ private void SetDefaultAttributeMaps() private AndroidJavaObject _unhandledExceptionWatcher; private readonly bool _enableClientSideUnwinding = false; - public string GameObjectName { get; internal set; } = BacktraceClient.DefaultBacktraceGameObjectName; + public string GameObjectName { get; internal set; } public NativeClient(BacktraceConfiguration configuration, BacktraceBreadcrumbs breadcrumbs, IDictionary clientAttributes, IEnumerable attachments) : base(configuration, breadcrumbs) { + GameObjectName = BacktraceClient.DefaultBacktraceGameObjectName; SetDefaultAttributeMaps(); if (!_enabled) { @@ -382,7 +383,7 @@ public void HandleAnr() } try { - _anrWatcher = new AndroidJavaObject(_anrPath, GameObjectName, CallbackMethodName); + _anrWatcher = new AndroidJavaObject(_anrPath, GameObjectName, CallbackMethodName, AnrWatchdogTimeout); } catch (Exception e) { @@ -440,7 +441,7 @@ public void HandleAnr() // we won't false positive ANR report lastUpdatedCache = 0; } - Thread.Sleep(5000); + Thread.Sleep(AnrWatchdogTimeout); } }); AnrThread.IsBackground = true; diff --git a/Runtime/Native/Base/NativeClientBase.cs b/Runtime/Native/Base/NativeClientBase.cs index 1fc1b0af..661aae87 100644 --- a/Runtime/Native/Base/NativeClientBase.cs +++ b/Runtime/Native/Base/NativeClientBase.cs @@ -13,6 +13,7 @@ internal abstract class NativeClientBase protected const string CrashType = "Crash"; protected const string ErrorTypeAttribute = "error.type"; + protected int AnrWatchdogTimeout; /// /// Determine if ANR occurred and NativeClient should report ANR in breadcrumbs /// @@ -57,6 +58,9 @@ internal NativeClientBase(BacktraceConfiguration configuration, BacktraceBreadcr _configuration = configuration; _breadcrumbs = breadcrumbs; _shouldLogAnrsInBreadcrumbs = ShouldStoreAnrBreadcrumbs(); + AnrWatchdogTimeout = configuration.AnrWatchdogTimeout > 1000 + ? configuration.AnrWatchdogTimeout + : BacktraceConfiguration.DefaultAnrWatchdogTimeout; } /// diff --git a/Runtime/Native/Windows/NativeClient.cs b/Runtime/Native/Windows/NativeClient.cs index 9236e723..cdc58309 100644 --- a/Runtime/Native/Windows/NativeClient.cs +++ b/Runtime/Native/Windows/NativeClient.cs @@ -1,6 +1,7 @@ #if UNITY_STANDALONE_WIN using Backtrace.Unity.Interfaces; using Backtrace.Unity.Model; +using Backtrace.Unity.Extensions; using Backtrace.Unity.Model.Breadcrumbs; using Backtrace.Unity.Model.Breadcrumbs.Storage; using Backtrace.Unity.Runtime.Native.Base; @@ -187,7 +188,7 @@ public void HandleAnr() // we won't false positive ANR report lastUpdatedCache = 0; } - Thread.Sleep(5000); + Thread.Sleep(AnrWatchdogTimeout); } }); AnrThread.IsBackground = true; diff --git a/Runtime/Native/iOS/NativeClient.cs b/Runtime/Native/iOS/NativeClient.cs index e17cf737..760d39f8 100644 --- a/Runtime/Native/iOS/NativeClient.cs +++ b/Runtime/Native/iOS/NativeClient.cs @@ -188,7 +188,7 @@ public void HandleAnr() // we won't false positive ANR report lastUpdatedCache = 0; } - Thread.Sleep(5000); + Thread.Sleep(AnrWatchdogTimeout); } }); diff --git a/iOS/libBacktrace-Unity-Cocoa.a b/iOS/libBacktrace-Unity-Cocoa.a index a94bafcf..edfabe46 100644 Binary files a/iOS/libBacktrace-Unity-Cocoa.a and b/iOS/libBacktrace-Unity-Cocoa.a differ diff --git a/package.json b/package.json index dbd6f754..4f4579f7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "io.backtrace.unity", "displayName": "Backtrace", - "version": "3.7.0", + "version": "3.7.1", "unity": "2017.1", "description": "Backtrace's integration with Unity games allows customers to capture and report handled and unhandled Unity exceptions to their Backtrace instance, instantly offering the ability to prioritize and debug software errors.", "keywords": [