Clarify inner exception and aggregate exception ToString()#27333
Clarify inner exception and aggregate exception ToString()#27333danmoseley wants to merge 9 commits intodotnet:masterfrom
Conversation
| // when it's retrieved. | ||
| var sb = new StringBuilder(256); | ||
| new StackTrace(fNeedFileInfo: true).ToString(System.Diagnostics.StackTrace.TraceFormat.TrailingNewLine, sb); | ||
| sb.AppendLine(SR.Exception_EndStackTraceFromPreviousThrow); |
There was a problem hiding this comment.
I removed this line (and thus the string) because elsewhere where remote stack trace and stack trace are combined, they are concatenated without a separator. Eg.,
There was a problem hiding this comment.
Please put it back. Have you looked at what the stack traces look like without it?
d2be58c to
8392980
Compare
| return ""; | ||
|
|
||
| /* Format something like this to clearly separate from the containing exception | ||
| ________________________________________________________________________________________________ |
There was a problem hiding this comment.
I believe that the ascii art part of the change has significant risk of breaking tools that analyze exception stacktraces, and it may be controversial (e.g. my personal preference would be a more compact format without the ascii art, but I am happy to be overruled.)
If you would like to pursue it, I think it should have its own issue and PR. The issue should be marked as breaking change and the stakeholders should be included to comment on it:
- The community projects that are in the business of formatting and understanding exception stacktraces (e.g. Ben.Demystifier)
- Our diagnostic and TI teams
There was a problem hiding this comment.
breaking tools that analyze exception stacktraces
e.g. some of our own corefx tests are failing because of this problem
There was a problem hiding this comment.
I really just wanted to start a discussion. Any suggestions for an alternative? Maybe the arrows? (Is there general agreement the existing format is hard to correlate?)
There was a problem hiding this comment.
I'll close this and open an issue to discuss that...
| foreach (Exception ex in m_innerExceptions) | ||
| { | ||
| if (m_innerExceptions[i] == InnerException) | ||
| if (object.ReferenceEquals(ex, InnerException)) |
There was a problem hiding this comment.
Why is == inappropriate here?
There was a problem hiding this comment.
We want to filter out the exact same object - figured it was clearer. Also a subclass could have overridden the equals operator.
There was a problem hiding this comment.
Also a subclass could have overridden the equals operator.
That's actually not how == works. Unless the strongly-typed variable being used defines an operator == (and Exception doesn't), this will be emitted with the exact same behavior as ReferenceEquals. It doesn't use Equals. e.g. this will output false:
using System;
class Program
{
static void Main() => Console.WriteLine(new Program() == new Program());
public override bool Equals(object obj) => true;
}There was a problem hiding this comment.
I had forgotten this. In the case where this is known to be true (as here), do we prefer to use == for brevity, or O.RE to be explicit about intent? I assume the former as we don't seem to use O.RE that much.
There was a problem hiding this comment.
It often ends up being personal preference.
There was a problem hiding this comment.
Roslyn compiles ReferenceEquals into the same IL as what you get from ==, so it is just a style difference. Similar to == null vs. is null.
| text.Append(m_innerExceptions[i].ToString()); | ||
| text.Append("<---"); | ||
| text.AppendLine(); | ||
| text.Append(InnerExceptionToString(ex)); |
There was a problem hiding this comment.
This helper ends up allocating it's own string builder, formatting to it, getting its string, and then appending that string to this builder. Why not just pass this builder in? If the concern is reusing it in places that don't use a builder, you can make the helper create the builder only if one want passed in, and return a its ToString only if one was.
| string s = GetType().ToString() + ": " + Message; | ||
|
|
||
| if (!string.IsNullOrEmpty(_fileName)) | ||
| if (!string.IsNullOrEmpty(_fileName) && !Message.Contains(_fileName)) |
There was a problem hiding this comment.
Is this all that meaningful? While we discourage parsing, the current format is much more parsable for the filename.
There was a problem hiding this comment.
Assuming the filename is set and not just in the message. Ok, guess I need feedback on this one.
Apart from reducing noise, another possible advantage of reducing line count is that the parts of the output are closer together in a log produced from concurrent Interleaving outputs eg a build log or test run.
| { | ||
| sb.AppendLine(); | ||
| sb.Append(SR.Exception_EndStackTraceFromPreviousThrow); | ||
| } |
There was a problem hiding this comment.
The idea was that the boundary could be inferred - this was in the previous PR I think.
There was a problem hiding this comment.
I don't believe that's easy to do in many/all cases. We explicitly skip this for async because that case generally is easier, but for other cases, you're generally not nearly as linear, and you end up with stack traces that look impossible, which is much more confusing and time consuming to analyze than when they're clearly demarcated.
|
Some of the changes in the PR like including the filename for bad image format exceptions are good and non-contentious. They can be submitted separately from the more profound changes. |
Continuation of the part of #25045 that was deferred from 3.0.
Using this test program - the SSL case is real, albeit the stacks are not --
It's easiest to open in separate tabs and switch between. I think the boxes make it considerably easier to match up which exception goes with which stack, and which exception is nested inside which other exception. Thoughts? Other suggestions?
Note that the non-aggregate non-nested exception case (top example) is not affected.
BTW also made some minor fixes to pretty some exceptions I noticed as I was doing this
File name: 'c:\some\file'is an unnecessary line.