diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3b00f9ff..9e0897fd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
# Backtrace Unity Release Notes
+## Version 3.2.1
+- Android stack trace parser improvements,
+- Fixed Android NDK initialization when database directory doesn't exist,
+- Added Privacy section to Readme
+
+
## Version 3.2.0
- This release adds the ability to capture native iOS crashes from Unity games deployed to iOS. The Backtrace Configuration now exposes a setting for games being prepared for iOS to choose `Capture native crashes`. When enabled, the backtrace-unity client will capture and submit native iOS crashes to the configured Backtrace instance. To generate human readable callstacks, game programmers will need to generate and upload appropriate debug symbols.
- Added default uname.sysname attributes for some platforms. The following is the list of uname.sysname platforms that can be populated. list "Android, IOS, Linux, Mac OS, ps3, ps4, Samsung TV, tvOS, WebGL, WiiU, Switch, Xbox". Note 'Switch' had previously been reported as 'switch'
diff --git a/README.md b/README.md
index a90a1cb7..d7be6de3 100644
--- a/README.md
+++ b/README.md
@@ -11,6 +11,7 @@
- [Setup ](#setup--a-name--installation----a-)
- [Android Specific information](#android-specific-information)
- [iOS Specific information](#ios-specific-information)
+- [Data Privacy](#data-privacy)
- [API Overview](#api-overview)
- [Architecture description](#architecture-description)
- [Investigating an Error in Backtrace](#investigating-an-error-in-backtrace)
@@ -175,6 +176,41 @@ This change will generate dSYM files every time you build your game in Xcode. Yo
To learn more about how to submit those symbol files to Backtrace, please see the Project Settings / Symbols. You can manage submission tokens, upload via the UI, or configure external Symbol Servers to connect and discover required symbols. Please review additional Symbol documentaion at https://support.backtrace.io/hc/en-us/articles/360040517071-Symbolication-Overview
+# Data Privacy
+
+Backtrace-Unity allows developers to remove and modify data that the library collects when an exception occurs using the following methods:
+
+* BeforeSend event
+The library will fire an event every time an exception in the managed environment occurs. The BeforeEvent trigger allows you to skip the report (you can do that by returning null value) or to modify data that library collected before sending the report. BeforeSend event might be useful in case if you would like to extend attributes or json object data based on data that application has at the time of exception.
+
+Example code:
+
+```csharp
+//Read from manager BacktraceClient instance
+var backtraceClient = GameObject.Find("manager name").GetComponent();
+// set beforeSend event
+_backtraceClient.BeforeSend = (BacktraceData data) =>
+{
+ data.Attributes.Attributes["my-dynamic-attribute"] = "value";
+ return data;
+};
+```
+
+* Environment Variable Management
+The `Annotations` class exposes the EnvironmentVariableCache dictionary - a dictionary that stores environment variables collected by the library. You can manipulate the data in this cache before the report is sent. For example - to replace the`USERNAME` environment variable collected by Backtrace library with random string you can easily edit annotations environment varaible and Backtrace-Untiy will reuse them on report creation.
+
+```csharp
+Annotations.EnvironmentVariablesCache["USERNAME"] = "%USERNAME%";
+```
+
+Also you can still use BeforeSend event to edit collected diagnostic data:
+```csharp
+ client.BeforeSend = (BacktraceData data) =>
+ {
+ data.Annotation.EnvironmentVariables["USERNAME"] = "%USERNAME%";
+ return data;
+ }
+```
# API Overview
diff --git a/Runtime/BacktraceClient.cs b/Runtime/BacktraceClient.cs
index 6b6f4a8b..48b4883e 100644
--- a/Runtime/BacktraceClient.cs
+++ b/Runtime/BacktraceClient.cs
@@ -305,7 +305,13 @@ public void Refresh()
}
_nativeClient = NativeClientFactory.GetNativeClient(Configuration, name);
-
+ if (_nativeClient != null)
+ {
+ foreach (var attribute in _clientAttributes)
+ {
+ _nativeClient.SetAttribute(attribute.Key, attribute.Value);
+ }
+ }
if (Configuration.SendUnhandledGameCrashesOnGameStartup && isActiveAndEnabled)
{
var nativeCrashUplaoder = new NativeCrashUploader();
@@ -319,6 +325,13 @@ private void Awake()
Refresh();
}
+ private void OnDestroy()
+ {
+ Debug.Log("Disabling Backtrace integration");
+ Enabled = false;
+ Application.logMessageReceived -= HandleUnityMessage;
+ }
+
///
/// Change maximum number of reportrs sending per one minute
///
@@ -552,6 +565,11 @@ private BacktraceData SetupBacktraceData(BacktraceReport report)
/// Main thread stack trace
internal void OnAnrDetected(string stackTrace)
{
+ if (!Enabled)
+ {
+ Debug.LogWarning("Please enable BacktraceClient first - Please validate Backtrace client initializaiton in Unity IDE.");
+ return;
+ }
const string anrMessage = "ANRException: Blocked thread detected";
_backtraceLogManager.Enqueue(new BacktraceUnityMessage(anrMessage, stackTrace, LogType.Error));
var hang = new BacktraceUnhandledException(anrMessage, stackTrace);
diff --git a/Runtime/Model/BacktraceData.cs b/Runtime/Model/BacktraceData.cs
index 0d670e06..e7edf398 100644
--- a/Runtime/Model/BacktraceData.cs
+++ b/Runtime/Model/BacktraceData.cs
@@ -45,7 +45,7 @@ public class BacktraceData
///
/// Version of the C# library
///
- public const string AgentVersion = "3.2.0";
+ public const string AgentVersion = "3.2.1";
///
/// Application thread details
diff --git a/Runtime/Model/BacktraceResult.cs b/Runtime/Model/BacktraceResult.cs
index 51a4d9b9..e593155d 100644
--- a/Runtime/Model/BacktraceResult.cs
+++ b/Runtime/Model/BacktraceResult.cs
@@ -114,12 +114,19 @@ 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 = rawResult.response == "ok" ? BacktraceResultStatus.Ok : BacktraceResultStatus.ServerError
};
return result;
}
diff --git a/Runtime/Model/BacktraceUnhandledException.cs b/Runtime/Model/BacktraceUnhandledException.cs
index 0388c690..79867c45 100644
--- a/Runtime/Model/BacktraceUnhandledException.cs
+++ b/Runtime/Model/BacktraceUnhandledException.cs
@@ -241,7 +241,7 @@ private BacktraceStackFrame SetNativeStackTraceInformation(string frameString)
var sourceCodeParts = sourceCodeInformation.Split(':');
if (sourceCodeParts.Length == 2)
{
- stackFrame.Line = int.Parse(sourceCodeParts[1]);
+ int.TryParse(sourceCodeParts[1], out stackFrame.Line);
stackFrame.Library = sourceCodeParts[0];
stackFrame.FunctionName = stackFrame.FunctionName.Substring(sourceCodeEndIndex + 2);
}
@@ -272,7 +272,7 @@ private BacktraceStackFrame SetAndroidStackTraceInformation(string frameString)
if (sourceCodeInformation.Length == 2)
{
stackFrame.Library = sourceCodeInformation[0];
- stackFrame.Line = int.Parse(sourceCodeInformation[1]);
+ int.TryParse(sourceCodeInformation[1], out stackFrame.Line);
}
else if (frameString.StartsWith("java.lang") || possibleSourceCodeInformation == "Unknown Source")
{
diff --git a/Runtime/Native/Android/NativeClient.cs b/Runtime/Native/Android/NativeClient.cs
index 6c110e63..6077b629 100644
--- a/Runtime/Native/Android/NativeClient.cs
+++ b/Runtime/Native/Android/NativeClient.cs
@@ -65,8 +65,15 @@ public NativeClient(string gameObjectName, BacktraceConfiguration configuration)
private void HandleNativeCrashes()
{
// make sure database is enabled
- if (!_captureNativeCrashes)
+ if (!_captureNativeCrashes || !_configuration.Enabled)
+ {
+ Debug.LogWarning("Backtrace native integration status: Disabled NDK integration");
+ return;
+ }
+ var databasePath = _configuration.CrashpadDatabasePath;
+ if (string.IsNullOrEmpty(databasePath) || !Directory.Exists(databasePath))
{
+ Debug.LogWarning("Backtrace native integration status: database path doesn't exist");
return;
}
@@ -78,15 +85,19 @@ private void HandleNativeCrashes()
int apiLevel = version.GetStatic("SDK_INT");
if (apiLevel < 21)
{
- Debug.LogWarning("Crashpad integration status: Unsupported Android API level");
+ Debug.LogWarning("Backtrace native integration status: Unsupported Android API level");
return;
}
}
var libDirectory = Path.Combine(Path.GetDirectoryName(Application.dataPath), "lib");
+ if (!Directory.Exists(libDirectory))
+ {
+ return;
+ }
var crashpadHandlerPath = Directory.GetFiles(libDirectory, "libcrashpad_handler.so", SearchOption.AllDirectories).FirstOrDefault();
if (string.IsNullOrEmpty(crashpadHandlerPath))
{
- Debug.LogWarning("Crashpad integration status: Cannot find crashpad library");
+ Debug.LogWarning("Backtrace native integration status: Cannot find crashpad library");
return;
}
// get default built-in Backtrace-Unity attributes
@@ -101,13 +112,13 @@ private void HandleNativeCrashes()
// isn't available
_captureNativeCrashes = Initialize(
AndroidJNI.NewStringUTF(minidumpUrl),
- AndroidJNI.NewStringUTF(_configuration.CrashpadDatabasePath),
+ AndroidJNI.NewStringUTF(databasePath),
AndroidJNI.NewStringUTF(crashpadHandlerPath),
AndroidJNIHelper.ConvertToJNIArray(backtraceAttributes.Attributes.Keys.ToArray()),
AndroidJNIHelper.ConvertToJNIArray(backtraceAttributes.Attributes.Values.ToArray()));
if (!_captureNativeCrashes)
{
- Debug.LogWarning("Crashpad integration status: Cannot initialize Crashpad client");
+ Debug.LogWarning("Backtrace native integration status: Cannot initialize Crashpad client");
}
}
@@ -174,12 +185,10 @@ public void HandleAnr(string gameObjectName, string callbackName)
/// Attribute value
public void SetAttribute(string key, string value)
{
- Debug.Log($"Adding attribute to crashpad");
if (!_captureNativeCrashes || string.IsNullOrEmpty(key))
{
return;
}
- Debug.Log($"Adding attribute to crashpad. {key} {value}");
// avoid null reference in crashpad source code
if (value == null)
{
diff --git a/Runtime/Services/BacktraceApi.cs b/Runtime/Services/BacktraceApi.cs
index a2cb308f..d6975dda 100644
--- a/Runtime/Services/BacktraceApi.cs
+++ b/Runtime/Services/BacktraceApi.cs
@@ -103,9 +103,14 @@ public IEnumerator SendMinidump(string minidumpPath, IEnumerable attachm
? System.Diagnostics.Stopwatch.StartNew()
: new System.Diagnostics.Stopwatch();
+ var minidumpBytes = File.ReadAllBytes(minidumpPath);
+ if (minidumpBytes == null || minidumpBytes.Length == 0)
+ {
+ yield break;
+ }
List formData = new List
{
- new MultipartFormFileSection("upload_file", File.ReadAllBytes(minidumpPath))
+ new MultipartFormFileSection("upload_file", minidumpBytes)
};
foreach (var file in attachments)
@@ -224,7 +229,7 @@ public IEnumerator Send(string json, List attachments, Dictionary attachments, Dictionary queryAttributes)
{
diff --git a/Tests/Runtime/BacktraceStackTraceTests.cs b/Tests/Runtime/BacktraceStackTraceTests.cs
index d2521804..c362cc6d 100644
--- a/Tests/Runtime/BacktraceStackTraceTests.cs
+++ b/Tests/Runtime/BacktraceStackTraceTests.cs
@@ -165,6 +165,11 @@ public class BacktraceStackTraceTests
FunctionName ="backtrace.io.backtrace_unity_android_plugin.BacktraceCrashHelper.InternalCall",
Library = "BacktraceCrashHelper.java",
Line = 31
+ },
+ new SampleStackFrame() {
+ Type = StackTraceType.Android,
+ FunctionName = "com.google.android.gms.ads.internal.webview.ac.loadUrl",
+ Custom = "com.google.android.gms.ads.internal.webview.ac.loadUrl(:com.google.android.gms.policy_ads_fdr_dynamite@204102000@204102000000.334548305.334548305:1)"
},
// android unknown source
new SampleStackFrame() {
@@ -324,9 +329,16 @@ public void TestStackTraceCreation_AndroidMixModeCallStack_ValidStackTraceObject
{
var mixModeCallStack = _mixModeCallStack.ElementAt(i);
var backtraceStackFrame = backtraceStackTrace.StackFrames[i - startIndex];
- Assert.AreEqual(mixModeCallStack.FunctionName, backtraceStackFrame.FunctionName);
- Assert.AreEqual(mixModeCallStack.Line, backtraceStackFrame.Line);
- Assert.AreEqual(mixModeCallStack.Library, backtraceStackFrame.Library);
+ if (!string.IsNullOrEmpty(mixModeCallStack.Custom))
+ {
+ Assert.AreEqual(mixModeCallStack.FunctionName, backtraceStackFrame.FunctionName);
+ }
+ else
+ {
+ Assert.AreEqual(mixModeCallStack.FunctionName, backtraceStackFrame.FunctionName);
+ Assert.AreEqual(mixModeCallStack.Line, backtraceStackFrame.Line);
+ Assert.AreEqual(mixModeCallStack.Library, backtraceStackFrame.Library);
+ }
}
}
@@ -512,6 +524,7 @@ internal enum StackTraceType { Default, Android, Native };
internal class SampleStackFrame
{
public StackTraceType Type = StackTraceType.Default;
+ public string Custom { get; set; }
public string StackFrame { get; set; }
public string FunctionName { get; set; }
public string Library { get; set; }
@@ -543,6 +556,10 @@ private string ParseDefaultStackTrace()
public string ParseAndroidStackTrace()
{
+ if (!string.IsNullOrEmpty(Custom))
+ {
+ return string.Format("{0}\n", Custom);
+ }
var formattedLineNumber = Line != 0 ? string.Format(":{0}", Line) : string.Empty;
return string.Format("{0}({1}{2})\n", FunctionName, Library, formattedLineNumber);
}
diff --git a/Tests/Runtime/ClientSendTests.cs b/Tests/Runtime/ClientSendTests.cs
index d99c2817..31910167 100644
--- a/Tests/Runtime/ClientSendTests.cs
+++ b/Tests/Runtime/ClientSendTests.cs
@@ -182,6 +182,11 @@ public IEnumerator PiiTests_ShouldRemoveEnvironmentVariableValue_IntegrationShou
var environmentVariableKey = "USERNAME";
var expectedValue = "%USERNAME%";
+ if (!Annotations.EnvironmentVariablesCache.ContainsKey(environmentVariableKey))
+ {
+ Annotations.EnvironmentVariablesCache[environmentVariableKey] = "fake user name";
+ }
+
var defaultUserName = Annotations.EnvironmentVariablesCache[environmentVariableKey];
Annotations.EnvironmentVariablesCache[environmentVariableKey] = expectedValue;
diff --git a/package.json b/package.json
index b4a3b492..822234d9 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "io.backtrace.unity",
"displayName": "Backtrace",
- "version": "3.2.0",
+ "version": "3.2.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": [