Enhanced logging, diagnostics, and robustness throughout

Added NLog-based logging and diagnostics to Console and WPF apps, with programmatic configuration and support for debugger output. Refactored apps to use dependency injection and Microsoft.Extensions.Hosting. Improved output layer extraction and fallback logic in detection/recognition, including objectness-class probability multiplication. Added crop saving for diagnostics. Introduced new CLI options for diagnostics. MainViewModel and MainWindow now use DI and log errors. NumberRecognitionEngine supports logging, crop saving, and robust fallback. Added Python diagnostic script. Improved error handling and argument parsing.
This commit is contained in:
MaddoScientisto 2026-02-15 18:06:03 +01:00
commit d2206a00cb
14 changed files with 571 additions and 78 deletions

View file

@ -9,6 +9,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NLog" Version="6.1.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.3" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.3" />
<PackageReference Include="NLog.Extensions.Logging" Version="6.1.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AIFotoONLUS.Core\AIFotoONLUS.Core.csproj" />

View file

@ -2,7 +2,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="clr-namespace:AIFotoONLUS.WPF.Converters"
StartupUri="MainWindow.xaml">
>
<Application.Resources>
<ResourceDictionary>
<converters:InverseBoolConverter x:Key="InverseBoolConverter" />

View file

@ -1,8 +1,78 @@
using System;
using System.Windows;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using NLog;
using NLog.Config;
using NLog.Targets;
using NLog.Extensions.Logging;
using AIFotoONLUS.WPF.ViewModels;
namespace AIFotoONLUS.WPF
{
public partial class App : System.Windows.Application
{
private IHost? _host;
protected override async void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
// configure NLog programmatically to write to console
var nlogConfig = new LoggingConfiguration();
var consoleTarget = new ColoredConsoleTarget("console")
{
Layout = "${longdate}|${level:uppercase=true}|${logger}|${message} ${exception:format=toString}"
};
var debugTarget = new DebuggerTarget("debug")
{
Layout = "${longdate}|${level:uppercase=true}|${logger}|${message} ${exception:format=toString}"
};
nlogConfig.AddTarget(consoleTarget);
nlogConfig.AddTarget(debugTarget);
nlogConfig.AddRule(NLog.LogLevel.Debug, NLog.LogLevel.Fatal, consoleTarget);
nlogConfig.AddRule(NLog.LogLevel.Debug, NLog.LogLevel.Fatal, debugTarget);
LogManager.Configuration = nlogConfig;
// ensure existing loggers pick up the new configuration
LogManager.ReconfigExistingLoggers();
_host = Host.CreateDefaultBuilder()
.ConfigureLogging(logging =>
{
logging.ClearProviders();
// write also to the VS Output (Debug) provider
logging.AddDebug();
logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Debug);
logging.AddNLog();
})
.ConfigureServices((context, services) =>
{
services.AddSingleton<MainViewModel>();
services.AddSingleton<MainWindow>();
})
.Build();
await _host.StartAsync().ConfigureAwait(false);
// emit a startup message to verify logging pipeline
var startupLogger = _host.Services.GetRequiredService<ILogger<App>>();
startupLogger.LogInformation("Host started and logging configured");
var main = _host.Services.GetRequiredService<MainWindow>();
main.Show();
}
protected override async void OnExit(ExitEventArgs e)
{
if (_host != null)
{
await _host.StopAsync().ConfigureAwait(false);
_host.Dispose();
}
// flush NLog to ensure all messages are written before exit
try { LogManager.Flush(); } catch { }
base.OnExit(e);
}
}
}
}

View file

@ -6,10 +6,11 @@ namespace AIFotoONLUS.WPF
{
public partial class MainWindow : Window
{
private MainViewModel _vm = new();
private readonly MainViewModel _vm;
public MainWindow()
public MainWindow(MainViewModel vm)
{
_vm = vm ?? throw new ArgumentNullException(nameof(vm));
InitializeComponent();
DataContext = _vm;
}
@ -17,7 +18,7 @@ namespace AIFotoONLUS.WPF
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
_vm.Dispose();
(_vm as IDisposable)?.Dispose();
}
}
}

View file

@ -1,4 +1,5 @@
using AIFotoONLUS.Core;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Collections.ObjectModel;
@ -82,8 +83,14 @@ namespace AIFotoONLUS.WPF.ViewModels
public RelayCommand ProcessCommand { get; }
public RelayCommand CancelCommand { get; }
public MainViewModel()
private readonly ILogger<MainViewModel>? _logger;
private readonly ILoggerFactory? _loggerFactory;
public MainViewModel(ILogger<MainViewModel>? logger, ILoggerFactory? loggerFactory)
{
_logger = logger;
_loggerFactory = loggerFactory;
BrowseImagesCommand = new RelayCommand(_ => BrowseFolder(isModel: false));
BrowseModelsCommand = new RelayCommand(_ => BrowseFolder(isModel: true));
LoadModelsCommand = new RelayCommand(_ => LoadModels());
@ -91,9 +98,16 @@ namespace AIFotoONLUS.WPF.ViewModels
CancelCommand = new RelayCommand(_ => Cancel(), _ => IsProcessing);
// load prefs
var prefs = Preferences.Load();
if (!string.IsNullOrWhiteSpace(prefs.imagesDir)) ImagesDirectory = prefs.imagesDir;
if (!string.IsNullOrWhiteSpace(prefs.modelsDir)) ModelsDirectory = prefs.modelsDir;
try
{
var prefs = Preferences.Load();
if (!string.IsNullOrWhiteSpace(prefs.imagesDir)) ImagesDirectory = prefs.imagesDir;
if (!string.IsNullOrWhiteSpace(prefs.modelsDir)) ModelsDirectory = prefs.modelsDir;
}
catch (Exception ex)
{
_logger?.LogWarning(ex, "Failed to load preferences");
}
}
private void BrowseFolder(bool isModel)
@ -121,13 +135,14 @@ namespace AIFotoONLUS.WPF.ViewModels
ConfidenceThreshold = 0.5,
NmsThreshold = 0.4
};
_engine = new NumberRecognitionEngine(_cfg);
var logger = _loggerFactory?.CreateLogger<NumberRecognitionEngine>();
_engine = new NumberRecognitionEngine(_cfg, logger);
Status = "Models loaded";
Preferences.Save(ImagesDirectory, ModelsDirectory);
}
catch (Exception ex)
{
_logger?.LogError(ex, "Error loading models");
System.Windows.MessageBox.Show(ex.Message, "Error loading models", MessageBoxButton.OK, MessageBoxImage.Error);
Status = "Error loading models";
}