diff --git a/CHANGELOG.md b/CHANGELOG.md index 340193f8e..6e5772ce1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## 7.5.1 (2023-02-08) + +### Dependency updates + +- Update bugsnag-cocoa from v6.25.1 to [v6.25.2](https://github.com/bugsnag/bugsnag-cocoa/blob/master/CHANGELOG.md#6252-2023-01-18) + +### Bug fixes + +* Fix an issue where collections in metadata were not present in native Android crashes. [#685](https://github.com/bugsnag/bugsnag-unity/pull/685) + +* Fix an issue where errors in serialisation threw exceptions. [#693](https://github.com/bugsnag/bugsnag-unity/pull/693) + +* Fix an issue where persisted events had 'unhandled' set to null [#695](https://github.com/bugsnag/bugsnag-unity/pull/695) + ## 7.5.0 (2023-01-04) ### Enhancements diff --git a/Gemfile b/Gemfile index 0d5b5fa85..ab2a4b2a9 100644 --- a/Gemfile +++ b/Gemfile @@ -6,7 +6,7 @@ gem 'xcodeproj' unless Gem.win_platform? # Use official Maze Runner release - gem 'bugsnag-maze-runner', git: 'https://github.com/bugsnag/maze-runner', tag: 'v7.10.1' + gem 'bugsnag-maze-runner', git: 'https://github.com/bugsnag/maze-runner', tag: 'v7.11.0' # Use a specific Maze Runner branch #gem 'bugsnag-maze-runner', git: 'https://github.com/bugsnag/maze-runner', branch: 'master' diff --git a/bugsnag-android b/bugsnag-android index 7ebe519f3..824fc4f71 160000 --- a/bugsnag-android +++ b/bugsnag-android @@ -1 +1 @@ -Subproject commit 7ebe519f34f13ff4682acdc74f8a60ef3bfb9ed0 +Subproject commit 824fc4f71e5b70e6d491c75d66dfd57774846826 diff --git a/bugsnag-cocoa b/bugsnag-cocoa index 8a692add2..b260ddb55 160000 --- a/bugsnag-cocoa +++ b/bugsnag-cocoa @@ -1 +1 @@ -Subproject commit 8a692add23a7874fc88a22bb361b096007f23c10 +Subproject commit b260ddb5570883ee8fd35860d8d7ff381250d4a1 diff --git a/build.cake b/build.cake index 837152857..da3b1d5fe 100644 --- a/build.cake +++ b/build.cake @@ -5,7 +5,7 @@ var target = Argument("target", "Default"); var solution = File("./BugsnagUnity.sln"); var configuration = Argument("configuration", "Release"); var project = File("./src/BugsnagUnity/BugsnagUnity.csproj"); -var version = "7.5.0"; +var version = "7.5.1"; Task("Restore-NuGet-Packages") .Does(() => NuGetRestore(solution)); diff --git a/example/Packages/manifest.json b/example/Packages/manifest.json new file mode 100644 index 000000000..7a5ffb35d --- /dev/null +++ b/example/Packages/manifest.json @@ -0,0 +1,41 @@ +{ + "dependencies": { + "com.bugsnag.unitynotifier": "https://github.com/bugsnag/bugsnag-unity-upm.git", + "com.unity.ads": "2.0.8", + "com.unity.analytics": "3.2.3", + "com.unity.collab-proxy": "1.2.15", + "com.unity.package-manager-ui": "2.0.13", + "com.unity.purchasing": "2.2.1", + "com.unity.textmeshpro": "1.4.1", + "com.unity.modules.ai": "1.0.0", + "com.unity.modules.animation": "1.0.0", + "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.cloth": "1.0.0", + "com.unity.modules.director": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.particlesystem": "1.0.0", + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.physics2d": "1.0.0", + "com.unity.modules.screencapture": "1.0.0", + "com.unity.modules.terrain": "1.0.0", + "com.unity.modules.terrainphysics": "1.0.0", + "com.unity.modules.tilemap": "1.0.0", + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.uielements": "1.0.0", + "com.unity.modules.umbra": "1.0.0", + "com.unity.modules.unityanalytics": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.unitywebrequestassetbundle": "1.0.0", + "com.unity.modules.unitywebrequestaudio": "1.0.0", + "com.unity.modules.unitywebrequesttexture": "1.0.0", + "com.unity.modules.unitywebrequestwww": "1.0.0", + "com.unity.modules.vehicles": "1.0.0", + "com.unity.modules.video": "1.0.0", + "com.unity.modules.vr": "1.0.0", + "com.unity.modules.wind": "1.0.0", + "com.unity.modules.xr": "1.0.0" + } +} diff --git a/example/README.md b/example/README.md new file mode 100644 index 000000000..e230ea838 --- /dev/null +++ b/example/README.md @@ -0,0 +1,31 @@ +# Unity Example App + +This Application was made to demonstrate various features of the BugSnag Unity notifier. + +## Features including: + + + +## Running the app + +
    +
  1. Clone the repo
  2. +``` +git clone git@github.com:bugsnag/bugsnag-unity.git --recursive +``` +
  3. Once the project has opened in Unity
  4. +
  5. Navigate to `Windows>Bugsnag>Configuration`
  6. +
  7. Enter your Bugsnag project API Key under the basic Configuration section of the package window
  8. +
  9. Press `Play` in the editor
  10. +
  11. Once loaded, click any of the error buttons
  12. +
  13. Confirm whether the BugSnag dashboard has received the error.
  14. +
