/// <summary> /// Processes a <see cref="ReflectionTypeLoadException"/> in order to makes sure /// type information is included and all <see cref="ReflectionTypeLoadException.LoaderExceptions"/> /// are recursively parsed. /// </summary> internal static void ProcessTypeLoadException(ExceptionData exceptionData, ReflectionTypeLoadException typeLoadException, StringBuilder builder, int indent) { AppendFormat(builder, indent, "ReflectionTypeLoadException (possibility: MEF composition error): {0}", typeLoadException.Message); builder.AppendLine(); Append(builder, indent, "- Types: "); IEnumerable <string> typeNames = typeLoadException.Types.Select(t => t == null ? "[null]" : t.Name); builder.AppendLine(String.Join(",", typeNames)); AppendLine(builder, indent, "- Loader exceptions:"); //keep that line above the if statement below, even if there were no Errors if (typeLoadException.LoaderExceptions != null) { foreach (Exception loaderException in typeLoadException.LoaderExceptions) { //the loader exceptions array may contain null references! if (loaderException == null) { continue; } //store hidden exception and recurse exceptionData.HiddenExceptions.Add(loaderException); Parse(exceptionData, loaderException, builder, indent + 1); } } }
/// <summary> /// Gets a string representation of the submitted <paramref name="data"/>. /// </summary> /// <param name="data">Processed exception data.</param> /// <param name="includeRootExceptionStack">Whether to include the /// stack trace of the root exception in the returned string. Not needed if /// the root exception is being logged anyway.</param> /// <returns>A formatted string reflecting the exception hierarchy.</returns> /// <exception cref="ArgumentNullException">If <paramref name="data"/> /// is a null reference.</exception> public static string Print(ExceptionData data, bool includeRootExceptionStack) { if (data == null) { throw new ArgumentNullException(nameof(data)); } //get builder message var sb = new StringBuilder(data.FormattedException); //add full string representation of the root exception if (includeRootExceptionStack) { sb.AppendLine(); AppendLine(sb, 0, "-"); sb.AppendLine(data.RootException.ToString()); } if (data.HiddenExceptions.Any()) { //add additional exception stack traces sb.AppendLine(); sb.AppendLine("---"); sb.AppendLine("NESTED / HIDDEN EXCEPTIONS:"); foreach (var exception in data.HiddenExceptions) { sb.AppendLine(); AppendLine(sb, 1, exception.ToString()); AppendLine(sb, 0, "---"); } } return(sb.ToString()); }
/// <summary> /// Main routine that analyzes the currently processed <paramref name="exception"/>. /// </summary> internal static void Parse(ExceptionData exceptionData, Exception exception, StringBuilder builder, int indent) { var typeLoadException = exception as ReflectionTypeLoadException; var aggregateException = exception as AggregateException; exceptionData.AllExceptions.Add(exception); if (aggregateException != null) { ProcessAggregateException(exceptionData, aggregateException, builder, indent); //skip recursion to InnerException - covered by the InnerExceptions loop return; } if (typeLoadException != null) { ProcessTypeLoadException(exceptionData, typeLoadException, builder, indent); } else { //default exception header AppendFormat(builder, indent, "{0}: {1}", exception.GetType().Name, exception.Message); builder.AppendLine(); } //recurse inner exception RecurseInnerException(exceptionData, exception, builder, indent); }
/// <summary> /// Triggers recursive parsing of the inner exception of the currently processed exception. /// </summary> internal static void RecurseInnerException(ExceptionData exceptionData, Exception exception, StringBuilder builder, int indent) { //recurse inner exception if (exception.InnerException != null) { AppendLine(builder, indent, "- Inner exception:"); Parse(exceptionData, exception.InnerException, builder, indent + 1); } }
public static LogEntryDto ParseLogEntry(LogEntry entry, string appName, string environment) { var exception = entry.Exception; ExceptionInfo exceptionInfo = null; if (exception != null) { ExceptionData exceptionData = ExceptionParser.GetExceptionData(exception); exceptionInfo = new ExceptionInfo { ExceptionType = exception.GetType().Name, ErrorMessage = exception.Message, ExceptionHash = exceptionData.ExceptionHash, StackTrace = ExceptionParser.Print(exceptionData, true) }; } //transparently assign string values to the message //if there is none, otherwise wrap into an object to //ensure a logged payload is always a JSON object rather //then a scalar. //We don't care about other primitives etc. If somebody //is stupid enough to log an int, it'll just get serialized var payload = entry.Payload; var message = entry.Message; if (payload is string) { if (String.IsNullOrEmpty(message)) { //do not change the original entry - causes issues with multiple sinks message = payload as string; payload = null; } else { payload = new { Message = payload }; } } return(new LogEntryDto { Timestamp = entry.TimestampOverride, AppName = appName, Environment = environment, Level = entry.LogLevel.ToString(), Context = entry.Context ?? "", Message = String.IsNullOrEmpty(message) ? null : message, PayloadType = entry.PayloadType, Payload = payload, ExceptionInfo = exceptionInfo, ContextData = entry.ContextData.Count == 0 ? null : entry.ContextData }); }
/// <summary> /// Recursively processes a given <paramref name="exception"/> and /// returns a verbose string that reflects exception hierarchies and /// stack traces of the submitted root exception and nested (hidden) /// exceptions that are not be included in the stack trace. /// </summary> /// <param name="exception">The root exception to be processed.</param> /// <param name="includeRootExceptionStack">Whether to include the /// stack trace of the root exception in the returned string. Not needed if /// the root exception is being logged anyway.</param> /// <returns>A formatted string reflecting the exception hierarchy.</returns> /// <exception cref="ArgumentNullException">If <paramref name="exception"/> /// is a null reference.</exception> public static string Parse(Exception exception, bool includeRootExceptionStack) { if (exception == null) { throw new ArgumentNullException(nameof(exception)); } ExceptionData ed = GetExceptionData(exception); return(Print(ed, includeRootExceptionStack)); }
/// <summary> /// Processes an <see cref="AggregateException"/> in order to make sure /// all inner exceptions are recursively parsed. /// </summary> internal static void ProcessAggregateException(ExceptionData exceptionData, AggregateException aggregateException, StringBuilder builder, int indent) { AppendFormat(builder, indent, "AggregateException: {0}", aggregateException.Message); builder.AppendLine(); AppendLine(builder, indent, "- Inner exceptions:"); //keep that line above the if statement below, even if there were no Errors //flatten nested aggregate exceptions var flattened = aggregateException.Flatten(); foreach (var innerException in flattened.InnerExceptions) { Parse(exceptionData, innerException, builder, indent + 1); } }
/// <summary> /// Parses a given <paramref name="exception"/>, and returns a /// <see cref="ExceptionData"/> object that contains the root /// exception plus hidden exceptions as well as a well-formatted /// stack trace. /// </summary> /// <exception cref="ArgumentNullException">If <paramref name="exception"/> /// is a null reference.</exception> public static ExceptionData GetExceptionData(Exception exception) { if (exception == null) { throw new ArgumentNullException(nameof(exception)); } var sb = new StringBuilder(); var ed = new ExceptionData(exception); Parse(ed, ed.RootException, sb, 0); ed.FormattedException = sb.ToString(); ed.ExceptionHash = CalculateHash(ed); return(ed); }
/// <summary> /// Gets a hash built based on all exceptions in the submitted <see cref="ExceptionData"/> /// object. Hidden exceptions result in multiple dotted hashes, to enable comparison of /// other hashes that ultimately result in the same exception.<br/> /// Note that if more than 10 exceptions occur, the last hash represents multiple exceptions /// (10th and higher). /// </summary> /// <returns></returns> internal static string CalculateHash(ExceptionData exceptionData) { var list = new List <Exception>(exceptionData.HiddenExceptions); list.Insert(0, exceptionData.RootException); using (MD5 md5 = MD5.Create()) { List <string> sourceStrings = list.Select(e => GetStackTraces(e)).ToList(); //TBD: this routine would just create a single (32 character) hash of all exceptions: //var unhashed = Encoding.Unicode.GetBytes(String.Concat(sourceStrings)); //var hash = md5.ComputeHash(unhashed); //return String.Concat(hash.Select(b => b.ToString("X2"))); //if the list contains more than 10 hashes, trim it down by concatenating some while (sourceStrings.Count > 10) { string st = sourceStrings[9]; sourceStrings.RemoveAt(9); sourceStrings[9] = String.Concat(sourceStrings[9], st); } IEnumerable <string> hashes = from sourceString in sourceStrings let stack = Encoding.Unicode.GetBytes(sourceString) let hash = md5.ComputeHash(stack) select String.Concat(hash.Select(b => b.ToString("X2"))); //an MD5 hash has 32 characters - trim it down to 8 chars, risk of collisions is basically zero //and it's wouldn't even be that much of a problem with proper logging (different context info, too) hashes = hashes.Select(h => h.Substring(0, 8)); return(String.Join(".", hashes)); } }