Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- [Setup <a name="installation"></a>](#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)
Expand Down Expand Up @@ -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<BacktraceClient>();
// 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
Expand Down
20 changes: 19 additions & 1 deletion Runtime/BacktraceClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -319,6 +325,13 @@ private void Awake()
Refresh();
}

private void OnDestroy()
{
Debug.Log("Disabling Backtrace integration");
Enabled = false;
Application.logMessageReceived -= HandleUnityMessage;
}

/// <summary>
/// Change maximum number of reportrs sending per one minute
/// </summary>
Expand Down Expand Up @@ -552,6 +565,11 @@ private BacktraceData SetupBacktraceData(BacktraceReport report)
/// <param name="stackTrace">Main thread stack trace</param>
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);
Expand Down
2 changes: 1 addition & 1 deletion Runtime/Model/BacktraceData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public class BacktraceData
/// <summary>
/// Version of the C# library
/// </summary>
public const string AgentVersion = "3.2.0";
public const string AgentVersion = "3.2.1";

/// <summary>
/// Application thread details
Expand Down
9 changes: 8 additions & 1 deletion Runtime/Model/BacktraceResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<BacktraceRawResult>(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;
}
Expand Down
4 changes: 2 additions & 2 deletions Runtime/Model/BacktraceUnhandledException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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")
{
Expand Down
23 changes: 16 additions & 7 deletions Runtime/Native/Android/NativeClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -78,15 +85,19 @@ private void HandleNativeCrashes()
int apiLevel = version.GetStatic<int>("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
Expand All @@ -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");
}
}

Expand Down Expand Up @@ -174,12 +185,10 @@ public void HandleAnr(string gameObjectName, string callbackName)
/// <param name="value">Attribute value</param>
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)
{
Expand Down
14 changes: 9 additions & 5 deletions Runtime/Services/BacktraceApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,14 @@ public IEnumerator SendMinidump(string minidumpPath, IEnumerable<string> attachm
? System.Diagnostics.Stopwatch.StartNew()
: new System.Diagnostics.Stopwatch();

var minidumpBytes = File.ReadAllBytes(minidumpPath);
if (minidumpBytes == null || minidumpBytes.Length == 0)
{
yield break;
}
List<IMultipartFormSection> formData = new List<IMultipartFormSection>
{
new MultipartFormFileSection("upload_file", File.ReadAllBytes(minidumpPath))
new MultipartFormFileSection("upload_file", minidumpBytes)
};

foreach (var file in attachments)
Expand Down Expand Up @@ -224,7 +229,7 @@ public IEnumerator Send(string json, List<string> attachments, Dictionary<string
yield return request.SendWebRequest();

BacktraceResult result;
if (request.responseCode == 200)
if (request.responseCode == 200 && (!request.isNetworkError || !request.isHttpError))
{
result = BacktraceResult.FromJson(request.downloadHandler.text);

Expand Down Expand Up @@ -265,10 +270,9 @@ public IEnumerator Send(string json, List<string> attachments, Dictionary<string

private void PrintLog(UnityWebRequest request)
{
string responseText = Encoding.UTF8.GetString(request.downloadHandler.data);
Debug.LogWarning(string.Format("{0}{1}", string.Format("[Backtrace]::Reponse code: {0}, Response text: {1}",
request.responseCode,
responseText),
request.downloadHandler.text),
"\n Please check provided url to Backtrace service or learn more from our integration guide: https://help.backtrace.io/integration-guides/game-engines/unity-integration-guide"));
}

Expand Down Expand Up @@ -313,7 +317,7 @@ private string GetAttachmentUploadUrl(string rxId, string attachmentName)

}

// private static readonly string reservedCharacters = "!*'();:@&=+$,/?%#[]";
// private static readonly string reservedCharacters = "!*'();:@&=+$,/?%#[]";

public static string GetParametrizedQuery(string serverUrl, Dictionary<string, string> queryAttributes)
{
Expand Down
23 changes: 20 additions & 3 deletions Tests/Runtime/BacktraceStackTraceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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);
}
}
}

Expand Down Expand Up @@ -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; }
Expand Down Expand Up @@ -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);
}
Expand Down
5 changes: 5 additions & 0 deletions Tests/Runtime/ClientSendTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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": [
Expand Down