using System.Text; using Titano.Pipeline; namespace Titano.Diagnostics; /// /// Rete di sicurezza per le eccezioni che sfuggono ai gestori. /// /// Un programma a finestre che incontra un'eccezione non gestita sul thread dell'interfaccia /// muore mostrando la finestra di errore di sistema, e con lui muore il lavoro in corso: la /// sequenza caricata, l'analisi appena fatta, le impostazioni non ancora salvate. È un /// prezzo sproporzionato per un difetto che nella maggior parte dei casi riguarda un /// dettaglio del disegno o un compito che stava chiudendo. /// /// Qui l'eccezione viene invece scritta in un file accanto alle impostazioni, con la data e /// la traccia completa, e mostrata in una finestra che dice dove è finita. Il programma /// resta aperto. Se il difetto è grave se ne accorgerà comunque; se non lo è, non ha portato /// via niente — e il file resta da leggere per capire cos'era. /// public static class CrashGuard { private static readonly object Gate = new(); private static bool _installed; public static string LogPath => Path.Combine(AppSettings.DataDirectory, "errori.txt"); public static void Install() { if (_installed) return; _installed = true; Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); Application.ThreadException += (_, e) => Handle(e.Exception, "interfaccia"); AppDomain.CurrentDomain.UnhandledException += (_, e) => { // Qui non si può impedire la chiusura: resta da lasciarne traccia. if (e.ExceptionObject is Exception exception) Record(exception, "dominio"); }; // Un compito il cui esito nessuno ha guardato non deve far cadere il processo, ma // vale la pena saperlo: quasi sempre è una decodifica interrotta a metà. TaskScheduler.UnobservedTaskException += (_, e) => { e.SetObserved(); Record(e.Exception, "compito non osservato"); }; } private static void Handle(Exception exception, string origin) { string path = Record(exception, origin); try { MessageBox.Show( $"Si è verificato un errore imprevisto e Titano lo ha annotato invece di chiudersi.\n\n" + $"{exception.GetType().Name}: {exception.Message}\n\n" + $"La traccia completa è in:\n{path}\n\n" + "Il lavoro in corso è ancora aperto. Conviene comunque salvare l'esportazione " + "e riavviare quando possibile.", "Titano", MessageBoxButtons.OK, MessageBoxIcon.Warning); } catch (Exception) { // Se non si riesce nemmeno a mostrare un messaggio non c'è altro da tentare. } } private static string Record(Exception exception, string origin) { string path = LogPath; try { lock (Gate) { Directory.CreateDirectory(AppSettings.DataDirectory); var text = new StringBuilder(); text.AppendLine(new string('-', 78)); text.AppendLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} origine: {origin}"); text.AppendLine($"versione .NET {Environment.Version}, {Environment.OSVersion}"); text.AppendLine(); text.AppendLine(exception.ToString()); text.AppendLine(); File.AppendAllText(path, text.ToString(), Encoding.UTF8); } } catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) { // Non poter annotare l'errore non è motivo per generarne un secondo. } return path; } }