diff --git a/sources/Google.Solutions.Common.Test/Util/TestExceptionExtensions.cs b/sources/Google.Solutions.Common.Test/Util/TestExceptionExtensions.cs index 81686dc9d..93d0edeac 100644 --- a/sources/Google.Solutions.Common.Test/Util/TestExceptionExtensions.cs +++ b/sources/Google.Solutions.Common.Test/Util/TestExceptionExtensions.cs @@ -29,6 +29,18 @@ namespace Google.Solutions.Common.Test.Util [TestFixture] public class TestExceptionExtensions : CommonFixtureBase { + private static Exception CreateException() + { + try + { + throw new ArgumentException("sample"); + } + catch (ArgumentException e) + { + return e; + } + } + //--------------------------------------------------------------------- // Unwrap. //--------------------------------------------------------------------- @@ -108,5 +120,26 @@ public void WhenExceptionHasInnerException_ThenFullMessageContainsAllMessages() new Exception("three"))); Assert.AreEqual("One: two: three", ex.FullMessage()); } + + //--------------------------------------------------------------------- + // ToString. + //--------------------------------------------------------------------- + + [Test] + public void WhenNoOptionsSet_ThenToStringReturnsStandardTrace() + { + var ex = CreateException(); + Assert.AreEqual(ex.ToString(), ex.ToString(ExceptionFormatOptions.None)); + } + + [Test] + public void WhenIncludeOffsetOptionsSet_ThenToStringIncludesOffsets() + { + var ex = CreateException(); + var s = ex.ToString(ExceptionFormatOptions.IncludeOffsets); + StringAssert.Contains( + "CreateException() +IL_00", + s); + } } } diff --git a/sources/Google.Solutions.Common/Util/ExceptionExtensions.cs b/sources/Google.Solutions.Common/Util/ExceptionExtensions.cs index 4599e9d07..f1b1435ef 100644 --- a/sources/Google.Solutions.Common/Util/ExceptionExtensions.cs +++ b/sources/Google.Solutions.Common/Util/ExceptionExtensions.cs @@ -20,6 +20,8 @@ // using System; +using System.Diagnostics; +using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; @@ -72,5 +74,65 @@ public static string FullMessage(this Exception exception) return fullMessage.ToString(); } + + public static string ToString(this Exception exception, ExceptionFormatOptions options) + { + switch (options) + { + case ExceptionFormatOptions.IncludeOffsets: + return StackTraceBuilder.CreateStackTraceWithOffsets(exception); + + default: + return exception.ToString(); + } + } + + private static class StackTraceBuilder + { + public static string CreateStackTraceWithOffsets(Exception e) + { + var stackTrace = new StackTrace(e, false); + var buffer = new StringBuilder(); + + buffer.AppendLine($"{e.GetType().FullName}: {e.Message}"); + + foreach (var frame in stackTrace.GetFrames().EnsureNotNull()) + { + var method = frame.GetMethod(); + var parameters = string.Join( + ", ", + method + .GetParameters() + .EnsureNotNull() + .Select(p => $"{p.ParameterType.Name} {p.Name}")); + + buffer.Append($" at {method.ReflectedType.FullName}.{method.Name}({parameters})"); + buffer.Append($" +IL_{frame.GetILOffset():x4}"); + + var line = frame.GetFileName(); + if (line != null) + { + buffer.Append($" in {line}:{frame.GetFileLineNumber()}"); + } + + buffer.AppendLine(); + } + + return buffer.ToString(); + } + } + } + + public enum ExceptionFormatOptions + { + /// + /// Normal format. + /// + None, + + /// + /// Include IL offsets. + /// + IncludeOffsets } } diff --git a/sources/Google.Solutions.IapDesktop.Application/Host/Diagnostics/BugReport.cs b/sources/Google.Solutions.IapDesktop.Application/Host/Diagnostics/BugReport.cs index 4820a0b9e..39d90fa4b 100644 --- a/sources/Google.Solutions.IapDesktop.Application/Host/Diagnostics/BugReport.cs +++ b/sources/Google.Solutions.IapDesktop.Application/Host/Diagnostics/BugReport.cs @@ -20,6 +20,7 @@ // using Google.Solutions.Common.Diagnostics; +using Google.Solutions.Common.Util; using System; using System.Reflection; using System.Text; @@ -50,18 +51,21 @@ public override string ToString() if (this.exception != null) { - text.Append(this.exception.ToString()); + for (var ex = this.exception; ex != null; ex = ex.InnerException) + { + text.Append(ex.ToString(ExceptionFormatOptions.IncludeOffsets)); + text.Append("\n\n"); + } if (this.exception is ReflectionTypeLoadException tle) { text.Append("\nLoader Exceptions:\n"); foreach (var e in tle.LoaderExceptions) { - text.Append(e.ToString()); + text.Append(e.ToString(ExceptionFormatOptions.IncludeOffsets)); + text.Append("\n\n"); } } - - text.Append("\n\n"); } var cpuArchitecture = Assembly.GetEntryAssembly()?.GetName().ProcessorArchitecture.ToString() ?? "unknown";