diff --git a/Bugsnag/Assets/Bugsnag/Runtime/Client.cs b/Bugsnag/Assets/Bugsnag/Runtime/Client.cs index b06e4f33..c751840e 100644 --- a/Bugsnag/Assets/Bugsnag/Runtime/Client.cs +++ b/Bugsnag/Assets/Bugsnag/Runtime/Client.cs @@ -58,6 +58,8 @@ internal class Client : IClient private bool _isUnity2019OrHigher; + private ErrorBuilder _errorBuilder; + private class BugsnagLogHandler : ILogHandler { @@ -116,6 +118,7 @@ public Client(INativeClient nativeClient) { InitMainthreadDispatcher(); NativeClient = nativeClient; + _errorBuilder = new ErrorBuilder(nativeClient); CacheManager = new CacheManager(Configuration); PayloadManager = new PayloadManager(CacheManager); _delivery = new Delivery(this, Configuration, CacheManager, PayloadManager); @@ -316,7 +319,7 @@ private void NotifyFromUnityLog(string condition, string stackTrace, LogType log var severity = Configuration.LogTypeSeverityMapping.Map(logType); var backupStackFrames = new System.Diagnostics.StackTrace(1, true).GetFrames(); var forceUnhandled = logType == LogType.Exception && !Configuration.ReportExceptionLogsAsHandled; - var exception = Error.FromUnityLogMessage(logMessage, backupStackFrames, severity, forceUnhandled); + var exception = _errorBuilder.FromUnityLogMessage(logMessage, backupStackFrames, severity, forceUnhandled); Notify(new Error[] { exception }, exception.HandledState, null, logType); } } @@ -332,13 +335,13 @@ private void NotifyFromUnityLog(string condition, string stackTrace, LogType log public void Notify(string name, string message, string stackTrace, Func callback) { - var exceptions = new Error[] { Error.FromStringInfo(name, message, stackTrace) }; + var exceptions = new Error[] { _errorBuilder.FromStringInfo(name, message, stackTrace) }; Notify(exceptions, HandledState.ForHandledException(), callback, LogType.Exception); } public void Notify(System.Exception exception, string stacktrace, Func callback) { - var exceptions = new Errors(exception, stacktrace).ToArray(); + var exceptions = _errorBuilder.EnumerateFrom(exception, stacktrace).ToArray(); Notify(exceptions, HandledState.ForHandledException(), callback, LogType.Exception); } @@ -358,7 +361,7 @@ void Notify(System.Exception exception, HandledState handledState, Func GetNativeMetadata(); + /// + /// Find the native loaded image that corresponds to a native instruction address + /// supplied by il2cpp_native_stack_trace(). + /// + /// The address to find the corresponding image of + /// The corresponding image, or null + LoadedImage FindImageAtAddress(UInt64 address); + bool ShouldAttemptDelivery(); /// diff --git a/Bugsnag/Assets/Bugsnag/Runtime/LoadedImage.cs b/Bugsnag/Assets/Bugsnag/Runtime/LoadedImage.cs new file mode 100644 index 00000000..4941e9c9 --- /dev/null +++ b/Bugsnag/Assets/Bugsnag/Runtime/LoadedImage.cs @@ -0,0 +1,20 @@ +using System; + +namespace BugsnagUnity +{ + class LoadedImage + { + public LoadedImage(UInt64 loadAddress, UInt64 size, string fileName, string uuid) + { + LoadAddress = loadAddress; + Size = size; + FileName = fileName; + Uuid = uuid; + } + + public readonly UInt64 LoadAddress; + public readonly UInt64 Size; + public readonly string FileName; + public readonly string Uuid; + } +} diff --git a/Bugsnag/Assets/Bugsnag/Runtime/Native/Cocoa/NativeImage.cs.meta b/Bugsnag/Assets/Bugsnag/Runtime/LoadedImage.cs.meta similarity index 83% rename from Bugsnag/Assets/Bugsnag/Runtime/Native/Cocoa/NativeImage.cs.meta rename to Bugsnag/Assets/Bugsnag/Runtime/LoadedImage.cs.meta index 2375bed3..e17dc486 100644 --- a/Bugsnag/Assets/Bugsnag/Runtime/Native/Cocoa/NativeImage.cs.meta +++ b/Bugsnag/Assets/Bugsnag/Runtime/LoadedImage.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 1da73d2c4d96846a191f28f516a5ef86 +guid: 4bbdc0fb5e82c40e38d4dd44c00a1fbe MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Bugsnag/Assets/Bugsnag/Runtime/Native/Android/NativeClient.cs b/Bugsnag/Assets/Bugsnag/Runtime/Native/Android/NativeClient.cs index 5550c1a9..184d01e7 100644 --- a/Bugsnag/Assets/Bugsnag/Runtime/Native/Android/NativeClient.cs +++ b/Bugsnag/Assets/Bugsnag/Runtime/Native/Android/NativeClient.cs @@ -1,4 +1,5 @@ #if (UNITY_ANDROID && !UNITY_EDITOR) || BSG_ANDROID_DEV +using System; using System.Collections.Generic; using BugsnagUnity.Payload; using UnityEngine; @@ -162,6 +163,11 @@ public void RegisterForOnSessionCallbacks() { NativeInterface.RegisterForOnSessionCallbacks(); } + + public LoadedImage FindImageAtAddress(UInt64 address) + { + return null; + } } } diff --git a/Bugsnag/Assets/Bugsnag/Runtime/Native/Cocoa/NativeImage.cs b/Bugsnag/Assets/Bugsnag/Runtime/Native/Cocoa/LoadedImages.cs similarity index 81% rename from Bugsnag/Assets/Bugsnag/Runtime/Native/Cocoa/NativeImage.cs rename to Bugsnag/Assets/Bugsnag/Runtime/Native/Cocoa/LoadedImages.cs index 4c34ce5f..88a91f85 100644 --- a/Bugsnag/Assets/Bugsnag/Runtime/Native/Cocoa/NativeImage.cs +++ b/Bugsnag/Assets/Bugsnag/Runtime/Native/Cocoa/LoadedImages.cs @@ -5,24 +5,6 @@ namespace BugsnagUnity { - class LoadedImage - { - public LoadedImage(NativeLoadedImage image) - { - LoadAddress = image.LoadAddress; - Size = image.Size; - FileName = Marshal.PtrToStringAnsi(image.FileName); - var uuid = new byte[16]; - Marshal.Copy(image.UuidBytes, uuid, 0, 16); - Uuid = new Guid(uuid).ToString(); - } - - public UInt64 LoadAddress; - public UInt64 Size; - public string FileName; - public string Uuid; - } - class LoadedImages { /// @@ -39,7 +21,13 @@ public void Refresh() var images = new LoadedImage[count]; for (UInt64 i = 0; i < count; i++) { - images[i] = new LoadedImage(nativeImages[i]); + var nativeImage = nativeImages[i]; + var uuid = new byte[16]; + Marshal.Copy(nativeImage.UuidBytes, uuid, 0, 16); + images[i] = new LoadedImage(nativeImage.LoadAddress, + nativeImage.Size, + Marshal.PtrToStringAnsi(nativeImage.FileName), + new Guid(uuid).ToString()); } Images = images; // bugsnag_getLoadedImages() locks a mutex, so we must call bugsnag_unlockLoadedImages() diff --git a/Bugsnag/Assets/Bugsnag/Runtime/Native/Cocoa/LoadedImages.cs.meta b/Bugsnag/Assets/Bugsnag/Runtime/Native/Cocoa/LoadedImages.cs.meta new file mode 100644 index 00000000..3fa23792 --- /dev/null +++ b/Bugsnag/Assets/Bugsnag/Runtime/Native/Cocoa/LoadedImages.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a5cc8f81bd01d4dcbaa61e30e7456efc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Bugsnag/Assets/Bugsnag/Runtime/Native/Cocoa/NativeClient.cs b/Bugsnag/Assets/Bugsnag/Runtime/Native/Cocoa/NativeClient.cs index 61896bcd..e72201f8 100644 --- a/Bugsnag/Assets/Bugsnag/Runtime/Native/Cocoa/NativeClient.cs +++ b/Bugsnag/Assets/Bugsnag/Runtime/Native/Cocoa/NativeClient.cs @@ -20,6 +20,8 @@ class NativeClient : INativeClient private static NativeClient _instance; private bool _registeredForSessionCallbacks; + private LoadedImages loadedImages; + public NativeClient(Configuration configuration) { _instance = this; @@ -485,6 +487,13 @@ public void RegisterForOnSessionCallbacks() _registeredForSessionCallbacks = true; NativeCode.bugsnag_registerForSessionCallbacksAfterStart(HandleSessionCallbacks); } + + public LoadedImage FindImageAtAddress(UInt64 address) + { + loadedImages.Refresh(); + return loadedImages.FindImageAtAddress(address); + } + } } #endif \ No newline at end of file diff --git a/Bugsnag/Assets/Bugsnag/Runtime/Native/Fallback/NativeClient.cs b/Bugsnag/Assets/Bugsnag/Runtime/Native/Fallback/NativeClient.cs index 894c67a4..d42f34ad 100644 --- a/Bugsnag/Assets/Bugsnag/Runtime/Native/Fallback/NativeClient.cs +++ b/Bugsnag/Assets/Bugsnag/Runtime/Native/Fallback/NativeClient.cs @@ -170,6 +170,11 @@ public void RegisterForOnSessionCallbacks() { // Not Used on this platform } + + public LoadedImage FindImageAtAddress(UInt64 address) + { + return null; + } } } #endif \ No newline at end of file diff --git a/Bugsnag/Assets/Bugsnag/Runtime/Native/Windows/NativeClient.cs b/Bugsnag/Assets/Bugsnag/Runtime/Native/Windows/NativeClient.cs index 26b0aebf..bc977518 100644 --- a/Bugsnag/Assets/Bugsnag/Runtime/Native/Windows/NativeClient.cs +++ b/Bugsnag/Assets/Bugsnag/Runtime/Native/Windows/NativeClient.cs @@ -217,6 +217,11 @@ public void RegisterForOnSessionCallbacks() { // Not Used on this platform } + + public LoadedImage FindImageAtAddress(UInt64 address) + { + return null; + } } } #endif \ No newline at end of file diff --git a/Bugsnag/Assets/Bugsnag/Runtime/Payload/Error.cs b/Bugsnag/Assets/Bugsnag/Runtime/Payload/Error.cs index e5711a40..6cf20256 100644 --- a/Bugsnag/Assets/Bugsnag/Runtime/Payload/Error.cs +++ b/Bugsnag/Assets/Bugsnag/Runtime/Payload/Error.cs @@ -7,61 +7,6 @@ namespace BugsnagUnity.Payload { - /// - /// Represents a set of Bugsnag payload exceptions that are generated from a single exception by resolving - /// the inner exceptions present. - /// - class Errors : IEnumerable - { - private IEnumerable UnwoundExceptions { get; } - - internal Errors(System.Exception exception, System.Diagnostics.StackFrame[] alternativeStackTrace) - { - UnwoundExceptions = FlattenAndReverseExceptionTree(exception).Select(e => Error.FromSystemException(e, alternativeStackTrace)); - } - - internal Errors(System.Exception exception, string providedStackTrace) - { - UnwoundExceptions = FlattenAndReverseExceptionTree(exception).Select(e => Error.FromSystemException(e, providedStackTrace)); - } - - public IEnumerator GetEnumerator() - { - return UnwoundExceptions.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - private static IEnumerable FlattenAndReverseExceptionTree(System.Exception ex) - { - if (ex == null) yield break; - - yield return ex; - - switch (ex) - { - case ReflectionTypeLoadException typeLoadException: - foreach (var exception in typeLoadException.LoaderExceptions) - { - foreach (var item in FlattenAndReverseExceptionTree(exception)) - { - yield return item; - } - } - break; - default: - foreach (var item in FlattenAndReverseExceptionTree(ex.InnerException)) - { - yield return item; - } - break; - } - } - } - /// /// Represents an individual error in the Bugsnag payload. /// @@ -133,116 +78,6 @@ public string ErrorMessage public string Type { get => "Unity"; } - - internal static Error FromSystemException(System.Exception exception, System.Diagnostics.StackFrame[] alternativeStackTrace) - { - var errorClass = exception.GetType().Name; - - // JVM exceptions in the main thread are handled by unity and require extra formatting - if (errorClass == ANDROID_JAVA_EXCEPTION_CLASS) - { - var androidErrorData = ProcessAndroidError(exception.Message); - var androidErrorClass = androidErrorData[0]; - var androidErrorMessage = androidErrorData[1]; - var lines = new StackTrace(exception.StackTrace, StackTraceFormat.AndroidJava).StackTraceLines; - return new Error(androidErrorClass, androidErrorMessage, lines, HandledState.ForUnhandledException(), true); - } - else - { - StackTraceLine[] lines; - if (!string.IsNullOrEmpty(exception.StackTrace)) - { - lines = new StackTrace(exception.StackTrace).StackTraceLines; - } - else - { - lines = new StackTrace(alternativeStackTrace).StackTraceLines; - } - return new Error(errorClass, exception.Message, lines); - } - - } - - internal static Error FromStringInfo(string name, string message, string stacktrace) - { - var stackFrames = new StackTrace(stacktrace).StackTraceLines; - return new Error(name, message, stackFrames); - } - - internal static Error FromSystemException(System.Exception exception, string stackTrace) - { - var errorClass = exception.GetType().Name; - var lines = new StackTrace(stackTrace).StackTraceLines; - - return new Error(errorClass, exception.Message, lines); - } - - public static Error FromUnityLogMessage(UnityLogMessage logMessage, System.Diagnostics.StackFrame[] stackFrames, Severity severity) - { - return FromUnityLogMessage(logMessage, stackFrames, severity, false); - } - - private static string[] ProcessAndroidError(string originalMessage) - { - string message; - string errorClass; - var match = Regex.Match(originalMessage, ERROR_CLASS_MESSAGE_PATTERN, RegexOptions.Singleline); - // If the message matches the "class: message" pattern, then the Java class is followed - // by a description of the Java exception. These two values will be used as the error - // class and message. - if (match.Success) - { - errorClass = match.Groups["errorClass"].Value; - message = match.Groups["message"].Value.Trim(); - } - else - { - // There was no Java exception description, so the Java class is the only content in - // the message. - errorClass = originalMessage; - message = string.Empty; - } - return new[] { errorClass, message }; - } - - public static Error FromUnityLogMessage(UnityLogMessage logMessage, System.Diagnostics.StackFrame[] fallbackStackFrames, Severity severity, bool forceUnhandled) - { - var match = Regex.Match(logMessage.Condition, ERROR_CLASS_MESSAGE_PATTERN, RegexOptions.Singleline); - - var lines = new StackTrace(logMessage.StackTrace).StackTraceLines; - if (lines.Length == 0) - { - lines = new StackTrace(fallbackStackFrames).StackTraceLines; - } - - var handledState = forceUnhandled - ? HandledState.ForUnhandledException() - : HandledState.ForUnityLogMessage(severity); - - if (match.Success) - { - var errorClass = match.Groups["errorClass"].Value; - var message = match.Groups["message"].Value.Trim(); - var isAndroidJavaException = false; - // JVM exceptions in the main thread are handled by unity and require extra formatting - if (errorClass == ANDROID_JAVA_EXCEPTION_CLASS) - { - isAndroidJavaException = true; - var androidErrorData = ProcessAndroidError(message); - errorClass = androidErrorData[0]; - message = androidErrorData[1]; - lines = new StackTrace(logMessage.StackTrace, StackTraceFormat.AndroidJava).StackTraceLines; - handledState = HandledState.ForUnhandledException(); - } - return new Error(errorClass, message, lines, handledState, isAndroidJavaException); - } - else - { - // include the type somehow in there - return new Error($"UnityLog{logMessage.Type}", logMessage.Condition, lines, handledState, false); - } - } - public static bool ShouldSend(System.Exception exception) { var errorClass = exception.GetType().Name; diff --git a/Bugsnag/Assets/Bugsnag/Runtime/Payload/ErrorBuilder.cs b/Bugsnag/Assets/Bugsnag/Runtime/Payload/ErrorBuilder.cs new file mode 100644 index 00000000..f725ee5a --- /dev/null +++ b/Bugsnag/Assets/Bugsnag/Runtime/Payload/ErrorBuilder.cs @@ -0,0 +1,174 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; + +namespace BugsnagUnity.Payload +{ + class ErrorBuilder + { + public ErrorBuilder(INativeClient client) + { + NativeClient = client; + } + private INativeClient NativeClient; + + private const string ANDROID_JAVA_EXCEPTION_CLASS = "AndroidJavaException"; + private const string ERROR_CLASS_MESSAGE_PATTERN = @"^(?\S+):\s+(?.*)"; + + /// + /// Build a set of Bugsnag payload exceptions from a single exception by resolving + /// the inner exceptions present. + /// + internal IEnumerable EnumerateFrom(System.Exception exception, System.Diagnostics.StackFrame[] alternativeStackTrace) + { + return FlattenAndReverseExceptionTree(exception).Select(e => FromSystemException(e, alternativeStackTrace)); + } + + /// + /// Build a set of Bugsnag payload exceptions from a single exception by resolving + /// the inner exceptions present. + /// + internal IEnumerable EnumerateFrom(System.Exception exception, string providedStackTrace) + { + return FlattenAndReverseExceptionTree(exception).Select(e => FromSystemException(e, providedStackTrace)); + } + + internal Error FromUnityLogMessage(UnityLogMessage logMessage, System.Diagnostics.StackFrame[] stackFrames, Severity severity) + { + return FromUnityLogMessage(logMessage, stackFrames, severity, false); + } + + internal Error FromUnityLogMessage(UnityLogMessage logMessage, System.Diagnostics.StackFrame[] fallbackStackFrames, Severity severity, bool forceUnhandled) + { + var match = Regex.Match(logMessage.Condition, ERROR_CLASS_MESSAGE_PATTERN, RegexOptions.Singleline); + + var lines = new StackTrace(logMessage.StackTrace).StackTraceLines; + if (lines.Length == 0) + { + lines = new StackTrace(fallbackStackFrames).StackTraceLines; + } + + var handledState = forceUnhandled + ? HandledState.ForUnhandledException() + : HandledState.ForUnityLogMessage(severity); + + if (match.Success) + { + var errorClass = match.Groups["errorClass"].Value; + var message = match.Groups["message"].Value.Trim(); + var isAndroidJavaException = false; + // JVM exceptions in the main thread are handled by unity and require extra formatting + if (errorClass == ANDROID_JAVA_EXCEPTION_CLASS) + { + isAndroidJavaException = true; + var androidErrorData = ProcessAndroidError(message); + errorClass = androidErrorData[0]; + message = androidErrorData[1]; + lines = new StackTrace(logMessage.StackTrace, StackTraceFormat.AndroidJava).StackTraceLines; + handledState = HandledState.ForUnhandledException(); + } + return new Error(errorClass, message, lines, handledState, isAndroidJavaException); + } + else + { + // include the type somehow in there + return new Error($"UnityLog{logMessage.Type}", logMessage.Condition, lines, handledState, false); + } + } + + internal Error FromStringInfo(string name, string message, string stacktrace) + { + var stackFrames = new StackTrace(stacktrace).StackTraceLines; + return new Error(name, message, stackFrames); + } + + internal Error FromSystemException(System.Exception exception, string stackTrace) + { + var errorClass = exception.GetType().Name; + var lines = new StackTrace(stackTrace).StackTraceLines; + + return new Error(errorClass, exception.Message, lines); + } + + internal Error FromSystemException(System.Exception exception, System.Diagnostics.StackFrame[] alternativeStackTrace) + { + var errorClass = exception.GetType().Name; + + // JVM exceptions in the main thread are handled by unity and require extra formatting + if (errorClass == ANDROID_JAVA_EXCEPTION_CLASS) + { + var androidErrorData = ProcessAndroidError(exception.Message); + var androidErrorClass = androidErrorData[0]; + var androidErrorMessage = androidErrorData[1]; + var lines = new StackTrace(exception.StackTrace, StackTraceFormat.AndroidJava).StackTraceLines; + return new Error(androidErrorClass, androidErrorMessage, lines, HandledState.ForUnhandledException(), true); + } + else + { + StackTraceLine[] lines; + if (!string.IsNullOrEmpty(exception.StackTrace)) + { + lines = new StackTrace(exception.StackTrace).StackTraceLines; + } + else + { + lines = new StackTrace(alternativeStackTrace).StackTraceLines; + } + return new Error(errorClass, exception.Message, lines); + } + + } + + private string[] ProcessAndroidError(string originalMessage) + { + string message; + string errorClass; + var match = Regex.Match(originalMessage, ERROR_CLASS_MESSAGE_PATTERN, RegexOptions.Singleline); + // If the message matches the "class: message" pattern, then the Java class is followed + // by a description of the Java exception. These two values will be used as the error + // class and message. + if (match.Success) + { + errorClass = match.Groups["errorClass"].Value; + message = match.Groups["message"].Value.Trim(); + } + else + { + // There was no Java exception description, so the Java class is the only content in + // the message. + errorClass = originalMessage; + message = string.Empty; + } + return new[] { errorClass, message }; + } + + private static IEnumerable FlattenAndReverseExceptionTree(System.Exception ex) + { + if (ex == null) yield break; + + yield return ex; + + switch (ex) + { + case ReflectionTypeLoadException typeLoadException: + foreach (var exception in typeLoadException.LoaderExceptions) + { + foreach (var item in FlattenAndReverseExceptionTree(exception)) + { + yield return item; + } + } + break; + default: + foreach (var item in FlattenAndReverseExceptionTree(ex.InnerException)) + { + yield return item; + } + break; + } + } + } +} diff --git a/Bugsnag/Assets/Bugsnag/Runtime/Payload/ErrorBuilder.cs.meta b/Bugsnag/Assets/Bugsnag/Runtime/Payload/ErrorBuilder.cs.meta new file mode 100644 index 00000000..ad260bf1 --- /dev/null +++ b/Bugsnag/Assets/Bugsnag/Runtime/Payload/ErrorBuilder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5748913f2bdfc44d2822306df7ba0524 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Bugsnag/Assets/Tests/ExceptionTests.cs b/Bugsnag/Assets/Tests/ExceptionTests.cs index fb275ced..c52db5e4 100644 --- a/Bugsnag/Assets/Tests/ExceptionTests.cs +++ b/Bugsnag/Assets/Tests/ExceptionTests.cs @@ -1,4 +1,6 @@ using System.Linq; +using System.Text; +using System.Threading; using NUnit.Framework; using UnityEngine; using UnityEngine.TestTools; @@ -10,6 +12,8 @@ namespace BugsnagUnityTests [TestFixture] public class ExceptionTests { + private ErrorBuilder errorBuilder = new ErrorBuilder(new NativeClient(new Configuration("X"))); + [Test] public void ParseExceptionFromLogMessage() { @@ -26,7 +30,7 @@ public void ParseExceptionFromLogMessage() Assert.IsTrue(Error.ShouldSend(log)); - var exception = Error.FromUnityLogMessage(log, new System.Diagnostics.StackFrame[] { }, Severity.Info); + var exception = errorBuilder.FromUnityLogMessage(log, new System.Diagnostics.StackFrame[] { }, Severity.Info); var stack = exception.Stacktrace.ToList(); Assert.AreEqual(7, stack.Count); @@ -80,8 +84,7 @@ public void ParseAndroidExceptionFromLogMessage() var log = new UnityLogMessage(condition, stacktrace, logType); Assert.IsTrue(Error.ShouldSend(log)); - - var exception = Error.FromUnityLogMessage(log, new System.Diagnostics.StackFrame[] { }, Severity.Warning); + var exception = errorBuilder.FromUnityLogMessage(log, new System.Diagnostics.StackFrame[] { }, Severity.Warning); var stack = exception.Stacktrace.ToList(); Assert.AreEqual("java.lang.IllegalArgumentException", exception.ErrorClass); @@ -118,7 +121,7 @@ public void ParseAndroidExceptionAndMessageFromLogMessage() Assert.IsTrue(Error.ShouldSend(log)); - var exception = Error.FromUnityLogMessage(log, new System.Diagnostics.StackFrame[] { }, Severity.Warning); + var exception = errorBuilder.FromUnityLogMessage(log, new System.Diagnostics.StackFrame[] { }, Severity.Warning); var stack = exception.Stacktrace.ToList(); Assert.AreEqual(13, stack.Count);