Skip to content

Commit

Permalink
Version 3.4.0 - attachment and background thread support, prevent fal…
Browse files Browse the repository at this point in the history
…se-positive anrs (#69)

* Background thread support (#67)

* Background thread support

* Fixed exception flow

* Removed renamed file

* Detect false-positive ANRs report and prevent ANR watchdog from sending them (#66)

* Detect false-positive ANRs report and prevent ANR watchdog from sending them + anr thread improvements

* Fixed typos + better documentation - added Samy's advices

* Attachment support (#68)

* Attachment support on Android - iOS shouldn't compile

* Updated the latest version of backtrace-android libraries and also adjust iOS integration code - code should now compile on iOS

* Native attachment support

* Attachment improvements

* Updated label

* Native client updates

* Android libraries

* Version update

* 3.4.0-rc1

* Prevents from reading empty attachments

* Arabic language support (#70)

* Fixed invalid date in log manager when calendar is unsupported

* Use single method to generate UTC timestamps + move it from extension code to static class to avoid unnecessary allocation

* Fixed line endings

* Updated native library

* Removed debug logs from native library

* Updated library version

* Added invariant culture to rest of the code

* native library update

* Fixed issue in the readme file

* Correct attachment name (#73)

* Correct attachment name

* Moved constants to const variables

* Prepare for final release

* Fixed typo in function name

* Changelog change
  • Loading branch information
konraddysput authored Apr 6, 2021
1 parent f126a8f commit ebe06b3
Show file tree
Hide file tree
Showing 39 changed files with 1,117 additions and 577 deletions.
Binary file modified Android/lib/arm64-v8a/libbacktrace-native.so
Binary file not shown.
Binary file modified Android/lib/arm64-v8a/libcrashpad_handler.so
Binary file not shown.
Binary file modified Android/lib/armeabi-v7a/libbacktrace-native.so
Binary file not shown.
Binary file modified Android/lib/armeabi-v7a/libcrashpad_handler.so
Binary file not shown.
Binary file modified Android/lib/x86/libbacktrace-native.so
Binary file not shown.
89 changes: 60 additions & 29 deletions CHANGELOG.md

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion Editor/BacktraceConfigurationEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ public override void OnInspectorGUI()
serializedObject.FindProperty("SendUnhandledGameCrashesOnGameStartup"),
new GUIContent(BacktraceConfigurationLabels.LABEL_SEND_UNHANDLED_GAME_CRASHES_ON_STARTUP));
#endif

EditorGUILayout.PropertyField(
serializedObject.FindProperty("ReportFilterType"),
new GUIContent(BacktraceConfigurationLabels.LABEL_REPORT_FILTER));
Expand Down Expand Up @@ -93,6 +92,13 @@ public override void OnInspectorGUI()
EditorGUILayout.HelpBox("Please insert value greater or equal -1", MessageType.Error);
}
}

#if UNITY_ANDROID || UNITY_IOS
EditorGUILayout.PropertyField(
serializedObject.FindProperty("AttachmentPaths"),
new GUIContent(BacktraceConfigurationLabels.LABEL_REPORT_ATTACHMENTS));
#endif

#if !UNITY_SWITCH
SerializedProperty enabled = serializedObject.FindProperty("Enabled");
EditorGUILayout.PropertyField(enabled, new GUIContent(BacktraceConfigurationLabels.LABEL_ENABLE_DATABASE));
Expand Down
1 change: 1 addition & 0 deletions Editor/BacktraceConfigurationLabels.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ internal static class BacktraceConfigurationLabels
"(Early access) Send Out of memory exceptions to Backtrace";
#endif
#endif
internal static string LABEL_REPORT_ATTACHMENTS = "Report attachment paths";
internal static string CAPTURE_NATIVE_CRASHES = "Capture native crashes";
internal static string LABEL_REPORT_FILTER = "Filter reports";
internal static string LABEL_NUMBER_OF_LOGS = "Collect last n game logs";
Expand Down
111 changes: 63 additions & 48 deletions README.md

Large diffs are not rendered by default.

142 changes: 133 additions & 9 deletions Runtime/BacktraceClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Threading;
using UnityEngine;

[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Backtrace.Unity.Tests.Runtime")]
Expand All @@ -20,14 +22,21 @@ public class BacktraceClient : MonoBehaviour, IBacktraceClient
{
public BacktraceConfiguration Configuration;

public const string VERSION = "3.3.3";
public const string VERSION = "3.4.0";
public bool Enabled { get; private set; }

/// <summary>
/// Client attributes
/// </summary>
private readonly Dictionary<string, string> _clientAttributes = new Dictionary<string, string>();

internal readonly Stack<BacktraceReport> BackgroundExceptions = new Stack<BacktraceReport>();

/// <summary>
/// Client report attachments
/// </summary>
private List<string> _clientReportAttachments;

/// <summary>
/// Attribute object accessor
/// </summary>
Expand All @@ -47,6 +56,26 @@ public string this[string index]
}
}

/// <summary>
/// Add attachment to managed reports.
/// Note: this option won't add attachment to your native reports. You can add attachments to
/// native reports only on BacktraceClient initialization.
/// </summary>
/// <param name="pathToAttachment">Path to attachment</param>
public void AddAttachment(string pathToAttachment)
{
_clientReportAttachments.Add(pathToAttachment);
}

/// <summary>
/// Returns list of defined path to attachments stored by Backtrace client.
/// </summary>
/// <returns>List of client attachments</returns>
public List<string> GetAttachments()
{
return _clientReportAttachments;
}

/// <summary>
/// Set client attributes that will be included in every report
/// </summary>
Expand Down Expand Up @@ -268,6 +297,7 @@ internal ReportLimitWatcher ReportLimitWatcher
/// </summary>
/// <param name="configuration">Backtrace configuration scriptable object</param>
/// <param name="attributes">Client side attributes</param>
/// param name="attachments">List of attachments </param>
/// <param name="gameObjectName">game object name</param>
/// <returns>Backtrace client</returns>
public static BacktraceClient Initialize(BacktraceConfiguration configuration, Dictionary<string, string> attributes = null, string gameObjectName = "BacktraceClient")
Expand Down Expand Up @@ -310,9 +340,24 @@ public static BacktraceClient Initialize(BacktraceConfiguration configuration, D
/// <param name="gameObjectName">game object name</param>
/// <returns>Backtrace client</returns>
public static BacktraceClient Initialize(string url, string databasePath, Dictionary<string, string> attributes = null, string gameObjectName = "BacktraceClient")
{
return Initialize(url, databasePath, attributes, null, gameObjectName);
}

/// <summary>
/// Initialize new Backtrace integration with database path. Note - database path will be auto created by Backtrace Unity plugin
/// </summary>
/// <param name="url">Server url</param>
/// <param name="databasePath">Database path</param>
/// <param name="attributes">Client side attributes</param>
/// <param name="attachments">Paths to attachments that Backtrace client will include in managed/native reports</param>
/// <param name="gameObjectName">game object name</param>
/// <returns>Backtrace client</returns>
public static BacktraceClient Initialize(string url, string databasePath, Dictionary<string, string> attributes = null, string[] attachments = null, string gameObjectName = "BacktraceClient")
{
var configuration = ScriptableObject.CreateInstance<BacktraceConfiguration>();
configuration.ServerUrl = url;
configuration.AttachmentPaths = attachments;
configuration.Enabled = true;
configuration.DatabasePath = databasePath;
configuration.CreateDatabase = true;
Expand All @@ -327,9 +372,23 @@ public static BacktraceClient Initialize(string url, string databasePath, Dictio
/// <param name="gameObjectName">game object name</param>
/// <returns>Backtrace client</returns>
public static BacktraceClient Initialize(string url, Dictionary<string, string> attributes = null, string gameObjectName = "BacktraceClient")
{
return Initialize(url, attributes, new string[0], gameObjectName);
}

/// <summary>
/// Initialize new Backtrace integration
/// </summary>
/// <param name="url">Server url</param>
/// <param name="attributes">Client side attributes</param>
/// <param name="attachments">Paths to attachments that Backtrace client will include in managed/native reports</param>
/// <param name="gameObjectName">game object name</param>
/// <returns>Backtrace client</returns>
public static BacktraceClient Initialize(string url, Dictionary<string, string> attributes = null, string[] attachments = null, string gameObjectName = "BacktraceClient")
{
var configuration = ScriptableObject.CreateInstance<BacktraceConfiguration>();
configuration.ServerUrl = url;
configuration.AttachmentPaths = attachments;
configuration.Enabled = false;
return Initialize(configuration, attributes, gameObjectName);
}
Expand All @@ -352,10 +411,10 @@ public void Refresh()
}

Enabled = true;

_current = Thread.CurrentThread;
CaptureUnityMessages();
_reportLimitWatcher = new ReportLimitWatcher(Convert.ToUInt32(Configuration.ReportPerMin));

_clientReportAttachments = Configuration.GetAttachmentPaths();

BacktraceApi = new BacktraceApi(
credentials: new BacktraceCredentials(Configuration.GetValidServerUrl()),
Expand Down Expand Up @@ -413,20 +472,32 @@ private void Awake()
/// <summary>
/// Update native client internal ANR timer.
/// </summary>
private void Update()
private void LateUpdate()
{
_nativeClient?.UpdateClientTime(Time.unscaledTime);

if (BackgroundExceptions.Count == 0)
{
return;
}
while (BackgroundExceptions.Count > 0)
{
// use SendReport method isntead of Send method
// because we already applied all watchdog/skipReport rules
// so we don't need to apply them once again
SendReport(BackgroundExceptions.Pop());
}
}

private void OnDestroy()
{
Enabled = false;
Application.logMessageReceived -= HandleUnityMessage;
Application.logMessageReceivedThreaded -= HandleUnityBackgroundException;
#if UNITY_ANDROID || UNITY_IOS
Application.lowMemory -= HandleLowMemory;
_nativeClient?.Disable();
#endif

}

/// <summary>
Expand Down Expand Up @@ -606,7 +677,7 @@ record = Database.Add(data);

if (data.Deduplication != 0)
{
queryAttributes["_mod_duplicate"] = data.Deduplication.ToString();
queryAttributes["_mod_duplicate"] = data.Deduplication.ToString(CultureInfo.InvariantCulture);
}

StartCoroutine(BacktraceApi.Send(json, data.Attachments, queryAttributes, (BacktraceResult result) =>
Expand Down Expand Up @@ -653,6 +724,7 @@ private BacktraceData SetupBacktraceData(BacktraceReport report)
: _backtraceLogManager.ToSourceCode();

report.AssignSourceCodeToReport(sourceCode);
report.AttachmentPaths.AddRange(_clientReportAttachments);

// pass copy of dictionary to prevent overriding client attributes
var result = report.ToBacktraceData(null, GameObjectDepth);
Expand Down Expand Up @@ -691,6 +763,8 @@ internal void OnAnrDetected(string stackTrace)
}
#endif

private Thread _current;

/// <summary>
/// Handle Unity unhandled exceptions
/// </summary>
Expand All @@ -700,12 +774,29 @@ private void CaptureUnityMessages()
if (Configuration.HandleUnhandledExceptions || Configuration.NumberOfLogs != 0)
{
Application.logMessageReceived += HandleUnityMessage;
Application.logMessageReceivedThreaded += HandleUnityBackgroundException;
#if UNITY_ANDROID || UNITY_IOS
Application.lowMemory += HandleLowMemory;
#endif
}
}

internal void OnApplicationPause(bool pause)
{
_nativeClient?.PauseAnrThread(pause);
}

internal void HandleUnityBackgroundException(string message, string stackTrace, LogType type)
{
// validate if a message is from main thread
// and skip messages from main thread
if (Thread.CurrentThread == _current)
{
return;
}
HandleUnityMessage(message, stackTrace, type);
}

#if UNITY_ANDROID || UNITY_IOS
internal void HandleLowMemory()
{
Expand Down Expand Up @@ -824,10 +915,22 @@ private bool ShouldSendReport(Exception exception, List<string> attachmentPaths,
{
return false;
}

//check rate limiting
bool shouldProcess = _reportLimitWatcher.WatchReport(new DateTime().Timestamp());
bool shouldProcess = _reportLimitWatcher.WatchReport(DateTimeHelper.Timestamp());
if (shouldProcess)
{
// This condition checks if we should send exception from current thread
// if comparision result confirm that we're trying to send an exception from different
// thread than main, we should add the exception object to the exception list
// and let update method send data to Backtrace.
if (Thread.CurrentThread.ManagedThreadId != _current.ManagedThreadId)
{
var report = new BacktraceReport(exception, attributes, attachmentPaths);
report.Attributes["exception.thread"] = Thread.CurrentThread.ManagedThreadId.ToString(CultureInfo.InvariantCulture);
BackgroundExceptions.Push(report);
return false;
}
return true;
}
if (OnClientReportLimitReached != null)
Expand All @@ -849,9 +952,20 @@ private bool ShouldSendReport(string message, List<string> attachmentPaths, Dict
}

//check rate limiting
bool shouldProcess = _reportLimitWatcher.WatchReport(new DateTime().Timestamp());
bool shouldProcess = _reportLimitWatcher.WatchReport(DateTimeHelper.Timestamp());
if (shouldProcess)
{
// This condition checks if we should send exception from current thread
// if comparision result confirm that we're trying to send an exception from different
// thread than main, we should add the exception object to the exception list
// and let update method send data to Backtrace.
if (Thread.CurrentThread.ManagedThreadId != _current.ManagedThreadId)
{
var report = new BacktraceReport(message, attributes, attachmentPaths);
report.Attributes["exception.thread"] = Thread.CurrentThread.ManagedThreadId.ToString(CultureInfo.InvariantCulture);
BackgroundExceptions.Push(report);
return false;
}
return true;
}
if (OnClientReportLimitReached != null)
Expand All @@ -877,9 +991,19 @@ private bool ShouldSendReport(BacktraceReport report)
return false;
}
//check rate limiting
bool shouldProcess = _reportLimitWatcher.WatchReport(new DateTime().Timestamp());
bool shouldProcess = _reportLimitWatcher.WatchReport(DateTimeHelper.Timestamp());
if (shouldProcess)
{
// This condition checks if we should send exception from current thread
// if comparision result confirm that we're trying to send an exception from different
// thread than main, we should add the exception object to the exception list
// and let update method send data to Backtrace.
if (Thread.CurrentThread.ManagedThreadId != _current.ManagedThreadId)
{
report.Attributes["exception.thread"] = Thread.CurrentThread.ManagedThreadId.ToString(CultureInfo.InvariantCulture);
BackgroundExceptions.Push(report);
return false;
}
return true;
}
if (OnClientReportLimitReached != null)
Expand Down
7 changes: 4 additions & 3 deletions Runtime/BacktraceDatabase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Backtrace.Unity.Types;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using UnityEngine;

Expand Down Expand Up @@ -372,7 +373,7 @@ private void FlushRecord(BacktraceDatabaseRecord record)
return;
}

queryAttributes["_mod_duplicate"] = record.Count.ToString();
queryAttributes["_mod_duplicate"] = record.Count.ToString(CultureInfo.InvariantCulture);

StartCoroutine(
BacktraceApi.Send(backtraceData, record.Attachments, queryAttributes, (BacktraceResult result) =>
Expand Down Expand Up @@ -403,7 +404,7 @@ private void SendData(BacktraceDatabaseRecord record)
stopWatch.Stop();
queryAttributes["performance.database.send"] = stopWatch.GetMicroseconds();
}
queryAttributes["_mod_duplicate"] = record.Count.ToString();
queryAttributes["_mod_duplicate"] = record.Count.ToString(CultureInfo.InvariantCulture);

StartCoroutine(
BacktraceApi.Send(backtraceData, record.Attachments, queryAttributes, (BacktraceResult sendResult) =>
Expand All @@ -418,7 +419,7 @@ private void SendData(BacktraceDatabaseRecord record)
BacktraceDatabaseContext.IncrementBatchRetry();
return;
}
bool shouldProcess = _reportLimitWatcher.WatchReport(new DateTime().Timestamp());
bool shouldProcess = _reportLimitWatcher.WatchReport(DateTimeHelper.Timestamp());
if (!shouldProcess)
{
return;
Expand Down
Loading

0 comments on commit ebe06b3

Please sign in to comment.