I'm using Azure Table Storage to as a storage mechanism. I'm able to write exceptions within a ViewModel to the Azure Table from within the ViewModel, but when I try to add a handler to unhandled exceptions on App.cs
the exception is logged on the Debug output window, but it isn't stored on the Azure Table.
This is my Program.cs
:
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
fonts.AddFont("SF-Pro-Text-Bold.otf", "SFProTextBold");
fonts.AddFont("SF-Pro-Text-Regular.otf", "SFProTextRegular");
fonts.AddFont("materialdesignicons-webfont.ttf", "MaterialDesignIcons");
});
builder.UseMauiApp<App>()
.UseMauiCommunityToolkit()
.RegisterDataServices()
.RegisterViewModels()
.RegisterViews();
builder.Logging.AddSerilog();
var storageConnectionString = "MY_CONNECTION_STRING";
var tableName = "logs";
Log.Logger = new LoggerConfiguration()
.WriteTo.AzureTableStorage(connectionString: storageConnectionString, storageTableName: tableName)
.WriteTo.Debug()
.CreateLogger();
return builder.Build();
}
}
This is my App.cs:
public partial class App : Application
{
private readonly ILogger<App>_logger;
public App(ILogger<App> logger)
{
_logger = logger;
InitializeComponent();
AppDomain.CurrentDomain.UnhandledException += HandleUnhandledException;
MainPage = new AppShell();
}
private void HandleUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
var exception = (Exception)e.ExceptionObject;
_logger.LogCritical(exception, exception.Message);
}
}
}
This is one of my ViewModels where I'm implementing logging:
public class OnboardingViewModel : BaseViewModel
{
private ILogger<OnboardingViewModel> _logger;
public OnboardingViewModel(ILogger<OnboardingViewModel> logger)
{
_logger = logger;
}
private void OnSkipOnboardingCommand()
{
try
{
throw new Exception("Oops! Something went wrong.");
}
catch (Exception e)
{
_logger.LogError(e, e.Message);
}
}
}
When I try this the exception is shown on the output window and saved to the Azure Table:
However if I change the OnSkipOnboardCommand
to this:
private void OnSkipOnboardingCommand()
{
throw new Exception("Oops! Unhandled Exception.");
}
I receive the exception on the HandleUnhandledException
methond on App.cs
. The exception is logged only to the Output Window:
[0:] [07:38:43 FTL] Oops! Unhandled Exception.
System.Exception: Oops! Unhandled Exception.
at MyApp.Mobile.ViewModels.OnboardingViewModel.OnSkipOnboardingCommand() in C:\Users\Developer\Development\MyApp\src\MyApp.Mobile\ViewModels\OnboardingViewModel.cs:line 74
at Microsoft.Maui.Controls.Command.<>c__DisplayClass4_0.<.ctor>b__0(Object o) in D:\a\_work\1\s\src\Controls\src\Core\Command.cs:line 80
at Microsoft.Maui.Controls.Command.Execute(Object parameter) in D:\a\_work\1\s\src\Controls\src\Core\Command.cs:line 123
at Microsoft.Maui.Controls.TapGestureRecognizer.SendTapped(View sender, Func`2 getPosition) in D:\a\_work\1\s\src\Controls\src\Core\TapGestureRecognizer.cs:line 59
at Microsoft.Maui.Controls.Platform.TapGestureHandler.OnTap(Int32 count, MotionEvent e) in D:\a\_work\1\s\src\Controls\src\Core\Platform\Android\TapGestureHandler.cs:line 69
at Microsoft.Maui.Controls.Platform.InnerGestureListener.Android.Views.GestureDetector.IOnGestureListener.OnSingleTapUp(MotionEvent e) in D:\a\_work\1\s\src\Controls\src\Core\Platform\Android\InnerGestureListener.cs:line 157
at Android.Views.GestureDetector.IOnGestureListenerInvoker.n_OnSingleTapUp_Landroid_view_MotionEvent_(IntPtr jnienv, IntPtr native__this, IntPtr native_e) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/obj/Release/net8.0/android-33/mcw/Android.Views.GestureDetector.cs:line 711
at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PPL_Z(_JniMarshal_PPL_Z callback, IntPtr jnienv, IntPtr klazz, IntPtr p0) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs:line 137
at Java.Interop.JniEnvironment.InstanceMethods.CallNonvirtualBooleanMethod(JniObjectReference instance, JniObjectReference type, JniMethodInfo method, JniArgumentValue* args) in /Users/runner/work/1/s/xamarin-android/external/Java.Interop/src/Java.Interop/obj/Release/net7.0/JniEnvironment.g.cs:line 11969
at Java.Interop.JniPeerMembers.JniInstanceMethods.InvokeVirtualBooleanMethod(String encodedMember, IJavaPeerable self, JniArgumentValue* parameters) in /Users/runner/work/1/s/xamarin-android/external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods_Invoke.cs:line 164
at Android.Views.GestureDetector.OnTouchEvent(MotionEvent ev) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/obj/Release/net8.0/android-33/mcw/Android.Views.GestureDetector.cs:line 1639
at Microsoft.Maui.Controls.Platform.TapAndPanGestureDetector.OnTouchEvent(MotionEvent ev) in D:\a\_work\1\s\src\Controls\src\Core\Platform\Android\TapAndPanGestureDetector.cs:line 36
at Microsoft.Maui.Controls.Platform.GesturePlatformManager.OnTouchEvent(MotionEvent e) in D:\a\_work\1\s\src\Controls\src\Core\Platform\GestureManager\GesturePlatformManager.Android.cs:line 83
at Microsoft.Maui.Controls.Platform.GesturePlatformManager.OnPlatformViewTouched(Object sender, TouchEventArgs e) in D:\a\_work\1\s\src\Controls\src\Core\Platform\GestureManager\GesturePlatformManager.Android.cs:line 204
at Android.Views.View.IOnTouchListenerImplementor.OnTouch(View v, MotionEvent e) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/obj/Release/net8.0/android-33/mcw/Android.Views.View.cs:line 4196
at Android.Views.View.IOnTouchListenerInvoker.n_OnTouch_Landroid_view_View_Landroid_view_MotionEvent_(IntPtr jnienv, IntPtr native__this, IntPtr native_v, IntPtr native_e) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/obj/Release/net8.0/android-33/mcw/Android.Views.View.cs:line 4137
at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PPLL_Z(_JniMarshal_PPLL_Z callback, IntPtr jnienv, IntPtr klazz, IntPtr p0, IntPtr p1) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs:line 236
However the exception is not persisted on Azure Storage. This only happens with the exceptions on the HandleUnhandledException
method on the App.cs
.
The fact the the exception is written to the Output Window but not to the Azure Table really confuses me.
Any idea on why this is happening or how I could get Unhandled Exceptions and store them on Azure Tables using Serilog? Should I change something on my code?
What I found
I think there's a few options here:
UnhandledException
event handler. I'm not familiar with how MAIU handles its logging of errors to the console, but perhaps this could be the issue.CreateMauiApp
method, where you're setting up your app and registering the view model, view, etc, it's possible that the OnSkipOnboardingCommand
is being invoked before the HandleUnhandledException
handler has been wired up to the app domain's UnhandledException
event. You can test this by wiring up the UnhandledException
event handler at the start of the CreateMauiApp
method.ExitApplication
property to false
on the UnhandledExceptionEventArgs
object, which will allow it to remain running.I'd also recommend reading Microsoft's documentation for the UnhandledException
event, from memory you won't be able to catch certain classes of critical exceptions with it.