+ +For more configuration and full information please read the documentation at +https://docs.bugsnag.com/platforms/unity/configuration-options/ + diff --git a/features/android/android_jvm_errors.feature b/features/android/android_jvm_errors.feature index c632f088f..53bd8517e 100644 --- a/features/android/android_jvm_errors.feature +++ b/features/android/android_jvm_errors.feature @@ -33,7 +33,6 @@ Feature: Android JVM Exceptions And the event "exceptions.0.stacktrace.0.lineNumber" equals 13 And the error payload field "events.0.threads" is null - #NOTE: Metadata testing will be improved in this scenario after PLAT-9127 Scenario: Android JVM Background Thread Smoke Test When I run the game in the "AndroidBackgroundJVMSmokeTest" state And I wait for 2 seconds @@ -44,6 +43,7 @@ Feature: Android JVM Exceptions And expected device metadata is included in the event And expected app metadata is included in the event And feature flags are included in the event + And custom metadata is included in the event And the event "breadcrumbs.0.name" equals "Bugsnag loaded" And the event "breadcrumbs.1.name" equals "test" And the event "user.id" equals "1" @@ -65,13 +65,6 @@ Feature: Android JVM Exceptions And the event "exceptions.0.stacktrace.0.lineNumber" equals 19 And the error payload field "events.0.threads" is not null - # Metadata - And the event "metaData.init" is null - And the event "metaData.custom.letter" equals "QX" - And the event "metaData.custom.better" equals "400" - And the event "metaData.test.test1" equals "test1" - And the event "metaData.test.test2" is null - And the error payload field "events.0.usage.config" is not null And the error payload field "events.0.usage.callbacks.onSession" equals 1 diff --git a/features/android/android_ndk_errors.feature b/features/android/android_ndk_errors.feature index c0b883f6d..8fba90806 100644 --- a/features/android/android_ndk_errors.feature +++ b/features/android/android_ndk_errors.feature @@ -29,6 +29,7 @@ Feature: Android NDK crash And the event "severityReason.type" equals "signal" And the event "severityReason.attributes.signalType" equals "SIGSEGV" And the event "severityReason.unhandledOverridden" is false + And custom metadata is included in the event And expected app metadata is included in the event # Stacktrace validation And the error payload field "events.0.exceptions.0.stacktrace" is a non-empty array @@ -45,9 +46,4 @@ Feature: Android NDK crash # Native context override And the event "context" equals "My Context" - # Metadata - And the event "metaData.init" is null - And the event "metaData.custom.letter" equals "QX" - And the event "metaData.custom.better" equals "400" - And the event "metaData.test.test1" equals "test1" - And the event "metaData.test.test2" is null + diff --git a/features/csharp/csharp_events.feature b/features/csharp/csharp_events.feature index 1fb043f30..98489631e 100644 --- a/features/csharp/csharp_events.feature +++ b/features/csharp/csharp_events.feature @@ -167,4 +167,10 @@ Feature: csharp events And the event "severityReason.type" equals "userSpecifiedSeverity" And the event "unhandled" is false + Scenario: Discard event after serialisation error + When I run the game in the "SerialisationError" state + And I wait to receive 1 error + And the exception "message" equals "SerialisationError" + + diff --git a/features/csharp/csharp_persistence.feature b/features/csharp/csharp_persistence.feature index 6513d4947..88c49a3ba 100644 --- a/features/csharp/csharp_persistence.feature +++ b/features/csharp/csharp_persistence.feature @@ -3,7 +3,7 @@ Feature: Unity Persistence Background: Given I clear the Bugsnag cache - @skip_windows @skip_macos @skip_webgl #pending PLAT-8632 + @skip_windows @skip_webgl @skip_cocoa @skip_android #pending PLAT-8632 Scenario: Receive a persisted session When I set the HTTP status code for the next requests to "408" And I run the game in the "PersistSession" state @@ -31,9 +31,11 @@ Feature: Unity Persistence And I run the game in the "PersistEventReport" state And I wait to receive 2 errors And I sort the errors by the payload field "events.0.exceptions.0.message" + And the error is valid for the error reporting API sent by the Unity notifier And the event "context" equals "Error 1" And the exception "message" equals "Error 1" And I discard the oldest error + And the error is valid for the error reporting API sent by the Unity notifier And the event "context" equals "Error 2" And the exception "message" equals "Error 2" diff --git a/features/fixtures/maze_runner/Assets/Scenes/MainScene.unity b/features/fixtures/maze_runner/Assets/Scenes/MainScene.unity index 6e83881d2..17e0ca76c 100644 --- a/features/fixtures/maze_runner/Assets/Scenes/MainScene.unity +++ b/features/fixtures/maze_runner/Assets/Scenes/MainScene.unity @@ -1036,6 +1036,9 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: e84e72df185f344be9e23af93b2cabc4, type: 3} m_Name: m_EditorClassIdentifier: + CustomStacktrace: 'Main.CUSTOM1 () (at Assets/Scripts/Main.cs:123) + + Main.CUSTOM2 () (at Assets/Scripts/Main.cs:123)' --- !u!1 &1160627402 GameObject: m_ObjectHideFlags: 0 @@ -2094,6 +2097,7 @@ GameObject: - component: {fileID: 2039813473} - component: {fileID: 2039813475} - component: {fileID: 2039813474} + - component: {fileID: 2039813477} m_Layer: 0 m_Name: Events m_TagString: Untagged @@ -2340,6 +2344,18 @@ MonoBehaviour: CustomStacktrace: 'Main.CUSTOM1 () (at Assets/Scripts/Main.cs:123) Main.CUSTOM2 () (at Assets/Scripts/Main.cs:123)' +--- !u!114 &2039813477 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2039813460} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: b0623c2806d68426e86c8fc6ba822dd8, type: 3} + m_Name: + m_EditorClassIdentifier: --- !u!1 &2059449697 GameObject: m_ObjectHideFlags: 0 diff --git a/features/fixtures/maze_runner/Assets/Scripts/Scenario.cs b/features/fixtures/maze_runner/Assets/Scripts/Scenario.cs index 8872639fe..2372cec79 100644 --- a/features/fixtures/maze_runner/Assets/Scripts/Scenario.cs +++ b/features/fixtures/maze_runner/Assets/Scripts/Scenario.cs @@ -87,24 +87,38 @@ private static string FindDotnetApiCompatibility() public void AddTestingMetadata() { - Bugsnag.AddMetadata("init", new Dictionary(){ - {"foo", "bar" }, - }); - Bugsnag.AddMetadata("test", "test1", "test1"); - Bugsnag.AddMetadata("test", "test2", "test2"); + Bugsnag.AddMetadata("custom", new Dictionary(){ - {"letter", "QX" }, - {"better", 400 }, - {"string-array", new string []{"1","2","3"} }, - {"int-array", new int []{1,2,3} }, - {"dict", new Dictionary(){ {"test" , 123 } } } + {"int", 123 }, + {"float", 123.123f }, + // {"long", 12345678901234567890 }, pending PLAT-9426 + {"double", 123.456 }, + {"stringArray", new []{"1",null,"3"} }, + {"emptyStringArray", new string[]{} }, + {"intList", new List(){1,2,3} }, + {"intArray", new []{4,5,6} }, + {"stringDict", new Dictionary(){ {"hello","goodbye"} } } }); + + Bugsnag.AddMetadata("clearMe", new Dictionary(){ + {"test", "test" }, + }); + + Bugsnag.ClearMetadata("clearMe"); + + Bugsnag.AddMetadata("test", "test1", "test1"); + Bugsnag.AddMetadata("test", "test1", "test2"); + Bugsnag.AddMetadata("test", "nullMe", "notNull"); + Bugsnag.AddMetadata("test", "nullMe", null); + Bugsnag.AddMetadata("app", new Dictionary(){ - {"buildno", "0.1" }, - {"cache", null }, + {"extra", "inApp" } }); - Bugsnag.ClearMetadata("init"); - Bugsnag.ClearMetadata("test", "test2"); + + Bugsnag.AddMetadata("device", new Dictionary(){ + {"extra", "inDevice" } + }); + } public void AddTestingFeatureFlags() diff --git a/features/fixtures/maze_runner/Assets/Scripts/Scenarios/Csharp/Events/SerialisationError.cs b/features/fixtures/maze_runner/Assets/Scripts/Scenarios/Csharp/Events/SerialisationError.cs new file mode 100644 index 000000000..4b1f1c5a2 --- /dev/null +++ b/features/fixtures/maze_runner/Assets/Scripts/Scenarios/Csharp/Events/SerialisationError.cs @@ -0,0 +1,35 @@ +using System; +using BugsnagUnity; + +public class SerialisationError : Scenario +{ + private class Break + { + public string BrokenString + { + get + { + throw new Exception("BrokenString"); + return string.Empty; + } + } + } + + public override void Run() + { + Bugsnag.AddOnError(BrokenCallback); + + DoSimpleNotify("SerialisationErrorBroken"); //this should not get delivered + + Bugsnag.RemoveOnError(BrokenCallback); + + DoSimpleNotify("SerialisationError"); //this one should + } + + private bool BrokenCallback(IEvent @event) + { + @event.AddMetadata("Test", "Test", new Break()); + return true; + } +} + diff --git a/features/fixtures/maze_runner/Assets/Scripts/Scenarios/Csharp/Events/SerialisationError.cs.meta b/features/fixtures/maze_runner/Assets/Scripts/Scenarios/Csharp/Events/SerialisationError.cs.meta new file mode 100644 index 000000000..c7a26f2c9 --- /dev/null +++ b/features/fixtures/maze_runner/Assets/Scripts/Scenarios/Csharp/Events/SerialisationError.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b0623c2806d68426e86c8fc6ba822dd8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/features/ios/ios_native_errors.feature b/features/ios/ios_native_errors.feature index a5750f70f..f5f011332 100644 --- a/features/ios/ios_native_errors.feature +++ b/features/ios/ios_native_errors.feature @@ -30,6 +30,7 @@ Feature: iOS Native Errors And I run the game in the "StartSDKDefault" state And I wait to receive an error And expected device metadata is included in the event + And custom metadata is included in the event And feature flags are included in the event And the event "breadcrumbs.0.name" equals "Bugsnag loaded" And the event "breadcrumbs.1.name" equals "test" @@ -47,7 +48,6 @@ Feature: iOS Native Errors And the event "app.version" is not null And the event "metaData.app.companyName" equals "bugsnag" And the event "metaData.app.name" matches ".azerunner" - And the event "metaData.app.buildno" is not null # Exception details And the error payload field "events" is an array with 1 elements @@ -56,13 +56,6 @@ Feature: iOS Native Errors And the event "unhandled" is true And the event "severity" equals "error" - # Metadata - And the event "metaData.init" is null - And the event "metaData.custom.letter" equals "QX" - And the event "metaData.custom.better" equals 400 - And the event "metaData.test.test1" equals "test1" - And the event "metaData.test.test2" is null - # Telemetry And the error payload field "events.0.usage.config" is not null And the error payload field "events.0.usage.callbacks.onSession" equals 1 @@ -75,6 +68,8 @@ Feature: iOS Native Errors And I run the game in the "StartSDKDefault" state And I wait to receive an error And expected device metadata is included in the event + And custom metadata is included in the event + And feature flags are included in the event And the event "breadcrumbs.0.name" equals "Bugsnag loaded" And the event "breadcrumbs.1.name" equals "test" @@ -92,7 +87,6 @@ Feature: iOS Native Errors And the event "app.version" is not null And the event "metaData.app.companyName" equals "bugsnag" And the event "metaData.app.name" matches ".azerunner" - And the event "metaData.app.buildno" is not null # Exception details And the error payload field "events" is an array with 1 elements @@ -103,12 +97,6 @@ Feature: iOS Native Errors And the error payload field "events.0.exceptions.0.stacktrace" is a non-empty array And the event "exceptions.0.stacktrace.0.method" equals "__pthread_kill" - # Metadata - And the event "metaData.init" is null - And the event "metaData.custom.letter" equals "QX" - And the event "metaData.custom.better" equals 400 - And the event "metaData.test.test1" equals "test1" - And the event "metaData.test.test2" is null diff --git a/features/steps/unity_steps.rb b/features/steps/unity_steps.rb index 4091b34c2..3502876a2 100644 --- a/features/steps/unity_steps.rb +++ b/features/steps/unity_steps.rb @@ -184,18 +184,24 @@ def check_error_reporting_api(notifier_name) end end + +# And the event "metaData.custom.long" equals 12345678901234567890 should be readded after PLAT-9426 + Then("custom metadata is included in the event") do steps %Q{ - Then the event "metaData.app.buildno" equals "0.1" - And the event "metaData.app.cache" is null - And the event "metaData.init" is null - And the event "metaData.custom.letter" equals "QX" - And the event "metaData.custom.better" equals 400 - And the event "metaData.test.test1" equals "test1" - And the event "metaData.test.test2" is null - And the error payload field "events.0.metaData.custom.int-array" is a non-empty array - And the error payload field "events.0.metaData.custom.string-array" is a non-empty array - And the error payload field "events.0.metaData.custom.dict" is not null + Then the event "metaData.custom.int" equals 123 + And the event "metaData.custom.float" equals 123.123 to 3 decimal places + And the event "metaData.custom.double" equals 123.456 to 3 decimal places + And the event "metaData.custom.stringArray.0" equals "1" + And the event "metaData.custom.emptyStringArray.0" is null + And the event "metaData.custom.intList.2" equals 3 + And the event "metaData.custom.intArray.1" equals 5 + And the event "metaData.custom.stringDict.hello" equals "goodbye" + And the event "metaData.clearMe" is null + And the event "metaData.test.test1" equals "test2" + And the event "metaData.test.nullMe" is null + And the event "metaData.app.extra" equals "inApp" + And the event "metaData.device.extra" equals "inDevice" } end @@ -315,7 +321,6 @@ def check_error_reporting_api(notifier_name) And the event "app.version" is not null And the event "metaData.app.companyName" equals "bugsnag" And the event "metaData.app.name" equals "Mazerunner" - And the event "metaData.app.buildno" is not null } end diff --git a/src/BugsnagUnity/Client.cs b/src/BugsnagUnity/Client.cs index 2526f8ec8..e58b4453a 100644 --- a/src/BugsnagUnity/Client.cs +++ b/src/BugsnagUnity/Client.cs @@ -510,13 +510,16 @@ private void NotifyOnMainThread(Error[] exceptions, HandledState handledState, F var report = new Report(Configuration, @event); if (!report.Ignored) { - PayloadManager.AddPendingPayload(report); - Send(report); - if (Configuration.IsBreadcrumbTypeEnabled(BreadcrumbType.Error)) + //if serialisation fails, then we ignore the event + if (PayloadManager.AddPendingPayload(report)) { - Breadcrumbs.Leave(Breadcrumb.FromReport(report)); + Send(report); + if (Configuration.IsBreadcrumbTypeEnabled(BreadcrumbType.Error)) + { + Breadcrumbs.Leave(Breadcrumb.FromReport(report)); + } + SessionTracking.AddException(report); } - SessionTracking.AddException(report); } } diff --git a/src/BugsnagUnity/Native/Android/NativeClient.cs b/src/BugsnagUnity/Native/Android/NativeClient.cs index 04d61dadb..c225ae0f5 100644 --- a/src/BugsnagUnity/Native/Android/NativeClient.cs +++ b/src/BugsnagUnity/Native/Android/NativeClient.cs @@ -126,10 +126,7 @@ public IDictionary GetNativeMetadata() public void AddNativeMetadata(string section, IDictionary data) { - foreach (var pair in data) - { - NativeInterface.AddMetadata(section,pair.Key,pair.Value == null ? "null" : pair.Value.ToString()); - } + NativeInterface.AddMetadata(section,data); } public void AddFeatureFlag(string name, string variant = null) diff --git a/src/BugsnagUnity/Native/Android/NativeInterface.cs b/src/BugsnagUnity/Native/Android/NativeInterface.cs index 10e8350a8..10f0dc72d 100644 --- a/src/BugsnagUnity/Native/Android/NativeInterface.cs +++ b/src/BugsnagUnity/Native/Android/NativeInterface.cs @@ -616,7 +616,8 @@ public Dictionary GetDevice() public Dictionary GetMetadata() { - return GetJavaMapData("getMetadata"); + var metadata = GetJavaMapData("getMetadata"); + return metadata; } public Dictionary GetUser() @@ -642,14 +643,134 @@ public void ClearMetadata(string tab, string key) CallNativeVoidMethod("clearMetadata", "(Ljava/lang/String;Ljava/lang/String;)V", new object[] { tab, key }); } - public void AddMetadata(string tab, string key, string value) + public void AddMetadata(string section, IDictionary metadata) { - if (tab == null || key == null) + if (section == null || metadata == null) { return; } - CallNativeVoidMethod("addMetadata", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V", - new object[] { tab, key, value }); + CallNativeVoidMethod("addMetadata", "(Ljava/lang/String;Ljava/util/Map;)V", + new object[] { section, DictionaryToJavaMap(metadata) }); + } + + internal static AndroidJavaObject DictionaryToJavaMap(IDictionary inputDict) + { + AndroidJavaObject map = new AndroidJavaObject("java.util.HashMap"); + if (inputDict != null) + { + foreach (var entry in inputDict) + { + var key = new AndroidJavaObject("java.lang.String", entry.Key); + + if (entry.Value == null) + { + map.Call("put", key, null); + } + else if (entry.Value is IDictionary) + { + if (entry.Value is IDictionary dictionary) + { + map.Call("put", key, DictionaryToJavaMap(dictionary)); + } + else + { + var convertedDictionary = ConvertIfPoss(entry.Value); + if (convertedDictionary != null) + { + map.Call("put", key, DictionaryToJavaMap(convertedDictionary)); + } + } + } + else if (IsListOrArray(entry.Value)) + { + map.Call("put", key, GetJavaArrayListFromCollection(entry.Value)); + } + else + { + map.Call("put", key, GetAndroidObjectFromBasicObject(entry.Value)); + } + } + } + return map; + } + + private static IDictionary ConvertIfPoss(object o) + { + var stringDict = o as IDictionary; + if (stringDict != null) + { + return stringDict; + } + + var dict = o as IDictionary; + + if (dict != null) + { + stringDict = new Dictionary(); + foreach (var key in dict.Keys) + { + stringDict[key?.ToString() ?? ""] = dict[key]; + } + return stringDict; + } + return null; + } + + private static bool IsListOrArray(object theObject) + { + var oType = theObject.GetType(); + return (oType.IsGenericType && oType.GetGenericTypeDefinition() == typeof(List<>)) || oType.IsArray; + } + + private static AndroidJavaObject GetJavaArrayListFromCollection(object theObject) + { + var collection = (IEnumerable)theObject; + var arrayList = new AndroidJavaObject("java.util.ArrayList"); + foreach (var item in collection) + { + arrayList.Call("add", GetAndroidObjectFromBasicObject(item)); + } + return arrayList; + } + + private static AndroidJavaObject GetAndroidObjectFromBasicObject(object theObject) + { + if (theObject == null) + { + return null; + } + string nativeClass; + if (theObject.GetType() == typeof(string)) + { + nativeClass = "java.lang.String"; + } + else if (theObject.GetType() == typeof(bool)) + { + nativeClass = "java.lang.Boolean"; + } + else if (theObject.GetType() == typeof(int)) + { + nativeClass = "java.lang.Integer"; + } + else if (theObject.GetType() == typeof(double)) + { + nativeClass = "java.lang.Double"; + } + else if (theObject.GetType() == typeof(float)) + { + nativeClass = "java.lang.Float"; + } + else if (theObject.GetType() == typeof(long)) + { + nativeClass = "java.lang.Long"; + } + else + { + return new AndroidJavaObject("java.lang.String", theObject.ToString()); + } + + return new AndroidJavaObject(nativeClass, theObject); + } public void LeaveBreadcrumb(string name, string type, IDictionary metadata) @@ -665,7 +786,7 @@ public void LeaveBreadcrumb(string name, string type, IDictionary src) - { - AndroidJavaObject map = new AndroidJavaObject("java.util.HashMap"); - if (src != null) - { - foreach (var entry in src) - { - using (AndroidJavaObject key = new AndroidJavaObject("java.lang.String", entry.Key)) - using (AndroidJavaObject value = new AndroidJavaObject("java.lang.String", entry.Value == null ? "null" : entry.Value.ToString())) - { - map.Call("put", key, value); - } - } - } - return map; - } - internal static bool IsUnity2019OrNewer() { using (AndroidJavaClass CharsetClass = new AndroidJavaClass("java.nio.charset.Charset")) diff --git a/src/BugsnagUnity/Native/Android/NativePayloadClassWrapper.cs b/src/BugsnagUnity/Native/Android/NativePayloadClassWrapper.cs index f5830064b..c85a6814e 100644 --- a/src/BugsnagUnity/Native/Android/NativePayloadClassWrapper.cs +++ b/src/BugsnagUnity/Native/Android/NativePayloadClassWrapper.cs @@ -237,7 +237,7 @@ public Dictionary GetNativeMetadataSection(string key, string se public void SetNativeDictionary(string key,IDictionary dict) { - using (var map = NativeInterface.BuildJavaMapDisposable(dict)) + using (var map = NativeInterface.DictionaryToJavaMap(dict)) { NativePointer.Call(key,map); } @@ -245,7 +245,7 @@ public void SetNativeDictionary(string key,IDictionary dict) public void SetNativeMetadataSection(string key, string section, IDictionary dict) { - using (var map = NativeInterface.BuildJavaMapDisposable(dict)) + using (var map = NativeInterface.DictionaryToJavaMap(dict)) { NativePointer.Call(key, section, map); } diff --git a/src/BugsnagUnity/Payload/Event.cs b/src/BugsnagUnity/Payload/Event.cs index 21f7cacd2..bfde88c2b 100644 --- a/src/BugsnagUnity/Payload/Event.cs +++ b/src/BugsnagUnity/Payload/Event.cs @@ -57,6 +57,18 @@ internal Event(Dictionary serialisedPayload) var eventObject = (Dictionary)serialisedPayload["event"]; + if (eventObject["unhandled"] != null) + { + Add("unhandled", eventObject["unhandled"]); + } + if (eventObject["severity"] != null) + { + Add("severity", eventObject["severity"]); + } + if (eventObject["severityReason"] != null) + { + Add("severityReason", eventObject["severityReason"]); + } _metadata = new Metadata(); _metadata.MergeMetadata((Dictionary)eventObject["metaData"]); diff --git a/src/BugsnagUnity/Payload/Metadata.cs b/src/BugsnagUnity/Payload/Metadata.cs index 2fb7dde76..706657294 100644 --- a/src/BugsnagUnity/Payload/Metadata.cs +++ b/src/BugsnagUnity/Payload/Metadata.cs @@ -40,10 +40,7 @@ public void AddMetadata(string section, IDictionary metadataSect var value = metadataSection[key]; if (value == null) { - if (existingSection.ContainsKey(key)) - { - existingSection.Remove(key); - } + ClearMetadata(section, key); } else { diff --git a/src/BugsnagUnity/PayloadManager.cs b/src/BugsnagUnity/PayloadManager.cs index 31f811a17..178d04c56 100644 --- a/src/BugsnagUnity/PayloadManager.cs +++ b/src/BugsnagUnity/PayloadManager.cs @@ -31,17 +31,26 @@ internal PayloadManager(CacheManager cacheManager) _cacheManager = cacheManager; } - internal void AddPendingPayload(IPayload payload) + internal bool AddPendingPayload(IPayload payload) { - using (var stream = new MemoryStream()) - using (var reader = new StreamReader(stream)) - using (var writer = new StreamWriter(stream, new UTF8Encoding(false)) { AutoFlush = false }) + try { - SimpleJson.SerializeObject(payload.GetSerialisablePayload(), writer); - writer.Flush(); - stream.Position = 0; - var jsonString = reader.ReadToEnd(); - _pendingPayloads.Add(new PendingPayload(jsonString,payload.Id)); + using (var stream = new MemoryStream()) + using (var reader = new StreamReader(stream)) + using (var writer = new StreamWriter(stream, new UTF8Encoding(false)) { AutoFlush = false }) + { + SimpleJson.SerializeObject(payload.GetSerialisablePayload(), writer); + writer.Flush(); + stream.Position = 0; + var jsonString = reader.ReadToEnd(); + _pendingPayloads.Add(new PendingPayload(jsonString, payload.Id)); + } + return true; + } + catch + { + // If payload serialisation fails ignore the payload + return false; } } diff --git a/src/BugsnagUnity/SessionTracker.cs b/src/BugsnagUnity/SessionTracker.cs index ab586e576..5ee9832b7 100644 --- a/src/BugsnagUnity/SessionTracker.cs +++ b/src/BugsnagUnity/SessionTracker.cs @@ -101,8 +101,10 @@ public void StartManagedSession() if (Client.Configuration.Endpoints.IsValid) { var payload = new SessionReport(Client.Configuration, session); - Client.PayloadManager.AddPendingPayload(payload); - Client.Send(payload); + if (Client.PayloadManager.AddPendingPayload(payload)) + { + Client.Send(payload); + } } else {