// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace EpicGames.Core
{
///
/// 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 = [];
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 exAgg)
{
Exception? innerException = exAgg.InnerException;
if (innerException != null)
{
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.Replace("\r\n", "\n").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 = [];
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)
{
if (key == null)
{
continue;
}
object? value = currentEx.Data[key];
if (value == null)
{
continue;
}
string valueString;
if(value is List valueList)
{
valueString = String.Format("({0})", String.Join(", ", valueList.Select(x => String.Format("\"{0}\"", x))));
}
else
{
valueString = value.ToString() ?? String.Empty;
}
message.AppendFormat(" data: {0} = {1}", key, valueString);
}
}
}
return message.Replace("\r\n", "\n").ToString();
}
}
}