From aa49c5d25ae957ac2b9c1931e3f17c2fed77e4f0 Mon Sep 17 00:00:00 2001 From: Konrad Dysput Date: Mon, 9 Apr 2018 18:56:44 +0200 Subject: [PATCH] Revert "Stack frames improvement - deleted stack frames generated by async state machine. Calling assembly fix" This reverts commit 52b2f0c24e47cb33e09e63dd1a67901d2c563b62. --- Backtrace/Base/BacktraceReportBase.cs | 95 ++++++++----------- Backtrace/Common/StackTraceHelper.cs | 57 +++++++++++ Backtrace/Common/SystemHelper.cs | 11 +-- Backtrace/Extensions/ExceptionExtensions.cs | 70 +++++++++----- Backtrace/Model/BacktraceData.cs | 4 +- Backtrace/Model/JsonData/ExceptionStack.cs | 18 ++-- Backtrace/Model/JsonData/SourceCodeData.cs | 6 +- Backtrace/Model/JsonData/ThreadData.cs | 6 +- Backtrace/Model/JsonData/ThreadInformation.cs | 6 +- 9 files changed, 160 insertions(+), 113 deletions(-) create mode 100644 Backtrace/Common/StackTraceHelper.cs diff --git a/Backtrace/Base/BacktraceReportBase.cs b/Backtrace/Base/BacktraceReportBase.cs index 720d155..e56744c 100644 --- a/Backtrace/Base/BacktraceReportBase.cs +++ b/Backtrace/Base/BacktraceReportBase.cs @@ -4,13 +4,8 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Globalization; using System.Linq; using System.Reflection; -#if !NET35 -using System.Runtime.CompilerServices; -using System.Runtime.ExceptionServices; -#endif using System.Text; namespace Backtrace.Base @@ -84,7 +79,7 @@ public string Classifier /// /// Current report exception stack /// - public List DiagnosticStack { get; set; } = new List(); + public List ExceptionStack { get; set; } = new List(); /// /// Create new instance of Backtrace report to sending a report with custom client message @@ -153,87 +148,71 @@ internal static Dictionary ConcatAttributes( return reportAttributes.Merge(attributes); } - /// - /// Set Calling Assembly and current thread stack trace property. - /// CallingAssembly and StackTrace are necessary to prepare diagnostic JSON in BacktraceData class - /// - private void SetCallingAppInformation() - { - // generate stacktrace with file info - // if assembly have pbd files, diagnostic JSON will contain information about - // line number and column number - var stackTrace = new StackTrace(true); - var stackFrames = stackTrace.GetFrames(); - SetStacktraceInformation(stackFrames, true); - - if (Exception == null) - { - return; - } - // add stack trace from exception - var head = DiagnosticStack.Any() ? DiagnosticStack[0] : null; - var generatedStack = Exception.GetExceptionStackFrames(head); - SetStacktraceInformation(generatedStack, false); - } - private void SetStacktraceInformation(StackFrame[] stackFrames, bool includeCallingAssembly, int startingIndex = 0) { - // check if stack frames exists - if (stackFrames == null) - { - return; - } var executedAssemblyName = Assembly.GetExecutingAssembly().FullName; - //if callingAssemblyFound is true, we dont need to found calling assembly in current stacktrace + //if includeCallingAssembly is true, we dont need to found calling assembly in current stacktrace bool callingAssemblyFound = !includeCallingAssembly; foreach (var stackFrame in stackFrames) { - var method = stackFrame.GetMethod(); - var declaringType = method?.DeclaringType; - if (declaringType == null) + if (stackFrame == null) { //received invalid or unvailable stackframe continue; } - Assembly assembly = declaringType.Assembly; + Assembly assembly = stackFrame?.GetMethod()?.DeclaringType?.Assembly; if (assembly == null) { continue; } var assemblyName = assembly.FullName; - if (executedAssemblyName.Equals(assemblyName) && CallingAssembly == null) + if (executedAssemblyName.Equals(assemblyName)) { // remove all system and microsoft stack frames //if we add any stackframe to list this is mistake because we receive //system or microsoft dll (for example async invoke) - DiagnosticStack.Clear(); + ExceptionStack.Clear(); startingIndex = 0; continue; } - + ExceptionStack.Insert(startingIndex, Model.JsonData.ExceptionStack.Convert(stackFrame, assembly.GetName().Name, true)); + startingIndex++; if (!callingAssemblyFound && !(SystemHelper.SystemAssembly(assembly))) { callingAssemblyFound = true; CallingAssembly = assembly; } -#if !NET35 - //test if current stack frame is generated by async state machine - var declaringTypeInfo = declaringType.GetTypeInfo(); - var stateMachineFrame = SystemHelper.StateMachineFrame(declaringTypeInfo); - if (stateMachineFrame) - { - continue; - } -#endif - if (!callingAssemblyFound) - { - continue; - } - var diagnosticStack = Model.JsonData.DiagnosticStack.Convert(stackFrame, assembly.GetName().Name, true); - DiagnosticStack.Insert(startingIndex, diagnosticStack); - startingIndex++; } } + /// + /// Set Calling Assembly and Exception Stack property. + /// CallingAssembly and StackTrace are necessary to prepare diagnostic JSON in BacktraceData class + /// + private void SetCallingAppInformation() + { + // generate stacktrace with file info + // if assembly have pbd files, diagnostic JSON will contain information about + // line number and column number + var stackTrace = new StackTrace(true); + var stackFrames = stackTrace.GetFrames(); + SetStacktraceInformation(stackFrames, true); + + if (Exception == null) + { + return; + } + //Add to current stack trace, stackframes from current exception + //stacktrace from current thread and from current excetpion are diffrent + var exceptionStackTrace = new StackTrace(Exception, true); + var exceptionStackFrames = exceptionStackTrace.GetFrames(); + if (exceptionStackFrames != null && exceptionStackFrames[0] != null + && ExceptionStack[0].ILOffset != exceptionStackFrames[0].GetILOffset() + && ExceptionStack[0].FunctionName != exceptionStackFrames[0].GetMethod()?.Name) + { + SetStacktraceInformation(exceptionStackFrames, false); + } + } + } } diff --git a/Backtrace/Common/StackTraceHelper.cs b/Backtrace/Common/StackTraceHelper.cs new file mode 100644 index 0000000..7358030 --- /dev/null +++ b/Backtrace/Common/StackTraceHelper.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace Backtrace.Common +{ + /// + /// All usefull information about current StackTrace + /// + public static class StackTraceHelper + { + /// + /// Get current thread stack trace + /// + /// Current exception (if exists + /// Current thread stack frames + public static List GetStackFrames(Exception source = null) + { + var currentAssembly = Assembly.GetExecutingAssembly(); + // generate stacktrace with file info + // if assembly have pbd files, diagnostic JSON will contain information about + // line number and column number + var stackTrace = new StackTrace(true); +#if DEBUG + Trace.WriteLine("CURRENT THREAD STACK TRACE:"); + Trace.WriteLine(stackTrace.ToString()); + Trace.WriteLine("END OF THE STACK TRACE"); +#endif + var stackFrames = stackTrace.GetFrames() + .Where(n => n?.GetMethod()?.DeclaringType?.Assembly != currentAssembly) + .ToList(); + + if (source != null) + { + var exceptionStackTrace = new StackTrace(source, true); +#if DEBUG + Trace.WriteLine("CURRENT EXCEPTION STACK TRACE:"); + Trace.WriteLine(exceptionStackTrace.ToString()); + Trace.WriteLine("END OF THE EXCEPTION STACK TRACE"); +#endif + var exceptionStackFrames = exceptionStackTrace.GetFrames(); + //information from exception stack frame is already in current stacktrace (example: catching unhandled app exception) + if (stackFrames[0] != null && exceptionStackFrames[0] != null + && stackFrames[0].GetILOffset() == exceptionStackFrames[0].GetILOffset() + && stackFrames[0].GetMethod()?.Name == exceptionStackFrames[0].GetMethod()?.Name) + { + return stackFrames; + } + stackFrames.InsertRange(0, exceptionStackFrames); + } + return stackFrames; + } + } +} diff --git a/Backtrace/Common/SystemHelper.cs b/Backtrace/Common/SystemHelper.cs index bcb4197..577c596 100644 --- a/Backtrace/Common/SystemHelper.cs +++ b/Backtrace/Common/SystemHelper.cs @@ -129,7 +129,7 @@ internal static bool SystemAssembly(Assembly assembly) { return false; } - var assemblyName = assembly.GetName().Name; + var assemblyName = assembly.FullName; return SystemAssembly(assemblyName); } /// @@ -143,16 +143,7 @@ internal static bool SystemAssembly(string assemblyName) return false; } return (assemblyName.StartsWith("Microsoft.") - || assemblyName.StartsWith("mscorlib") - || assemblyName.Equals("System") || assemblyName.StartsWith("System.")); } -#if !NET35 - internal static bool StateMachineFrame(TypeInfo declaringTypeInfo) - { - return typeof(System.Runtime.CompilerServices.IAsyncStateMachine) - .GetTypeInfo().IsAssignableFrom(declaringTypeInfo); - } -#endif } } diff --git a/Backtrace/Extensions/ExceptionExtensions.cs b/Backtrace/Extensions/ExceptionExtensions.cs index 474b6a4..57b38a8 100644 --- a/Backtrace/Extensions/ExceptionExtensions.cs +++ b/Backtrace/Extensions/ExceptionExtensions.cs @@ -1,8 +1,6 @@ using Backtrace.Model; -using Backtrace.Model.JsonData; using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Text.RegularExpressions; @@ -22,35 +20,57 @@ public static BacktraceReport ToBacktraceReport(this Exception source) { return new BacktraceReport(source); } +#if !NET35 + + /// Provides full stack trace for the exception that occurred. + /// + /// Exception object. + /// Environment stack trace, for pulling additional stack frames. + public static string ToLogString(this Exception exception, string environmentStackTrace) + { + List environmentStackTraceLines = GetUserStackTraceLines(environmentStackTrace); + environmentStackTraceLines.RemoveAt(0); + + List stackTraceLines = GetStackTraceLines(exception.StackTrace); + stackTraceLines.AddRange(environmentStackTraceLines); + + string fullStackTrace = String.Join(Environment.NewLine, stackTraceLines); + + string logMessage = exception.Message + Environment.NewLine + fullStackTrace; + return logMessage; + } /// - /// Generate stack traces that not exists in current thread stack trace + /// Gets a list of stack frame lines, as strings. /// - /// Unique exception stack frames - internal static StackFrame[] GetExceptionStackFrames(this Exception source, DiagnosticStack firstFrame) + /// Stack trace string. + private static List GetStackTraceLines(string stackTrace) { - if (source == null) - { - return null; - } - var exceptionStackTrace = new StackTrace(source, true); - var exceptionStackFrames = exceptionStackTrace.GetFrames(); - if (exceptionStackFrames == null || !exceptionStackFrames.Any()) - { - return null; - } - if (firstFrame == null) - { - return exceptionStackFrames; - } - var comparer = exceptionStackFrames[0]; - //validate if exception stack frame exists in environment stack trace - if (firstFrame.ILOffset == comparer.GetILOffset() - && firstFrame.FunctionName == comparer.GetMethod()?.Name) + return stackTrace.Split(new[] { Environment.NewLine }, StringSplitOptions.None).ToList(); + } + + /// + /// Gets a list of stack frame lines, as strings, only including those for which line number is known. + /// + /// Full stack trace, including external code. + private static List GetUserStackTraceLines(string fullStackTrace) + { + List outputList = new List(); + Regex regex = new Regex(@"([^\)]*\)) in (.*):line (\d)*$", RegexOptions.Compiled); + + List stackTraceLines = ExceptionExtensions.GetStackTraceLines(fullStackTrace); + foreach (string stackTraceLine in stackTraceLines) { - return null; + if (!regex.IsMatch(stackTraceLine)) + { + continue; + } + + outputList.Add(stackTraceLine); } - return exceptionStackFrames; + + return outputList; } +#endif } } diff --git a/Backtrace/Model/BacktraceData.cs b/Backtrace/Model/BacktraceData.cs index 3408d18..406356b 100644 --- a/Backtrace/Model/BacktraceData.cs +++ b/Backtrace/Model/BacktraceData.cs @@ -157,7 +157,7 @@ internal Dictionary SourceCode { get { - var sourceCode = new SourceCodeData(Report.DiagnosticStack); + var sourceCode = new SourceCodeData(Report.ExceptionStack); if (sourceCode.data.Any()) { return sourceCode.data; @@ -199,7 +199,7 @@ public BacktraceData(BacktraceReportBase report, Dictionary scoped { Report = report; _backtraceAttributes = new BacktraceAttributes(Report, scopedAttributes); - ThreadData = new ThreadData(report.CallingAssembly, Report.DiagnosticStack); + ThreadData = new ThreadData(report.CallingAssembly, Report.ExceptionStack); } } } diff --git a/Backtrace/Model/JsonData/ExceptionStack.cs b/Backtrace/Model/JsonData/ExceptionStack.cs index 38e3d00..8062b97 100644 --- a/Backtrace/Model/JsonData/ExceptionStack.cs +++ b/Backtrace/Model/JsonData/ExceptionStack.cs @@ -16,7 +16,7 @@ namespace Backtrace.Model.JsonData /// /// Parse exception information to Backtrace API format /// - public class DiagnosticStack + public class ExceptionStack { /// /// Function where exception occurs @@ -66,7 +66,7 @@ public class DiagnosticStack /// Library name /// If true, current exception stack is generated by exception /// ExceptionStack instance - internal static DiagnosticStack Convert(StackFrame stackFrame, string libraryName, bool generatedByException = false) + internal static ExceptionStack Convert(StackFrame stackFrame, string libraryName, bool generatedByException = false) { if (stackFrame == null) { @@ -74,7 +74,7 @@ internal static DiagnosticStack Convert(StackFrame stackFrame, string libraryNam } int? ILOffset = stackFrame.GetILOffset(); string functionName = stackFrame.GetMethod()?.Name; - return new DiagnosticStack() + return new ExceptionStack() { Column = stackFrame.GetFileColumnNumber(), Library = libraryName, @@ -86,7 +86,7 @@ internal static DiagnosticStack Convert(StackFrame stackFrame, string libraryNam }; } - internal static IEnumerable FromCurrentThread(string libraryName, IEnumerable exceptionStack) + internal static IEnumerable FromCurrentThread(string libraryName, IEnumerable exceptionStack) { var currentAssembly = Assembly.GetExecutingAssembly(); var stackTrace = new StackTrace(true); @@ -127,7 +127,7 @@ internal static IEnumerable FromCurrentThread(string libraryNam /// /// Current exception /// List of exception stack trace - internal static List Convert(Exception exception) + internal static List Convert(Exception exception) { // don't do anything if this is not exception if (exception == null) @@ -141,15 +141,15 @@ internal static List Convert(Exception exception) string source = exception.Source; if (stackFrames == null || stackFrames.Length == 0) { - return new List(); + return new List(); } // convert from stacktrace to diagnostic stack trace return stackFrames.Select(n => Convert(n, source, true)).ToList(); } #if NET45 - internal static IEnumerable Convert(IEnumerable clrStackFrames) + internal static IEnumerable Convert(IEnumerable clrStackFrames) { - var result = new List(); + var result = new List(); if (clrStackFrames == null || clrStackFrames.Count() == 0) { return result; @@ -160,7 +160,7 @@ internal static IEnumerable Convert(IEnumerable { continue; } - result.Add(new DiagnosticStack() + result.Add(new ExceptionStack() { FunctionName = clrStackFrame.Method.Name, Library = clrStackFrame.ModuleName diff --git a/Backtrace/Model/JsonData/SourceCodeData.cs b/Backtrace/Model/JsonData/SourceCodeData.cs index 0d76f7d..41a2af4 100644 --- a/Backtrace/Model/JsonData/SourceCodeData.cs +++ b/Backtrace/Model/JsonData/SourceCodeData.cs @@ -56,7 +56,7 @@ public string SourceCodeFullPath /// /// Exception Stack /// New instance of SoruceCode - public static SourceCode FromExceptionStack(DiagnosticStack exceptionStack) + public static SourceCode FromExceptionStack(ExceptionStack exceptionStack) { return new SourceCode() { @@ -71,12 +71,12 @@ public static SourceCode FromExceptionStack(DiagnosticStack exceptionStack) /// Source code information about current executed program /// public Dictionary data = new Dictionary(); - internal SourceCodeData(IEnumerable exceptionStack) + internal SourceCodeData(IEnumerable exceptionStack) { SetStack(exceptionStack); } - private void SetStack(IEnumerable exceptionStack) + private void SetStack(IEnumerable exceptionStack) { if (exceptionStack == null || exceptionStack.Count() == 0) { diff --git a/Backtrace/Model/JsonData/ThreadData.cs b/Backtrace/Model/JsonData/ThreadData.cs index aa8ca23..7ffa780 100644 --- a/Backtrace/Model/JsonData/ThreadData.cs +++ b/Backtrace/Model/JsonData/ThreadData.cs @@ -29,7 +29,7 @@ public class ThreadData /// /// Create instance of ThreadData class to collect information about used threads /// - internal ThreadData(Assembly callingAssembly, IEnumerable exceptionStack) + internal ThreadData(Assembly callingAssembly, IEnumerable exceptionStack) { #if NET45 //use available in .NET 4.5 api to find stack trace of all available managed threads @@ -46,7 +46,7 @@ internal ThreadData(Assembly callingAssembly, IEnumerable excep /// Generate information for current thread /// /// Current BacktraceReport exception stack - private void GenerateCurrentThreadInformation(IEnumerable exceptionStack) + private void GenerateCurrentThreadInformation(IEnumerable exceptionStack) { var current = Thread.CurrentThread; //get current thread id @@ -117,7 +117,7 @@ private void GetUsedThreads(Assembly callingAssembly) { threadName = thread.ManagedThreadId.ToString(); } - var frames = DiagnosticStack.Convert(thread.StackTrace); + var frames = ExceptionStack.Convert(thread.StackTrace); ThreadInformations[threadName] = new ThreadInformation(threadName, false, frames); } } diff --git a/Backtrace/Model/JsonData/ThreadInformation.cs b/Backtrace/Model/JsonData/ThreadInformation.cs index 376c4b6..c01c65c 100644 --- a/Backtrace/Model/JsonData/ThreadInformation.cs +++ b/Backtrace/Model/JsonData/ThreadInformation.cs @@ -26,7 +26,7 @@ public class ThreadInformation [JsonProperty(PropertyName = "stack")] - internal IEnumerable Stack = new List(); + internal IEnumerable Stack = new List(); /// /// Create new instance of ThreadInformation @@ -34,7 +34,7 @@ public class ThreadInformation /// Thread name /// Denotes whether a thread is a faulting thread - in most cases main thread /// Exception stack information - public ThreadInformation(string threadName, bool fault, IEnumerable stack) + public ThreadInformation(string threadName, bool fault, IEnumerable stack) { if (stack != null) { @@ -50,7 +50,7 @@ public ThreadInformation(string threadName, bool fault, IEnumerableThread to analyse /// Exception stack information /// Is current thread flag - public ThreadInformation(Thread thread, IEnumerable stack, bool currentThread = false) + public ThreadInformation(Thread thread, IEnumerable stack, bool currentThread = false) : this( threadName: thread.GenerateValidThreadName(), fault: currentThread, //faulting thread = current thread