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:
+
+ - Handled errors and exceptions: Events which can be handled gracefully can also be reported to BugSnag
+ - Crashes: Events which terminate the app are sent to Bugsnag automatically. Reopen the app after a crash to send reports.
+ - Android specific crashes: Native, segfaults, C++, JVM & ANR exceptions. Reopen the app after a crash to send reports
+ - Cocoa sepecific crashes: Native, C++, OOM & App Hangs. Reopen the app after a crash to send reports
+
+
+
+## Running the app
+
+
+ - Clone the repo
+```
+git clone git@github.com:bugsnag/bugsnag-unity.git --recursive
+```
+ - Once the project has opened in Unity
+ - Navigate to `Windows>Bugsnag>Configuration`
+ - Enter your Bugsnag project API Key under the basic Configuration section of the package window
+ - Press `Play` in the editor
+ - Once loaded, click any of the error buttons
+ - Confirm whether the BugSnag dashboard has received the error.
+
+
+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
{