-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Clarify inner exception and aggregate exception ToString() #27333
Changes from all commits
aae660d
7b57069
54336a7
1f7c0e9
2dc55bd
ba389ee
c37aab5
8392980
7a6b228
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -438,16 +438,12 @@ public override string ToString() | |
| StringBuilder text = new StringBuilder(); | ||
| text.Append(base.ToString()); | ||
|
|
||
| for (int i = 0; i < m_innerExceptions.Count; i++) | ||
| foreach (Exception ex in m_innerExceptions) | ||
| { | ||
| if (m_innerExceptions[i] == InnerException) | ||
| if (object.ReferenceEquals(ex, InnerException)) | ||
| continue; // Already logged in base.ToString() | ||
|
|
||
| text.Append(Environment.NewLineConst + InnerExceptionPrefix); | ||
| text.AppendFormat(CultureInfo.InvariantCulture, SR.AggregateException_InnerException, i); | ||
| text.Append(m_innerExceptions[i].ToString()); | ||
| text.Append("<---"); | ||
| text.AppendLine(); | ||
| text.Append(InnerExceptionToString(ex)); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure. |
||
| } | ||
|
|
||
| return text.ToString(); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -95,11 +95,10 @@ public override string ToString() | |
| { | ||
| string s = GetType().ToString() + ": " + Message; | ||
|
|
||
| if (!string.IsNullOrEmpty(_fileName)) | ||
| if (!string.IsNullOrEmpty(_fileName) && !Message.Contains(_fileName)) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this all that meaningful? While we discourage parsing, the current format is much more parsable for the filename.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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. |
||
| s += Environment.NewLineConst + SR.Format(SR.IO_FileName_Name, _fileName); | ||
|
|
||
| if (InnerException != null) | ||
| s += InnerExceptionPrefix + InnerException.ToString(); | ||
| s += InnerExceptionToString(InnerException); | ||
|
|
||
| if (StackTrace != null) | ||
| s += Environment.NewLineConst + StackTrace; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -321,13 +321,6 @@ internal void ToString(TraceFormat traceFormat, StringBuilder sb) | |
| sb.AppendFormat(CultureInfo.InvariantCulture, inFileLineNum, fileName, sf.GetFileLineNumber()); | ||
| } | ||
| } | ||
|
|
||
| // Skip EDI boundary for async | ||
| if (sf.IsLastFrameFromForeignExceptionStackTrace && !isAsync) | ||
| { | ||
| sb.AppendLine(); | ||
| sb.Append(SR.Exception_EndStackTraceFromPreviousThrow); | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this being removed?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The idea was that the boundary could be inferred - this was in the previous PR I think.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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. |
||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,15 +5,14 @@ | |
| using System.Collections; | ||
| using System.Diagnostics; | ||
| using System.Runtime.Serialization; | ||
| using System.Text; | ||
|
|
||
| namespace System | ||
| { | ||
| [Serializable] | ||
| [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] | ||
| public partial class Exception : ISerializable | ||
| { | ||
| private protected const string InnerExceptionPrefix = " ---> "; | ||
|
|
||
| public Exception() | ||
| { | ||
| _HResult = HResults.COR_E_EXCEPTION; | ||
|
|
@@ -128,10 +127,7 @@ public override string ToString() | |
| s += ": " + message; | ||
| } | ||
|
|
||
| if (_innerException != null) | ||
| { | ||
| s += Environment.NewLineConst + InnerExceptionPrefix + _innerException.ToString() + Environment.NewLineConst + " " + SR.Exception_EndOfInnerExceptionStack; | ||
| } | ||
| s += InnerExceptionToString(InnerException); | ||
|
|
||
| string? stackTrace = StackTrace; | ||
| if (stackTrace != null) | ||
|
|
@@ -142,6 +138,34 @@ public override string ToString() | |
| return s; | ||
| } | ||
|
|
||
| // Returns the indented inner exception's ToString() prefixed, but not suffixed, with a newline. | ||
| // If there is no inner exception, returns empty string. | ||
| private protected string InnerExceptionToString(Exception? inner) | ||
| { | ||
| if (inner == null) | ||
| return ""; | ||
|
|
||
| /* Format something like this to clearly separate from the containing exception | ||
| ________________________________________________________________________________________________ | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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:
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
e.g. some of our own corefx tests are failing because of this problem
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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?)
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll close this and open an issue to discuss that... |
||
| |_1.Interop+Crypto+OpenSslCryptographicException: error:25070067:DSO support routines:DSO_load:could not load the shared library | ||
| | at _1.Program.<>c.<Main>b__0_0() in C:\proj\30\ConsoleApp1\Program.cs:line 31 | ||
| | at _1.Program.Wrap(Action cb) in C:\proj\30\ConsoleApp1\Program.cs:line 61 | ||
| \________________________________________________________________________________________________ | ||
|
|
||
| */ | ||
| const string nl = Environment.NewLineConst; | ||
| string hundredUnderscores = new string('_', 100); | ||
|
|
||
| var sb = new StringBuilder(); | ||
| sb.Append(nl + " ").Append(hundredUnderscores).Append(nl); | ||
| sb.Append(inner.ToString().Trim()); // Remove any trailing newline | ||
| sb.Replace("\r\n", "\n", nl.Length, sb.Length - nl.Length); // Normalize newlines except the first | ||
| sb.Replace("\n", nl + " |", nl.Length, sb.Length - nl.Length); // Indent each line, after the first | ||
| sb.Append(nl + " \\").Append(hundredUnderscores); | ||
|
|
||
| return sb.ToString(); | ||
| } | ||
|
|
||
| protected event EventHandler<SafeSerializationEventArgs>? SerializeObjectState | ||
| { | ||
| add { throw new PlatformNotSupportedException(SR.PlatformNotSupported_SecureBinarySerialization); } | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -443,7 +443,6 @@ internal void SetCurrentStackTrace() | |||||||
| // 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); | ||||||||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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.,
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please put it back. Have you looked at what the stack traces look like without it? |
||||||||
| _remoteStackTraceString = sb.ToString(); | ||||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is == inappropriate here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We want to filter out the exact same object - figured it was clearer. Also a subclass could have overridden the equals operator.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's actually not how
==works. Unless the strongly-typed variable being used defines anoperator ==(andExceptiondoesn't), this will be emitted with the exact same behavior as ReferenceEquals. It doesn't useEquals. e.g. this will outputfalse:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It often ends up being personal preference.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Roslyn compiles ReferenceEquals into the same IL as what you get from
==, so it is just a style difference. Similar to== nullvs.is null.