mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-11 22:48:04 -06:00
165 lines
6.1 KiB
C#
165 lines
6.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 Microsoft.CommandPalette.UI.Services;
|
|
using Microsoft.CommandPalette.UI.Services.Helpers;
|
|
using Microsoft.Extensions.Logging;
|
|
using Windows.Win32;
|
|
using Windows.Win32.Foundation;
|
|
using Windows.Win32.UI.WindowsAndMessaging;
|
|
using SystemUnhandledExceptionEventArgs = System.UnhandledExceptionEventArgs;
|
|
using XamlUnhandledExceptionEventArgs = Microsoft.UI.Xaml.UnhandledExceptionEventArgs;
|
|
|
|
namespace Microsoft.CommandPalette.UI.Helpers;
|
|
|
|
/// <summary>
|
|
/// Global error handler for Command Palette.
|
|
/// </summary>
|
|
internal sealed partial class GlobalErrorHandler
|
|
{
|
|
private readonly CmdPalLogger logger;
|
|
|
|
public GlobalErrorHandler(CmdPalLogger logger)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(logger);
|
|
this.logger = logger;
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
private void HandleException(Exception ex, Context context)
|
|
{
|
|
Log_UnhandledException(ex, context);
|
|
|
|
if (context == Context.MainThreadException)
|
|
{
|
|
var error = DiagnosticsHelper.BuildExceptionMessage(ex, null);
|
|
var report = $"""
|
|
This is an error report generated by Microsoft 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);
|
|
|
|
string message;
|
|
string caption;
|
|
try
|
|
{
|
|
message = ResourceLoaderInstance.GetString("GlobalErrorHandler_CrashMessageBox_Message");
|
|
caption = ResourceLoaderInstance.GetString("GlobalErrorHandler_CrashMessageBox_Caption");
|
|
}
|
|
catch
|
|
{
|
|
// The resource loader may not be available if the exception occurred during startup.
|
|
// Fall back to hardcoded strings in that case.
|
|
message = "Command Palette has encountered a fatal error and must close.";
|
|
caption = "Command Palette - Fatal error";
|
|
}
|
|
|
|
PInvoke.MessageBox(
|
|
HWND.Null,
|
|
message,
|
|
caption,
|
|
MESSAGEBOX_STYLE.MB_ICONERROR);
|
|
}
|
|
}
|
|
|
|
private 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, () => logger.CurrentVersionLogDirectoryPath);
|
|
}
|
|
|
|
// Optionally store a copy on the desktop for user (in)convenience
|
|
if (storeOnDesktop)
|
|
{
|
|
var path = Save(report, name, () => Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory));
|
|
|
|
// show the desktop copy if both succeeded
|
|
if (path != null)
|
|
{
|
|
reportPath = path;
|
|
}
|
|
}
|
|
|
|
return reportPath;
|
|
|
|
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)
|
|
{
|
|
Log_FailureToSaveExceptionReport(ex);
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
private enum Context
|
|
{
|
|
Unknown = 0,
|
|
MainThreadException,
|
|
BackgroundThreadException,
|
|
UnobservedTaskException,
|
|
AppDomainUnhandledException,
|
|
}
|
|
|
|
[LoggerMessage(Level = LogLevel.Error, Message = "Unhandled exception detected in context {context}")]
|
|
partial void Log_UnhandledException(Exception exception, Context context);
|
|
|
|
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to save exception report")]
|
|
partial void Log_FailureToSaveExceptionReport(Exception exception);
|
|
}
|