Skip to content

Commit

Permalink
Better formats reflection exceptions
Browse files Browse the repository at this point in the history
If there is a ReflectionTypeLoadException there currently is no way to understand what error actually occurred.

Adds better error output.

Speculatively addresses #241
  • Loading branch information
atruskie committed Jun 26, 2019
1 parent 484f922 commit 429177e
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 17 deletions.
20 changes: 10 additions & 10 deletions src/AnalysisPrograms/AnalyseLongRecordings/AnalyseLongRecording.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ public static void Execute(Arguments arguments)
// the config file so we can't know which analyzer we can use. Thus we will change to using the file name,
// or an argument to resolve the analyzer to load.
// Get analysis name:
IAnalyser2 analyzer = FindAndCheckAnalyser<IAnalyser2>(arguments.AnalysisIdentifier, configFile.Name);
IAnalyser2 analyzer = FindAndCheckAnalyzer<IAnalyser2>(arguments.AnalysisIdentifier, configFile.Name);

// 2. get the analysis config
AnalyzerConfig configuration = analyzer.ParseConfig(configFile);
Expand Down Expand Up @@ -351,7 +351,7 @@ public static void Execute(Arguments arguments)
Log.Success($"Analysis Complete.\nSource={sourceAudio.Name}\nOutput={instanceOutputDirectory.FullName}");
}

public static T FindAndCheckAnalyser<T>(string analysisIdentifier, string partialIdentifier)
public static T FindAndCheckAnalyzer<T>(string analysisIdentifier, string partialIdentifier)
where T : class, IAnalyser2
{
string searchName;
Expand All @@ -369,26 +369,26 @@ public static T FindAndCheckAnalyser<T>(string analysisIdentifier, string partia
fragments.Length >= 2,
$"We need at least two segments to search for an analyzer, supplied name `{partialIdentifier}` is insufficient.");

// assume indentifier (e.g. "Towsey.Acoustic") in first two segments
// assume identifier (e.g. "Towsey.Acoustic") in first two segments
searchName = fragments[0] + "." + fragments[1];
Log.Debug($"Searching for partial analysis identifier name. `{searchName}` extracted from `{partialIdentifier}`");
}

var analysers = AnalysisCoordinator.GetAnalyzers<T>(typeof(MainEntry).Assembly).ToList();
T analyser = analysers.FirstOrDefault(a => a.Identifier == searchName);
if (analyser == null)
var analyzers = AnalysisCoordinator.GetAnalyzers<T>(typeof(MainEntry).Assembly).ToList();
T analyzer = analyzers.FirstOrDefault(a => a.Identifier == searchName);
if (analyzer == null)
{
var error = $"We can not determine what analysis you want to run. We tried to search for \"{searchName}\"";
LoggedConsole.WriteErrorLine(error);
var knownAnalyzers = analysers.Aggregate(string.Empty, (a, i) => a + $" {i.Identifier}\n");
LoggedConsole.WriteLine("Available analysers are:\n" + knownAnalyzers);
var knownAnalyzers = analyzers.Aggregate(string.Empty, (a, i) => a + $" {i.Identifier}\n");
LoggedConsole.WriteLine("Available analyzers are:\n" + knownAnalyzers);

throw new ValidationException($"Cannot find an IAnalyser2 with the name `{searchName}`");
}

Log.Info($"Using analyzer {analyser.Identifier}");
Log.Info($"Using analyzer {analyzer.Identifier}");

return analyser;
return analyzer;
}

