diff --git a/OF DL.Gui/Program.cs b/OF DL.Gui/Program.cs index 599a7b9..af2df39 100644 --- a/OF DL.Gui/Program.cs +++ b/OF DL.Gui/Program.cs @@ -1,4 +1,5 @@ using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; using Microsoft.Extensions.DependencyInjection; using OF_DL.Helpers; using OF_DL.Services; @@ -8,33 +9,98 @@ namespace OF_DL.Gui; public static class Program { + private static int s_hasProcessedUnhandledException; + public static bool HidePrivateInfo { get; private set; } public static void Main(string[] args) { - // Parse command line arguments - HidePrivateInfo = args.Contains("--hide-private-info", StringComparer.OrdinalIgnoreCase); - - ServiceCollection services = new(); - services.AddSingleton(); - ServiceProvider tempProvider = services.BuildServiceProvider(); - ILoggingService loggingService = tempProvider.GetRequiredService(); - - Log.Information("Starting OF DL GUI"); - - // Check if running in Docker and print a message - if (EnvironmentHelper.IsRunningInDocker()) + try { - Console.WriteLine( - "In your web browser, navigate to the port forwarded from your docker container. For instance, if your docker run command included \"-p 8080:8080\", open your web browser to \"http://localhost:8080\"."); - } + // Parse command line arguments + HidePrivateInfo = args.Contains("--hide-private-info", StringComparer.OrdinalIgnoreCase); - BuildAvaloniaApp() - .StartWithClassicDesktopLifetime(args); + ServiceCollection services = new(); + services.AddSingleton(); + ServiceProvider tempProvider = services.BuildServiceProvider(); + ILoggingService loggingService = tempProvider.GetRequiredService(); + + RegisterGlobalExceptionHandlers(); + Log.Information("Starting OF DL GUI"); + + // Check if running in Docker and print a message + if (EnvironmentHelper.IsRunningInDocker()) + { + Console.WriteLine( + "In your web browser, navigate to the port forwarded from your docker container. For instance, if your docker run command included \"-p 8080:8080\", open your web browser to \"http://localhost:8080\"."); + } + + BuildAvaloniaApp() + .StartWithClassicDesktopLifetime(args); + } + catch (Exception ex) + { + HandleUnhandledException(ex, "Program.Main", isTerminating: true); + } + finally + { + Log.CloseAndFlush(); + } } private static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure() .UsePlatformDetect() .LogToTrace(); + + private static void RegisterGlobalExceptionHandlers() + { + AppDomain.CurrentDomain.UnhandledException += (_, eventArgs) => + { + HandleUnhandledException(eventArgs.ExceptionObject as Exception, + "AppDomain.CurrentDomain.UnhandledException", + eventArgs.IsTerminating); + }; + + TaskScheduler.UnobservedTaskException += (_, eventArgs) => + { + HandleUnhandledException(eventArgs.Exception, "TaskScheduler.UnobservedTaskException", + isTerminating: false); + eventArgs.SetObserved(); + }; + } + + private static void HandleUnhandledException(Exception? exception, string source, bool isTerminating) + { + if (Interlocked.Exchange(ref s_hasProcessedUnhandledException, 1) != 0) + { + return; + } + + try + { + if (exception != null) + { + Log.Fatal(exception, "Unhandled exception from {Source}. Terminating={IsTerminating}", + source, isTerminating); + Console.WriteLine($"Unhandled exception from {source}: {exception}"); + } + else + { + Log.Fatal("Unhandled non-exception object from {Source}. Terminating={IsTerminating}", + source, isTerminating); + Console.WriteLine($"Unhandled non-exception object from {source}."); + } + } + finally + { + Log.CloseAndFlush(); + } + + if (!isTerminating && + Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime) + { + desktopLifetime.Shutdown(1); + } + } }