// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Tools.DotNETCommon; namespace Tools.DotNETCommon { /// /// Methods for adding context information to exceptions /// public static class ExceptionUtils { /// /// Unique key name for adding context to exceptions thrown inside Epic apps /// const string ContextEntryName = "EpicGames.Context"; /// /// Adds a context message to a list stored on the given exception. Intended to be used in situations where supplying additional context /// for an exception is valuable, but wrapping it would remove information. /// /// The exception that was thrown /// Message to append to the context list public static void AddContext(Exception Ex, string Message) { List Messages = Ex.Data[ContextEntryName] as List; if (Messages == null) { Messages = new List(); Ex.Data[ContextEntryName] = Messages; } Messages.Add(Message); } /// /// Adds a context message to a list stored on the given exception. Intended to be used in situations where supplying additional context /// for an exception is valuable, but wrapping it would remove information. /// /// The exception that was thrown /// Formatting string for /// Message to append to the context list public static void AddContext(Exception Ex, string Format, params object[] Args) { AddContext(Ex, String.Format(Format, Args)); } /// /// Enumerates the context lines from the given exception /// /// The exception to retrieve context from /// Sequence of context lines public static IEnumerable GetContext(Exception Ex) { List Messages = Ex.Data[ContextEntryName] as List; if (Messages != null) { foreach (string Message in Messages) { yield return Message; } } } /// /// Formats an exception for display in the log, including additional lines of context that were attached to it. /// /// The exception to format /// String containing the exception information. May be multiple lines. public static string FormatException(Exception Ex) { StringBuilder ErrorMessage = new StringBuilder(); if (Ex is AggregateException) { Exception InnerException = ((AggregateException)Ex).InnerException; ErrorMessage.Append(InnerException.ToString()); foreach (string Line in GetContext(InnerException)) { ErrorMessage.AppendFormat("\n {0}", Line); } } else { ErrorMessage.Append(Ex.ToString()); } foreach (string Line in GetContext(Ex)) { ErrorMessage.AppendFormat("\n{0}", Line); } return ErrorMessage.ToString(); } /// /// Formats a detailed information about where an exception occurs, including any inner exceptions /// /// The exception to format /// String containing the exception information. May be multiple lines. public static string FormatExceptionDetails(Exception Ex) { List ExceptionStack = new List(); for (Exception CurrentEx = Ex; CurrentEx != null; CurrentEx = CurrentEx.InnerException) { ExceptionStack.Add(CurrentEx); } StringBuilder Message = new StringBuilder(); for (int Idx = ExceptionStack.Count - 1; Idx >= 0; Idx--) { Exception CurrentEx = ExceptionStack[Idx]; Message.AppendFormat("{0}{1}: {2}\n{3}", (Idx == ExceptionStack.Count - 1) ? "" : "Wrapped by ", CurrentEx.GetType().Name, CurrentEx.Message, CurrentEx.StackTrace); if (CurrentEx.Data.Count > 0) { foreach (object Key in CurrentEx.Data.Keys) { object Value = CurrentEx.Data[Key]; string ValueString; if(Value is List) { ValueString = String.Format("({0})", String.Join(", ", ((List)Value).Select(x => String.Format("\"{0}\"", x)))); } else { ValueString = Value.ToString(); } Message.AppendFormat(" data: {0} = {1}", Key, ValueString); } } } return Message.ToString(); } } }