/// <summary>
Expand Down
14 changes: 13 additions & 1 deletion src/AnalysisPrograms/CheckEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ namespace AnalysisPrograms
using System.Threading.Tasks;
using Acoustics.Shared;
using Acoustics.Tools.Audio;
using AnalysisBase;
using AnalysisPrograms.AnalyseLongRecordings;
using AnalysisPrograms.Production;
using AnalysisPrograms.Production.Arguments;
Expand All @@ -29,7 +30,18 @@ public class CheckEnvironment
private int Execute(Arguments arguments)
{
var errors = new List<string>();
Log.Info("Checking required executables can be found");
Log.Info("Checking required executables and libraries can be found and loaded");

// this is an important call used in analyze long recordings.
// This call effectively check is we can load types and if files are present (I think)
try
{
AnalysisCoordinator.GetAnalyzers<IAnalyser2>(typeof(MainEntry).Assembly);
}
catch (ReflectionTypeLoadException rtlex)
{
errors.Add(ExceptionLookup.FormatReflectionTypeLoadException(rtlex));
}

// master audio utility checks for available executables
try
Expand Down
46 changes: 46 additions & 0 deletions src/AnalysisPrograms/Production/Exceptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ namespace AnalysisPrograms.Production
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using Acoustics.Shared;
using Acoustics.Shared.ConfigFile;

Expand All @@ -36,6 +38,39 @@ public static class ExceptionLookup

internal static Dictionary<Type, ExceptionStyle> ErrorLevels => levels ?? (levels = CreateExceptionMap());

public static string FormatReflectionTypeLoadException(Exception exception)
{
if (exception == null || !(exception is ReflectionTypeLoadException error))
{
return null;
}

var message = new StringBuilder();
message.Append(
"System.Reflection.ReflectionTypeLoadException: Unable to load one or more of the requested types.\n");

foreach (var inner in error.LoaderExceptions)
{
message.AppendLine("\t- " + inner.Message);
if (inner is FileNotFoundException exFileNotFound)
{
if (!string.IsNullOrEmpty(exFileNotFound.FusionLog))
{
message.AppendLine("Fusion Log:");
message.AppendLine(exFileNotFound.FusionLog);
}
}

message.AppendLine();
}

message.AppendLine(@"
This error message likely means there is something wrong with your install of AP.exe or the required software needed to run AP.exe.
Please report this problem as a bug");

return message.ToString();
}

private static Dictionary<Type, ExceptionStyle> CreateExceptionMap()
{
// WARNING: EXIT CODES CANNOT BE > 255 (for linux compatibility)
Expand Down Expand Up @@ -105,6 +140,15 @@ private static Dictionary<Type, ExceptionStyle> CreateExceptionMap()
Handle = false,
}
},
{
typeof(ReflectionTypeLoadException),
new ExceptionStyle()
{
ErrorCode = 189,
PrintUsage = false,
FormatMessage = FormatReflectionTypeLoadException,
}
},
{
typeof(NoDeveloperMethodException),
new ExceptionStyle { ErrorCode = 199 }
Expand Down Expand Up @@ -152,6 +196,8 @@ public int ErrorCode
public bool Handle { get; set; }

public bool PrintUsage { get; set; }

public Func<Exception, string> FormatMessage { get; set; }
}
}

Expand Down
10 changes: 6 additions & 4 deletions src/AnalysisPrograms/Production/MainEntryUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -416,21 +416,23 @@ private static void CurrentDomainOnUnhandledException(object sender, UnhandledEx

found = found && style.Handle;

// format the message
string message = fatalMessage + (style?.FormatMessage?.Invoke(inner) ?? inner.Message);

// if found, print message only if usage printing disabled
if (found && !style.PrintUsage)
{
// this branch prints the message, but the stack trace is only output in the log
NoConsole.Log.Fatal(fatalMessage, ex);
LoggedConsole.WriteFatalLine(fatalMessage + inner.Message);
NoConsole.Log.Fatal(message, ex);
LoggedConsole.WriteFatalLine(message);
}
else if (found && ex.GetType() != typeof(Exception))
{
// this branch prints the message, and command usage, but the stack trace is only output in the log
NoConsole.Log.Fatal(fatalMessage, ex);
NoConsole.Log.Fatal(message, ex);

// the static CommandLineApplication is not set when CommandLineException is thrown
var command = inner is CommandParsingException exception ? exception.Command.Name : CommandLineApplication?.Name;
var message = fatalMessage + inner.Message;
PrintUsage(message, Usages.Single, command ?? string.Empty);
}
else
Expand Down
2 changes: 1 addition & 1 deletion src/AnalysisPrograms/Recognizers/Base/MultiRecognizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ public static RecognizerResults DoCallRecognition(string name, TimeSpan segmentS
Log.Debug("Looking for recognizer and config files for " + name);

// find an appropriate event recognizer
var recognizer = AnalyseLongRecording.FindAndCheckAnalyser<IEventRecognizer>(name, name + ".yml");
var recognizer = AnalyseLongRecording.FindAndCheckAnalyzer<IEventRecognizer>(name, name + ".yml");

// load up the standard config file for this species
var configurationFile = ConfigFile.Resolve(name + ".yml");
Expand Down
2 changes: 1 addition & 1 deletion src/AnalysisPrograms/Recognizers/Base/RecognizerEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public static void Execute(Arguments arguments)
LoggedConsole.WriteLine("# Output folder: " + outputDirectory);

// find an appropriate event IAnalyzer
IAnalyser2 recognizer = AnalyseLongRecording.FindAndCheckAnalyser<IEventRecognizer>(
IAnalyser2 recognizer = AnalyseLongRecording.FindAndCheckAnalyzer<IEventRecognizer>(
arguments.AnalysisIdentifier,
configFile.Name);

Expand Down

0 comments on commit 429177e

Please sign in to comment.