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";