From 18a4fba3ca8e4c67080cf4237817f6e0360f7ff3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=2EB=C3=B6xler?= Date: Wed, 4 Feb 2026 11:44:11 +0100 Subject: [PATCH] feat(debug): add shared test-logging library, auto-start option, and Test Logging panel - Add Sentinel.NLogViewer.TestLogging project (options, NLog config, message generator, runner) - Refactor TestApp to use shared library; keep console entry and key-to-stop behavior - Add TestLoggingService, TestLoggingViewModel, and TestLoggingWindow in main app (DEBUG only) - Add Debug menu with 'Test logging' item and optional AutoStartTestLogging config - Bump main app NLog to 6.0.0 for TestLogging dependency; no functional change in Release --- NLogViewer.sln | 9 +- app/Sentinel.NLogViewer.App/App.xaml.cs | 64 ++++- app/Sentinel.NLogViewer.App/MainWindow.xaml | 2 +- .../MainWindow.xaml.cs | 30 ++- .../Sentinel.NLogViewer.App.csproj | 3 +- .../Services/ConfigurationService.cs | 6 +- .../Services/TestLoggingService.cs | 61 +++++ .../TestLoggingWindow.xaml | 81 ++++++ .../TestLoggingWindow.xaml.cs | 16 ++ .../ViewModels/TestLoggingViewModel.cs | 117 +++++++++ app/Sentinel.NLogViewer.App/appsettings.json | 3 +- .../NLogTestLoggingConfig.cs | 36 +++ .../Sentinel.NLogViewer.TestLogging.csproj | 14 ++ .../TestLogGeneratorRunner.cs | 89 +++++++ .../TestLogMessageGenerator.cs | 121 +++++++++ .../TestLoggingOptions.cs | 25 ++ .../Program.cs | 233 +++--------------- .../Sentinel.NLogViewer.App.TestApp.csproj | 8 +- 18 files changed, 696 insertions(+), 222 deletions(-) create mode 100644 app/Sentinel.NLogViewer.App/Services/TestLoggingService.cs create mode 100644 app/Sentinel.NLogViewer.App/TestLoggingWindow.xaml create mode 100644 app/Sentinel.NLogViewer.App/TestLoggingWindow.xaml.cs create mode 100644 app/Sentinel.NLogViewer.App/ViewModels/TestLoggingViewModel.cs create mode 100644 app/Sentinel.NLogViewer.TestLogging/NLogTestLoggingConfig.cs create mode 100644 app/Sentinel.NLogViewer.TestLogging/Sentinel.NLogViewer.TestLogging.csproj create mode 100644 app/Sentinel.NLogViewer.TestLogging/TestLogGeneratorRunner.cs create mode 100644 app/Sentinel.NLogViewer.TestLogging/TestLogMessageGenerator.cs create mode 100644 app/Sentinel.NLogViewer.TestLogging/TestLoggingOptions.cs diff --git a/NLogViewer.sln b/NLogViewer.sln index 24aa3e0..e4de234 100644 --- a/NLogViewer.sln +++ b/NLogViewer.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 18 VisualStudioVersion = 18.0.11222.15 @@ -13,6 +13,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sentinel.NLogViewer.Wpf.Mat EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sentinel.NLogViewer.App", "app\Sentinel.NLogViewer.App\Sentinel.NLogViewer.App.csproj", "{B2C3D4E5-F6A7-8901-BCDE-F123456789AB}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sentinel.NLogViewer.TestLogging", "app\Sentinel.NLogViewer.TestLogging\Sentinel.NLogViewer.TestLogging.csproj", "{E5F6A7B8-C9D0-1234-EF56-789ABCDE0123}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sentinel.NLogViewer.App.TestApp", "testapp\Sentinel.NLogViewer.App.TestApp\Sentinel.NLogViewer.App.TestApp.csproj", "{C3D4E5F6-A7B8-9012-CDEF-123456789ABC}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sentinel.NLogViewer.App.Tests", "tests\Sentinel.NLogViewer.App.Tests\Sentinel.NLogViewer.App.Tests.csproj", "{D4E5F6A7-B8C9-0123-DEF1-23456789ABCD}" @@ -58,6 +60,10 @@ Global {B2C3D4E5-F6A7-8901-BCDE-F123456789AB}.Debug|Any CPU.Build.0 = Debug|Any CPU {B2C3D4E5-F6A7-8901-BCDE-F123456789AB}.Release|Any CPU.ActiveCfg = Release|Any CPU {B2C3D4E5-F6A7-8901-BCDE-F123456789AB}.Release|Any CPU.Build.0 = Release|Any CPU + {E5F6A7B8-C9D0-1234-EF56-789ABCDE0123}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E5F6A7B8-C9D0-1234-EF56-789ABCDE0123}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E5F6A7B8-C9D0-1234-EF56-789ABCDE0123}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E5F6A7B8-C9D0-1234-EF56-789ABCDE0123}.Release|Any CPU.Build.0 = Release|Any CPU {C3D4E5F6-A7B8-9012-CDEF-123456789ABC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C3D4E5F6-A7B8-9012-CDEF-123456789ABC}.Debug|Any CPU.Build.0 = Debug|Any CPU {C3D4E5F6-A7B8-9012-CDEF-123456789ABC}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -76,6 +82,7 @@ Global {A1B2C3D4-E5F6-7890-ABCD-EF1234567890} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {D1CF3283-A165-477B-A08A-BEBFCD7A7712} = {9F4FA7B3-9ADD-4601-8EC7-8C9A7BD691C7} {B2C3D4E5-F6A7-8901-BCDE-F123456789AB} = {A1B2C3D4-E5F6-7890-ABCD-EF1234567891} + {E5F6A7B8-C9D0-1234-EF56-789ABCDE0123} = {A1B2C3D4-E5F6-7890-ABCD-EF1234567891} {C3D4E5F6-A7B8-9012-CDEF-123456789ABC} = {9F4FA7B3-9ADD-4601-8EC7-8C9A7BD691C7} {D4E5F6A7-B8C9-0123-DEF1-23456789ABCD} = {1EBF655C-A427-41E1-8BFC-6A2429B7EF6E} EndGlobalSection diff --git a/app/Sentinel.NLogViewer.App/App.xaml.cs b/app/Sentinel.NLogViewer.App/App.xaml.cs index 652d44b..6375e58 100644 --- a/app/Sentinel.NLogViewer.App/App.xaml.cs +++ b/app/Sentinel.NLogViewer.App/App.xaml.cs @@ -16,16 +16,20 @@ public partial class App : Application { private IHost? _host; +#if DEBUG + private TestLoggingService? _testLoggingService; +#endif + public App() { // Build the host with dependency injection var hostBuilder = Host.CreateApplicationBuilder(); - + // Register services ConfigureServices(hostBuilder.Services); - + _host = hostBuilder.Build(); - + // Initialize localization service BEFORE XAML is loaded // This ensures the correct culture is set for resource loading var localizationService = _host.Services.GetRequiredService(); @@ -42,31 +46,37 @@ private void ConfigureServices(IServiceCollection services) services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - + // Register services as scoped (one per window/view) services.AddScoped(); services.AddScoped(); - + // Register parsers as transient (new instance each time) services.AddTransient(); services.AddTransient(); services.AddTransient(); - + // Register ViewModels as scoped services.AddScoped(); services.AddScoped(); services.AddScoped(); - + // Register Windows as transient (new window each time) services.AddTransient(); services.AddTransient(); services.AddTransient(); + +#if DEBUG + services.AddSingleton(); + services.AddTransient(); + services.AddTransient(); +#endif } protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); - + // Create and show the main window using DI // Create a scope for the main window and its dependencies if (_host != null) @@ -74,16 +84,50 @@ protected override void OnStartup(StartupEventArgs e) var scope = _host.Services.CreateScope(); var mainWindow = scope.ServiceProvider.GetRequiredService(); MainWindow = mainWindow; - + // Store the scope so it's disposed when the window closes mainWindow.Closed += (s, args) => scope.Dispose(); - + mainWindow.Show(); + +#if DEBUG + _testLoggingService = _host.Services.GetRequiredService(); + if (ShouldAutoStartTestLogging()) + { + _testLoggingService.Start(new Sentinel.NLogViewer.TestLogging.TestLoggingOptions + { + TargetName = "chainsaw", + UdpHost = "127.0.0.1", + UdpPort = 4000, + MessageIntervalMs = 1000, + ExceptionProbability = 0.2 + }); + } +#endif + } + } + +#if DEBUG + private bool ShouldAutoStartTestLogging() + { + if (_host == null) return false; + try + { + var configService = _host.Services.GetRequiredService(); + return configService.LoadConfiguration().AutoStartTestLogging; + } + catch + { + return false; } } +#endif protected override void OnExit(ExitEventArgs e) { +#if DEBUG + _testLoggingService?.Stop(); +#endif // Dispose the host and all registered services _host?.Dispose(); base.OnExit(e); diff --git a/app/Sentinel.NLogViewer.App/MainWindow.xaml b/app/Sentinel.NLogViewer.App/MainWindow.xaml index 8e2ab57..3b6378f 100644 --- a/app/Sentinel.NLogViewer.App/MainWindow.xaml +++ b/app/Sentinel.NLogViewer.App/MainWindow.xaml @@ -32,7 +32,7 @@ - + + { + using var scope = App.ServiceProvider!.CreateScope(); + var window = scope.ServiceProvider.GetRequiredService(); + window.Owner = this; + window.Show(); + }; + var debugMenu = new MenuItem { Header = "_Debug" }; + debugMenu.Items.Add(testLoggingItem); + MainMenu.Items.Insert(MainMenu.Items.Count - 1, debugMenu); } +#endif /// /// Sets the window title with the application version diff --git a/app/Sentinel.NLogViewer.App/Sentinel.NLogViewer.App.csproj b/app/Sentinel.NLogViewer.App/Sentinel.NLogViewer.App.csproj index 0a2aef0..f9b2e77 100644 --- a/app/Sentinel.NLogViewer.App/Sentinel.NLogViewer.App.csproj +++ b/app/Sentinel.NLogViewer.App/Sentinel.NLogViewer.App.csproj @@ -34,7 +34,7 @@ - + @@ -42,6 +42,7 @@ + diff --git a/app/Sentinel.NLogViewer.App/Services/ConfigurationService.cs b/app/Sentinel.NLogViewer.App/Services/ConfigurationService.cs index 57841b6..468dff6 100644 --- a/app/Sentinel.NLogViewer.App/Services/ConfigurationService.cs +++ b/app/Sentinel.NLogViewer.App/Services/ConfigurationService.cs @@ -52,7 +52,8 @@ public AppConfiguration LoadConfiguration() Ports = new List { "udp://0.0.0.0:4000" }, Language = string.Empty, // Empty means auto-detect on first start MaxLogEntriesPerTab = 10000, - AutoStartListening = false + AutoStartListening = false, + AutoStartTestLogging = false }; } @@ -79,5 +80,8 @@ public class AppConfiguration public string Language { get; set; } = string.Empty; // Empty means auto-detect public int MaxLogEntriesPerTab { get; set; } = 10000; public bool AutoStartListening { get; set; } = false; + + /// When true (Debug only), auto-start the test log generator on startup. + public bool AutoStartTestLogging { get; set; } = false; } } diff --git a/app/Sentinel.NLogViewer.App/Services/TestLoggingService.cs b/app/Sentinel.NLogViewer.App/Services/TestLoggingService.cs new file mode 100644 index 0000000..82d8a68 --- /dev/null +++ b/app/Sentinel.NLogViewer.App/Services/TestLoggingService.cs @@ -0,0 +1,61 @@ +using System.ComponentModel; +using Sentinel.NLogViewer.TestLogging; + +namespace Sentinel.NLogViewer.App.Services; + +/// +/// Holds and controls the test log generator runner for the debug panel and optional auto-start. +/// +public sealed class TestLoggingService : INotifyPropertyChanged +{ + private TestLogGeneratorRunner? _runner; + + /// Whether the test log generator is currently running. + public bool IsRunning => _runner?.IsRunning ?? false; + + /// Total normal messages generated (0 if not running). + public int MessageCount => _runner?.MessageCount ?? 0; + + /// Total exception logs generated (0 if not running). + public int ExceptionCount => _runner?.ExceptionCount ?? 0; + + /// + /// Starts the test log generator with the given options. Stops any existing runner first. + /// + public void Start(TestLoggingOptions options) + { + if (options == null) + throw new ArgumentNullException(nameof(options)); + + Stop(); + _runner = new TestLogGeneratorRunner(options); + _runner.CountsChanged += OnCountsChanged; + _runner.Start(); + OnPropertyChanged(nameof(IsRunning)); + OnPropertyChanged(nameof(MessageCount)); + OnPropertyChanged(nameof(ExceptionCount)); + } + + /// Stops and disposes the current runner. + public void Stop() + { + if (_runner == null) return; + _runner.CountsChanged -= OnCountsChanged; + _runner.Dispose(); + _runner = null; + OnPropertyChanged(nameof(IsRunning)); + OnPropertyChanged(nameof(MessageCount)); + OnPropertyChanged(nameof(ExceptionCount)); + } + + private void OnCountsChanged(object? sender, EventArgs e) + { + OnPropertyChanged(nameof(MessageCount)); + OnPropertyChanged(nameof(ExceptionCount)); + } + + public event PropertyChangedEventHandler? PropertyChanged; + + private void OnPropertyChanged(string propertyName) => + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); +} diff --git a/app/Sentinel.NLogViewer.App/TestLoggingWindow.xaml b/app/Sentinel.NLogViewer.App/TestLoggingWindow.xaml new file mode 100644 index 0000000..10325c3 --- /dev/null +++ b/app/Sentinel.NLogViewer.App/TestLoggingWindow.xaml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +