// 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;
}
}
}