Ben.Demystifier icon indicating copy to clipboard operation
Ben.Demystifier copied to clipboard

Serialized exceptions not correctly Demystified

Open enemaerke opened this issue 3 years ago • 1 comments

Exceptions going through the serialization constructor stores the original stacktrace in a private _stackTraceString field (see https://referencesource.microsoft.com/#mscorlib/system/exception.cs,93). This original stacktrace is tested for and returned from the StackTrace property-getter in Exception (see https://referencesource.microsoft.com/#mscorlib/system/exception.cs,352 )

From my observations, Demystifying an exception after serialization will not capture and Demystify this original stacktrace but will instead return a string that is the exception type and the exception message, no stacktrace included.

This is a quick repro (cheating a little bit with the reflection to avoid a serialization pass in this code):

var originalStackTrace = @"   at Some.Service.TestCode(Guid id) in C:\path\class.cs:line 28
   at Some.Service.Start(Guid id) in C:\path\class.cs:line 22
";
var exception = Activator.CreateInstance<NullReferenceException>();
SetPrivateFieldWithReflection(exception, "_message", "Object reference not set to an instance of an object.");
SetPrivateFieldWithReflection(exception, "_stackTraceString", originalStackTrace);

// verify that the Demystified string cuts off the stacktrace but the original StackTrace property retains it
Assert.Equal($"System.NullReferenceException: Object reference not set to an instance of an object.{Environment.NewLine}", exception.ToStringDemystified());
Assert.Equal(originalStackTrace, exception.StackTrace);

enemaerke avatar Mar 26 '21 07:03 enemaerke

Working a bit more with this in our context we are hitting this scenario (in a microservice setup):

  • Service A calls Service B
  • Service B throws exception which is serialized back to Service A (the stacktrace is captured by the standard serialization at this point)
  • In Service A, in our general logging, when processing a given exception we have a hard time determining if we should use ToStringDemystified() or just the standard StackTrace property, not having an immediate way picking:
    • If it is an exception that has crossed a serialization boundary, ToStringDemystified() does not return any stacktrace and is not usefull and we'd like to instead log the StackTrace property that returns the serialization captured stacktrace.
    • If the exception has not crossed a serialization boundary, we would prefer to use ToStringDemystified()

So we need a way to determine this. We could attempt to determine the number of stackframes, since they are 0 when having crossed the serialization boundary but that would require us to perform either new StackTrace(exception).FrameCount or even new EnhancedStackTrace(exception).FrameCount and thus potentially paying the penalty of obtaining a stacktrace twice. A way to avoid this would be to allow the EnhancedStackTrace to also be demystified. So essentially:

var enhancedStackTrace = new EnhancedStackTrace(exc);
if (enhancedStackTrace.FrameCound > 0) {
  return enhancedStackTrace.ToStringDemystified(exc);   //proposed new method
}

return exc.StackTrace;

This new method hanging off of the EnhancedStackTrace would essentially enable the ToStringDemystified to skip new'ing up another instance of EnhancedStackTrace and saving a stackframe capture. Would there be interest in such an extension?

enemaerke avatar Apr 08 '21 10:04 enemaerke