Global exception handling. Threadsafe MessageBoxAlertAdminDialog

This commit is contained in:
Robert McRackan 2022-05-27 13:38:28 -04:00
parent 5a80a0cc06
commit 114925ebce
9 changed files with 56 additions and 20 deletions

View File

@ -256,18 +256,21 @@ namespace AppScaffolding
private static void logStartupState(Configuration config) private static void logStartupState(Configuration config)
{ {
#if DEBUG
var mode = "Debug";
#else
var mode = "Release";
#endif
if (System.Diagnostics.Debugger.IsAttached)
mode += " (Debugger attached)";
// begin logging session with a form feed // begin logging session with a form feed
Log.Logger.Information("\r\n\f"); Log.Logger.Information("\r\n\f");
Log.Logger.Information("Begin. {@DebugInfo}", new Log.Logger.Information("Begin. {@DebugInfo}", new
{ {
AppName = EntryAssembly.GetName().Name, AppName = EntryAssembly.GetName().Name,
Version = BuildVersion.ToString(), Version = BuildVersion.ToString(),
#if DEBUG Mode = mode,
Mode = "Debug",
#else
Mode = "Release",
#endif
LogLevel_Verbose_Enabled = Log.Logger.IsVerboseEnabled(), LogLevel_Verbose_Enabled = Log.Logger.IsVerboseEnabled(),
LogLevel_Debug_Enabled = Log.Logger.IsDebugEnabled(), LogLevel_Debug_Enabled = Log.Logger.IsDebugEnabled(),
LogLevel_Information_Enabled = Log.Logger.IsInformationEnabled(), LogLevel_Information_Enabled = Log.Logger.IsInformationEnabled(),

View File

@ -124,7 +124,7 @@ namespace LibationWinForms.Dialogs
} }
catch (Exception ex) catch (Exception ex)
{ {
MessageBoxLib.ShowAdminAlert("Error attempting to save accounts", "Error saving accounts", ex); MessageBoxLib.ShowAdminAlert(this, "Error attempting to save accounts", "Error saving accounts", ex);
} }
} }

View File

@ -46,7 +46,7 @@ namespace LibationWinForms.Dialogs
if (template is null) if (template is null)
{ {
MessageBoxLib.ShowAdminAlert($"Programming error. {nameof(EditTemplateDialog)} was not created correctly", "Edit template error", new NullReferenceException($"{nameof(template)} is null")); MessageBoxLib.ShowAdminAlert(this, $"Programming error. {nameof(EditTemplateDialog)} was not created correctly", "Edit template error", new NullReferenceException($"{nameof(template)} is null"));
return; return;
} }

View File

@ -77,6 +77,7 @@ namespace LibationWinForms.Dialogs
catch (Exception ex) catch (Exception ex)
{ {
MessageBoxLib.ShowAdminAlert( MessageBoxLib.ShowAdminAlert(
this,
"Error scanning library. You may still manually select books to remove from Libation's library.", "Error scanning library. You may still manually select books to remove from Libation's library.",
"Error scanning library", "Error scanning library",
ex); ex);

View File

@ -40,7 +40,7 @@ namespace LibationWinForms
} }
catch (Exception ex) catch (Exception ex)
{ {
MessageBoxLib.ShowAdminAlert("Error attempting to export your library.", "Error exporting", ex); MessageBoxLib.ShowAdminAlert(this, "Error attempting to export your library.", "Error exporting", ex);
} }
} }
} }

View File

