// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Linq; using System.Text.Json; using EpicGames.Core; using Microsoft.Extensions.Logging; namespace UnrealBuildTool { /// /// Base class for exceptions thrown by UBT /// public class BuildException : Exception { /// /// Constructor /// /// The error message to display. public BuildException(string Message) : base(Message) { } /// /// Constructor which wraps another exception /// /// An inner exception to wrap /// The error message to display. public BuildException(Exception? InnerException, string Message) : base(Message, InnerException) { } /// /// Constructor /// /// Formatting string for the error message /// Arguments for the formatting string public BuildException(string Format, params object?[] Arguments) : base(String.Format(Format, Arguments)) { } /// /// Constructor which wraps another exception /// /// The inner exception being wrapped /// Format for the message string /// Format arguments public BuildException(Exception InnerException, string Format, params object?[] Arguments) : base(String.Format(Format, Arguments), InnerException) { } /// /// Log BuildException with a provided ILogger /// /// The ILogger to use to log this exception public virtual void LogException(ILogger Logger) { Logger.LogError(this, "{Ex}", ExceptionUtils.FormatException(this)); Logger.LogDebug(this, "{Ex}", ExceptionUtils.FormatExceptionDetails(this)); } /// /// Returns the string representing the exception. Our build exceptions do not show the callstack since they are used to report known error conditions. /// /// Message for the exception public override string ToString() { return Message; } } /// /// Implementation of that captures a full structured logging event. /// class BuildLogEventException : BuildException { /// /// The event object /// public LogEvent Event { get; } /// /// Constructor /// /// Event to construct from public BuildLogEventException(LogEvent Event) : this(null, Event) { } /// /// Constructor which wraps another exception /// /// The inner exception /// Event to construct from public BuildLogEventException(Exception? InnerException, LogEvent Event) : base(InnerException, Event.ToString()) { this.Event = Event; } /// public BuildLogEventException(string Format, params object[] Arguments) : this(LogEvent.Create(LogLevel.Error, Format, Arguments)) { } /// public BuildLogEventException(Exception? InnerException, string Format, params object[] Arguments) : this(InnerException, LogEvent.Create(LogLevel.Error, default, InnerException, Format, Arguments)) { } /// /// Constructor which wraps another exception /// /// Event id for the error /// Inner exception to wrap /// Structured logging format string /// Argument objects public BuildLogEventException(Exception? InnerException, EventId EventId, string Format, params object[] Arguments) : this(InnerException, LogEvent.Create(LogLevel.Error, EventId, InnerException, Format, Arguments)) { } /// public override void LogException(ILogger Logger) { Logger.Log(Event.Level, Event.Id, Event, this, (s, e) => s.ToString()); Logger.LogDebug(Event.Id, this, "{Ex}", ExceptionUtils.FormatExceptionDetails(this)); } } /// /// Implementation of that will return a unique exit code. /// class CompilationResultException : BuildLogEventException { /// /// The exit code associated with this exception /// public CompilationResult Result { get; } readonly bool HasMessage = true; /// /// Constructor /// /// The resulting exit code public CompilationResultException(CompilationResult Result) : base(LogEvent.Create(LogLevel.Error, "{CompilationResult}", Result)) { HasMessage = false; this.Result = Result; } /// /// Constructor /// /// The resulting exit code /// Event to construct from public CompilationResultException(CompilationResult Result, LogEvent Event) : base(Event) { this.Result = Result; } /// /// Constructor which wraps another exception /// /// The resulting exit code /// The inner exception /// Event to construct from public CompilationResultException(CompilationResult Result, Exception? InnerException, LogEvent Event) : base(InnerException, Event) { this.Result = Result; } /// /// Constructor /// /// The resulting exit code /// Event id for the error /// Formatting string for the error message /// Arguments for the formatting string public CompilationResultException(CompilationResult Result, EventId EventId, string Format, params object[] Arguments) : base(LogEvent.Create(LogLevel.Error, EventId, Format, Arguments)) { this.Result = Result; } /// /// Constructor /// /// The resulting exit code /// Formatting string for the error message /// Arguments for the formatting string public CompilationResultException(CompilationResult Result, string Format, params object[] Arguments) : base(LogEvent.Create(LogLevel.Error, Format, Arguments)) { this.Result = Result; } /// /// Constructor which wraps another exception /// /// The resulting exit code /// The inner exception being wrapped /// Format for the message string /// Format arguments public CompilationResultException(CompilationResult Result, Exception? InnerException, string Format, params object[] Arguments) : base(InnerException, LogEvent.Create(LogLevel.Error, default, InnerException, Format, Arguments)) { this.Result = Result; } /// /// Constructor which wraps another exception /// /// The resulting exit code /// Event id for the error /// Inner exception to wrap /// Structured logging format string /// Argument objects public CompilationResultException(CompilationResult Result, Exception? InnerException, EventId EventId, string Format, params object[] Arguments) : base(InnerException, LogEvent.Create(LogLevel.Error, EventId, InnerException, Format, Arguments)) { this.Result = Result; } /// public override void LogException(ILogger Logger) { if (HasMessage) { Logger.Log(Event.Level, Event.Id, Event, this, (s, e) => s.ToString()); } Logger.LogDebug(Event.Id, this, "{Ex}", ExceptionUtils.FormatExceptionDetails(this)); } } /// /// Extension methods for build exceptions /// public static class BuildExceptionExtensions { /// /// Log Exception with a provided ILogger /// /// The exception to log /// The ILogger to use to log this exception public static void LogException(this Exception ex, ILogger logger) { if (ex is BuildException buildException) { buildException.LogException(logger); } else if (ex is JsonException jsonException) { FileReference source = new FileReference(jsonException.Path ?? jsonException.Source ?? "unknown"); LogValue fileValue = LogValue.SourceFile(source, source.GetFileName()); long line = jsonException.LineNumber ?? 0; logger.LogError(KnownLogEvents.Compiler, "{File}({Line}): error: {Message}", fileValue, line, ExceptionUtils.FormatExceptionDetails(ex)); } else if (ex is AggregateException aggregateException) { logger.LogError(ex, "Unhandled {Count} aggregate exceptions", aggregateException.InnerExceptions.Count); foreach (Exception innerException in aggregateException.InnerExceptions) { innerException.LogException(logger); } } else { logger.LogError(ex, "Unhandled exception: {Ex}", ExceptionUtils.FormatExceptionDetails(ex)); } } /// /// Get the CompilationResult for a provided Exception /// /// The exception to get the result for /// CompilationResult public static CompilationResult GetCompilationResult(this Exception ex) { return (ex as CompilationResultException)?.Result ?? (ex.InnerException as CompilationResultException)?.Result ?? (ex as AggregateException)?.InnerExceptions.OfType().FirstOrDefault()?.Result ?? CompilationResult.OtherCompilationError; } } }