2026-04-04 13:30:13 +02:00
|
|
|
using Avalonia;
|
|
|
|
|
using Avalonia.Controls.ApplicationLifetimes;
|
|
|
|
|
using Avalonia.Data.Core.Plugins;
|
|
|
|
|
using Avalonia.Markup.Xaml;
|
|
|
|
|
using AvaloniaSerilogDI.ViewModels;
|
|
|
|
|
using AvaloniaSerilogDI.Views;
|
|
|
|
|
using Common.Core.Extensions;
|
|
|
|
|
using LogViewer.Avalonia;
|
|
|
|
|
using LogViewer.Core;
|
|
|
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
|
using Microsoft.Extensions.Hosting;
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
2026-04-04 15:36:30 +02:00
|
|
|
using MsBox.Avalonia;
|
|
|
|
|
using MsBox.Avalonia.Enums;
|
2026-04-04 13:30:13 +02:00
|
|
|
using RandomLogging.Service;
|
|
|
|
|
using Serilog;
|
|
|
|
|
using Serilog.Sinks.LogView.Core;
|
|
|
|
|
using System;
|
2026-04-04 15:36:30 +02:00
|
|
|
using System.Globalization;
|
|
|
|
|
using System.Linq;
|
2026-04-04 13:30:13 +02:00
|
|
|
using System.Reflection;
|
|
|
|
|
using System.Threading;
|
2026-04-04 15:36:30 +02:00
|
|
|
using Icon = MsBox.Avalonia.Enums.Icon;
|
2026-04-04 13:30:13 +02:00
|
|
|
namespace AvaloniaSerilogDI;
|
|
|
|
|
|
|
|
|
|
public partial class App : Application
|
|
|
|
|
{
|
|
|
|
|
#region Constructors
|
2026-04-04 15:36:30 +02:00
|
|
|
|
2026-04-04 13:30:13 +02:00
|
|
|
public override void Initialize()
|
2026-04-04 15:36:30 +02:00
|
|
|
=> AvaloniaXamlLoader.Load(this);
|
2026-04-04 13:30:13 +02:00
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
2026-04-05 08:00:09 +02:00
|
|
|
|
|
|
|
|
private IHost? Host {get; set;}
|
|
|
|
|
private CancellationTokenSource? CancellationTokenSource { get; set;}
|
2026-04-04 13:30:13 +02:00
|
|
|
|
2026-04-05 08:00:09 +02:00
|
|
|
|
2026-04-04 13:30:13 +02:00
|
|
|
#region Methods
|
|
|
|
|
|
2026-04-04 15:36:30 +02:00
|
|
|
public override void OnFrameworkInitializationCompleted()
|
2026-04-04 13:30:13 +02:00
|
|
|
{
|
|
|
|
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
|
|
|
|
{
|
2026-04-04 15:36:30 +02:00
|
|
|
// Avoid duplicate validations from both Avalonia and the CommunityToolkit.
|
|
|
|
|
// More info: https://docs.avaloniaui.net/docs/guides/development-guides/data-validation#manage-validationplugins
|
|
|
|
|
DisableAvaloniaDataAnnotationValidation();
|
2026-04-04 13:30:13 +02:00
|
|
|
// catch all unhandled errors
|
|
|
|
|
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
|
|
|
|
|
|
2026-04-05 08:00:09 +02:00
|
|
|
HostApplicationBuilder builder = Microsoft.Extensions.Hosting.Host.CreateApplicationBuilder();
|
2026-04-04 13:30:13 +02:00
|
|
|
|
2026-04-04 15:36:30 +02:00
|
|
|
builder
|
|
|
|
|
// Register the Random Logging Service
|
|
|
|
|
.AddRandomBackgroundService()
|
2026-04-04 13:30:13 +02:00
|
|
|
|
2026-04-04 15:36:30 +02:00
|
|
|
// visual debugging tools
|
|
|
|
|
.AddLogViewer();
|
2026-04-04 13:30:13 +02:00
|
|
|
|
2026-04-04 15:36:30 +02:00
|
|
|
IServiceCollection services = builder.Services;
|
2026-04-04 13:30:13 +02:00
|
|
|
|
2026-04-04 15:36:30 +02:00
|
|
|
// Serilog Logger
|
2026-04-04 13:30:13 +02:00
|
|
|
|
2026-04-04 15:36:30 +02:00
|
|
|
// Azure: https://devblogs.microsoft.com/dotnet/asp-net-core-logging/
|
|
|
|
|
// ApplicationInsights: https://github.com/serilog-contrib/serilog-sinks-applicationinsights
|
|
|
|
|
// AmazonCloudWatch: https://blog.ivankahl.com/logging-dotnet-to-aws-cloudwatch-using-serilog/
|
|
|
|
|
// video: https://www.youtube.com/watch?v=nVAkSBpsuTk (How Structured Logging With Serilog Can Make Your Life Easier)
|
|
|
|
|
// video: https://www.youtube.com/watch?v=_iryZxv8Rxw (C# Logging with Serilog and Seq - Structured Logging Made Easy)
|
|
|
|
|
// ps: docker run -d --restart unless-stopped --name seq -e ACCEPT_EULA=Y -v c:\WIP\LogData:/data -p 8081:80 datalust/seq:latest
|
|
|
|
|
// docker rmi datalust/seq --force
|
2026-04-04 13:30:13 +02:00
|
|
|
|
2026-04-04 15:36:30 +02:00
|
|
|
// ref: https://stackoverflow.com/questions/66304596/how-to-dependency-inject-serilog-into-the-rest-of-my-classes-in-net-console-app
|
|
|
|
|
services.AddLogging(configure: cfg =>
|
2026-04-04 13:30:13 +02:00
|
|
|
{
|
2026-04-04 15:36:30 +02:00
|
|
|
Log.Logger = new LoggerConfiguration()
|
|
|
|
|
.ReadFrom.Configuration(builder.Configuration)
|
2026-04-05 08:00:09 +02:00
|
|
|
.WriteTo.DataStoreLoggerSink( dataStoreProvider: () => Host!.Services.TryGetService<ILogDataStore>()!,formatProvider: CultureInfo.InvariantCulture)
|
2026-04-04 15:36:30 +02:00
|
|
|
.CreateLogger();
|
|
|
|
|
|
|
|
|
|
cfg.ClearProviders().AddSerilog(Log.Logger);
|
2026-04-04 13:30:13 +02:00
|
|
|
});
|
|
|
|
|
|
2026-04-04 15:36:30 +02:00
|
|
|
services
|
|
|
|
|
.AddSingleton<MainViewModel>()
|
|
|
|
|
.AddSingleton(service => new MainWindow
|
|
|
|
|
{
|
|
|
|
|
DataContext = service.GetRequiredService<MainViewModel>()
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-05 08:00:09 +02:00
|
|
|
Host = builder.Build();
|
|
|
|
|
CancellationTokenSource = new();
|
2026-04-04 15:36:30 +02:00
|
|
|
|
2026-04-04 13:30:13 +02:00
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
LogStartingMode();
|
|
|
|
|
|
|
|
|
|
// set and show
|
2026-04-05 08:00:09 +02:00
|
|
|
desktop.MainWindow = Host.Services.GetRequiredService<MainWindow>();
|
2026-04-04 13:30:13 +02:00
|
|
|
desktop.ShutdownRequested += OnShutdownRequested;
|
|
|
|
|
|
|
|
|
|
// startup background services
|
2026-04-05 08:00:09 +02:00
|
|
|
_ = Host.StartAsync(CancellationTokenSource.Token);
|
2026-04-04 13:30:13 +02:00
|
|
|
}
|
|
|
|
|
catch (OperationCanceledException)
|
|
|
|
|
{
|
|
|
|
|
// skip
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Log.Fatal(ex, "Application terminated unexpectedly");
|
|
|
|
|
|
|
|
|
|
ShowMessageBox("Unhandled Error", ex.Message);
|
|
|
|
|
|
|
|
|
|
CleanUp();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
base.OnFrameworkInitializationCompleted();
|
|
|
|
|
}
|
2026-04-04 15:36:30 +02:00
|
|
|
private static void DisableAvaloniaDataAnnotationValidation()
|
|
|
|
|
{
|
|
|
|
|
// Get an array of plugins to remove
|
|
|
|
|
var dataValidationPluginsToRemove = BindingPlugins.DataValidators.OfType<DataAnnotationsValidationPlugin>().ToArray();
|
2026-04-04 13:30:13 +02:00
|
|
|
|
2026-04-04 15:36:30 +02:00
|
|
|
// remove each entry found
|
|
|
|
|
foreach (var plugin in dataValidationPluginsToRemove)
|
|
|
|
|
{
|
|
|
|
|
BindingPlugins.DataValidators.Remove(plugin);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
private void OnShutdownRequested(object? sender, ShutdownRequestedEventArgs e)
|
2026-04-04 13:30:13 +02:00
|
|
|
=> CleanUp();
|
|
|
|
|
|
|
|
|
|
private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
|
|
|
|
|
{
|
|
|
|
|
Exception exception = (Exception)e.ExceptionObject;
|
|
|
|
|
|
|
|
|
|
Log.Fatal(exception, "Application terminated unexpectedly");
|
|
|
|
|
ShowMessageBox("Unhandled Error", exception.Message);
|
|
|
|
|
|
|
|
|
|
CleanUp();
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-04 15:36:30 +02:00
|
|
|
private static void ShowMessageBox(string title, string message)
|
2026-04-04 13:30:13 +02:00
|
|
|
{
|
2026-04-04 15:36:30 +02:00
|
|
|
var box = MessageBoxManager.GetMessageBoxStandard(
|
|
|
|
|
"Exception",
|
|
|
|
|
text: message,
|
|
|
|
|
ButtonEnum.Ok,
|
|
|
|
|
Icon.Stop
|
|
|
|
|
);
|
|
|
|
|
_ = box.ShowAsync().GetAwaiter();
|
2026-04-04 13:30:13 +02:00
|
|
|
}
|
|
|
|
|
private void LogStartingMode()
|
|
|
|
|
{
|
|
|
|
|
// Get the Launch mode
|
|
|
|
|
bool isDevelopment = string.Equals(Environment.GetEnvironmentVariable("DOTNET_MODIFIABLE_ASSEMBLIES"), "debug",
|
2026-04-04 15:36:30 +02:00
|
|
|
StringComparison.OrdinalIgnoreCase);
|
2026-04-04 13:30:13 +02:00
|
|
|
|
|
|
|
|
// initialize a logger & EventId
|
2026-04-05 08:00:09 +02:00
|
|
|
ILogger<App> logger = Host!.Services.GetRequiredService<ILogger<App>>();
|
2026-04-04 15:36:30 +02:00
|
|
|
EventId eventId = new(id: 0, name: Assembly.GetEntryAssembly()!.GetName().Name);
|
2026-04-04 13:30:13 +02:00
|
|
|
|
|
|
|
|
// log a test pattern for each log level
|
|
|
|
|
logger.TestPattern(eventId: eventId);
|
|
|
|
|
|
|
|
|
|
// log that we have started...
|
|
|
|
|
logger.Emit(eventId, LogLevel.Information, $"Running in {(isDevelopment ? "Debug" : "Release")} mode");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void CleanUp()
|
|
|
|
|
{
|
|
|
|
|
// tell the background services that we are shutting down
|
2026-04-05 08:00:09 +02:00
|
|
|
_ = Host?.StopAsync(CancellationTokenSource?.Token ?? CancellationToken.None);
|
2026-04-04 13:30:13 +02:00
|
|
|
|
|
|
|
|
// flush logs
|
|
|
|
|
Log.CloseAndFlush();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
}
|