@ -126,6 +126,7 @@ namespace LibationWinForms
catch (Exception ex) catch (Exception ex)
{ {
MessageBoxLib.ShowAdminAlert( MessageBoxLib.ShowAdminAlert(
this,
"Error importing library. Please try again. If this still happens after 2 or 3 tries, stop and contact administrator", "Error importing library. Please try again. If this still happens after 2 or 3 tries, stop and contact administrator",
"Error importing library", "Error importing library",
ex); ex);

View File

@ -4,22 +4,27 @@ using System.Linq;
using System.Windows.Forms; using System.Windows.Forms;
using DataLayer; using DataLayer;
using Dinah.Core.Logging; using Dinah.Core.Logging;
using Dinah.Core.Threading;
using LibationWinForms.Dialogs; using LibationWinForms.Dialogs;
using Serilog; using Serilog;
namespace LibationWinForms namespace LibationWinForms
{ {
public static class MessageBoxLib public static class MessageBoxLib
{ {
/// <summary> /// <summary>
/// Logs error. Displays a message box dialog with specified text and caption. /// Logs error. Displays a message box dialog with specified text and caption.
/// </summary> /// </summary>
/// <param name="synchronizeInvoke">Form calling this method.</param>
/// <param name="text">The text to display in the message box.</param> /// <param name="text">The text to display in the message box.</param>
/// <param name="caption">The text to display in the title bar of the message box.</param> /// <param name="caption">The text to display in the title bar of the message box.</param>
/// <param name="exception">Exception to log</param> /// <param name="exception">Exception to log.</param>
/// <returns>One of the System.Windows.Forms.DialogResult values.</returns> public static void ShowAdminAlert(System.ComponentModel.ISynchronizeInvoke owner, string text, string caption, Exception exception)
public static DialogResult ShowAdminAlert(string text, string caption, Exception exception)
{ {
// for development and debugging, show me what broke!
if (System.Diagnostics.Debugger.IsAttached)
throw exception;
try try
{ {
Serilog.Log.Logger.Error(exception, "Alert admin error: {@DebugText}", new { text, caption }); Serilog.Log.Logger.Error(exception, "Alert admin error: {@DebugText}", new { text, caption });
@ -27,10 +32,22 @@ namespace LibationWinForms
catch { } catch { }
using var form = new MessageBoxAlertAdminDialog(text, caption, exception); using var form = new MessageBoxAlertAdminDialog(text, caption, exception);
return form.ShowDialog();
if (owner is not null)
{
try
{
owner.UIThreadSync(() => form.ShowDialog());
return;
}
catch { }
}
// synchronizeInvoke is null or previous attempt failed. final try
form.ShowDialog();
} }
public static void VerboseLoggingWarning_ShowIfTrue() public static void VerboseLoggingWarning_ShowIfTrue()
{ {
// when turning on debug (and especially Verbose) to share logs, some privacy settings may not be obscured // when turning on debug (and especially Verbose) to share logs, some privacy settings may not be obscured
if (Log.Logger.IsVerboseEnabled()) if (Log.Logger.IsVerboseEnabled())

View File

@ -49,7 +49,7 @@ namespace LibationWinForms
#if !DEBUG #if !DEBUG
checkForUpdate(); checkForUpdate();
#endif #endif
// logging is init'd here
AppScaffolding.LibationScaffolding.RunPostMigrationScaffolding(config); AppScaffolding.LibationScaffolding.RunPostMigrationScaffolding(config);
} }
catch (Exception ex) catch (Exception ex)
@ -58,14 +58,17 @@ namespace LibationWinForms
var body = "An unrecoverable error occurred. Since this error happened before logging could be initialized, this error can not be written to the log file."; var body = "An unrecoverable error occurred. Since this error happened before logging could be initialized, this error can not be written to the log file.";
try try
{ {
MessageBoxLib.ShowAdminAlert(body, title, ex); MessageBoxLib.ShowAdminAlert(null, body, title, ex);
} }
catch catch
{ {
MessageBox.Show($"{body}\r\n\r\n{ex.Message}\r\n\r\n{ex.StackTrace}", title, MessageBoxButtons.OK, MessageBoxIcon.Error); MessageBox.Show($"{body}\r\n\r\n{ex.Message}\r\n\r\n{ex.StackTrace}", title, MessageBoxButtons.OK, MessageBoxIcon.Error);
} }
return; return;
} }
// global exception handling (ShowAdminAlert) attempts to use logging. only call it after logging has been init'd
postLoggingGlobalExceptionHandling();
Application.Run(new Form1()); Application.Run(new Form1());
} }
@ -170,7 +173,7 @@ namespace LibationWinForms
} }
catch (Exception ex) catch (Exception ex)
{ {
MessageBoxLib.ShowAdminAlert("Error checking for update", "Error checking for update", ex); MessageBoxLib.ShowAdminAlert(null, "Error checking for update", "Error checking for update", ex);
return; return;
} }
@ -182,5 +185,16 @@ namespace LibationWinForms
Updater.Run(upgradeProperties.LatestRelease, upgradeProperties.ZipUrl); Updater.Run(upgradeProperties.LatestRelease, upgradeProperties.ZipUrl);
} }
private static void postLoggingGlobalExceptionHandling()
{
// this line is all that's needed for strict handling
AppDomain.CurrentDomain.UnhandledException += (_, e) => MessageBoxLib.ShowAdminAlert(null, "Libation has crashed due to an unhandled error.", "Application crash!", (Exception)e.ExceptionObject);
// these 2 lines makes it graceful. sync (eg in main form's ctor) and thread exceptions will still crash us, but event (sync, void async, Task async) will not
Application.ThreadException += (_, e) => MessageBoxLib.ShowAdminAlert(null, "Libation has encountered an unexpected error.", "Unexpected error", e.Exception);
// I never found a case where including made a difference. I think this enum is default and including it will override app user config file
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
}
} }
} }

View File

@ -43,7 +43,7 @@ namespace LibationWinForms
} }
catch (Exception ex) catch (Exception ex)
{ {
MessageBoxLib.ShowAdminAlert("Error downloading update", "Error downloading update", ex); MessageBoxLib.ShowAdminAlert(null, "Error downloading update", "Error downloading update", ex);
} }
} }
} }