mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-05 18:57:19 +02:00
135 lines
5.1 KiB
C#
135 lines
5.1 KiB
C#
|
|
// Copyright (c) Microsoft Corporation
|
|||
|
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|||
|
|
// See the LICENSE file in the project root for more information.
|
|||
|
|
|
|||
|
|
using ManagedCommon;
|
|||
|
|
using Microsoft.CmdPal.Core.Common.Helpers;
|
|||
|
|
using Windows.Win32;
|
|||
|
|
using Windows.Win32.Foundation;
|
|||
|
|
using Windows.Win32.UI.WindowsAndMessaging;
|
|||
|
|
using SystemUnhandledExceptionEventArgs = System.UnhandledExceptionEventArgs;
|
|||
|
|
using XamlUnhandledExceptionEventArgs = Microsoft.UI.Xaml.UnhandledExceptionEventArgs;
|
|||
|
|
|
|||
|
|
namespace Microsoft.CmdPal.UI.Helpers;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Global error handler for Command Palette.
|
|||
|
|
/// </summary>
|
|||
|
|
internal sealed partial class GlobalErrorHandler
|
|||
|
|
{
|
|||
|
|
// GlobalErrorHandler is designed to be self-contained; it can be registered and invoked before a service provider is available.
|
|||
|
|
internal void Register(App app)
|
|||
|
|
{
|
|||
|
|
ArgumentNullException.ThrowIfNull(app);
|
|||
|
|
|
|||
|
|
app.UnhandledException += App_UnhandledException;
|
|||
|
|
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
|
|||
|
|
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void App_UnhandledException(object sender, XamlUnhandledExceptionEventArgs e)
|
|||
|
|
{
|
|||
|
|
// Exceptions thrown on the main UI thread are handled here.
|
|||
|
|
if (e.Exception != null)
|
|||
|
|
{
|
|||
|
|
HandleException(e.Exception, Context.MainThreadException);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void CurrentDomain_UnhandledException(object sender, SystemUnhandledExceptionEventArgs e)
|
|||
|
|
{
|
|||
|
|
// Exceptions thrown on background threads are handled here.
|
|||
|
|
if (e.ExceptionObject is Exception ex)
|
|||
|
|
{
|
|||
|
|
HandleException(ex, Context.AppDomainUnhandledException);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void TaskScheduler_UnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
|
|||
|
|
{
|
|||
|
|
// This event is raised only when a faulted Task is garbage-collected
|
|||
|
|
// without its exception being observed. It is NOT raised immediately
|
|||
|
|
// when the Task faults; timing depends on GC finalization.
|
|||
|
|
e.SetObserved();
|
|||
|
|
HandleException(e.Exception, Context.UnobservedTaskException, isRecoverable: true);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void HandleException(Exception ex, Context context, bool isRecoverable = false)
|
|||
|
|
{
|
|||
|
|
Logger.LogError($"Unhandled exception detected ({context})", ex);
|
|||
|
|
|
|||
|
|
if (context == Context.MainThreadException)
|
|||
|
|
{
|
|||
|
|
var error = DiagnosticsHelper.BuildExceptionMessage(ex, null);
|
|||
|
|
var report = $"""
|
|||
|
|
This is an error report generated by Windows Command Palette.
|
|||
|
|
If you are seeing this message, it means the application has encountered an unexpected issue.
|
|||
|
|
You can help us fix it by filing a report at https://aka.ms/powerToysReportBug.
|
|||
|
|
{error}
|
|||
|
|
""";
|
|||
|
|
|
|||
|
|
StoreReport(report, storeOnDesktop: false);
|
|||
|
|
|
|||
|
|
PInvoke.MessageBox(
|
|||
|
|
HWND.Null,
|
|||
|
|
"Command Palette has encountered a fatal error and must close.\n\nAn error report has been saved to your desktop.",
|
|||
|
|
"Unhandled Error",
|
|||
|
|
MESSAGEBOX_STYLE.MB_ICONERROR);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static string? StoreReport(string report, bool storeOnDesktop)
|
|||
|
|
{
|
|||
|
|
// Generate a unique name for the report file; include timestamp and a random zero-padded number to avoid collisions
|
|||
|
|
// in case of crash storm.
|
|||
|
|
var name = FormattableString.Invariant($"CmdPal_ErrorReport_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{Random.Shared.Next(100000):D5}.log");
|
|||
|
|
|
|||
|
|
// Always store a copy in log directory, this way it is available for Bug Report Tool
|
|||
|
|
string? reportPath = null;
|
|||
|
|
if (Logger.CurrentVersionLogDirectoryPath != null)
|
|||
|
|
{
|
|||
|
|
reportPath = Save(report, name, static () => Logger.CurrentVersionLogDirectoryPath);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Optionally store a copy on the desktop for user (in)convenience
|
|||
|
|
if (storeOnDesktop)
|
|||
|
|
{
|
|||
|
|
var path = Save(report, name, static () => Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory));
|
|||
|
|
|
|||
|
|
// show the desktop copy if both succeeded
|
|||
|
|
if (path != null)
|
|||
|
|
{
|
|||
|
|
reportPath = path;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return reportPath;
|
|||
|
|
|
|||
|
|
static string? Save(string reportContent, string reportFileName, Func<string> directory)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
var logDirectory = directory();
|
|||
|
|
Directory.CreateDirectory(logDirectory);
|
|||
|
|
var reportFilePath = Path.Combine(logDirectory, reportFileName);
|
|||
|
|
File.WriteAllText(reportFilePath, reportContent);
|
|||
|
|
return reportFilePath;
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
Logger.LogError("Failed to store exception report", ex);
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private enum Context
|
|||
|
|
{
|
|||
|
|
Unknown = 0,
|
|||
|
|
MainThreadException,
|
|||
|
|
BackgroundThreadException,
|
|||
|
|
UnobservedTaskException,
|
|||
|
|
AppDomainUnhandledException,
|
|||
|
|
}
|
|||
|
|
}
|