Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion NLogViewer.sln
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@


Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18
VisualStudioVersion = 18.0.11222.15
Expand All @@ -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}"
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
64 changes: 54 additions & 10 deletions app/Sentinel.NLogViewer.App/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<LocalizationService>();
Expand All @@ -42,48 +46,88 @@ private void ConfigureServices(IServiceCollection services)
services.AddSingleton<LocalizationService>();
services.AddSingleton<TextFileFormatDetector>();
services.AddSingleton<TextFileFormatConfigService>();

// Register services as scoped (one per window/view)
services.AddScoped<UdpLogReceiverService>();
services.AddScoped<LogFileParserService>();

// Register parsers as transient (new instance each time)
services.AddTransient<Parsers.Log4JEventParser>();
services.AddTransient<Parsers.PlainTextParser>();
services.AddTransient<Parsers.JsonLogParser>();

// Register ViewModels as scoped
services.AddScoped<MainViewModel>();
services.AddScoped<SettingsViewModel>();
services.AddScoped<LanguageSelectionViewModel>();

// Register Windows as transient (new window each time)
services.AddTransient<MainWindow>();
services.AddTransient<SettingsWindow>();
services.AddTransient<LanguageSelectionWindow>();

#if DEBUG
services.AddSingleton<TestLoggingService>();
services.AddTransient<TestLoggingViewModel>();
services.AddTransient<TestLoggingWindow>();
#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)
{
var scope = _host.Services.CreateScope();
var mainWindow = scope.ServiceProvider.GetRequiredService<MainWindow>();
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<TestLoggingService>();
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<ConfigurationService>();
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);
Expand Down
2 changes: 1 addition & 1 deletion app/Sentinel.NLogViewer.App/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
</Grid.RowDefinitions>

<!-- Menu Bar -->
<Menu Grid.Row="0">
<Menu x:Name="MainMenu" Grid.Row="0">
<MenuItem Header="{helpers:Resource Key=Menu_File, DefaultValue=_File}">
<MenuItem Header="{helpers:Resource Key=Menu_OpenFile, DefaultValue=_Open File...}"
Command="{Binding OpenFileCommand}"
Expand Down
30 changes: 26 additions & 4 deletions app/Sentinel.NLogViewer.App/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Microsoft.Extensions.DependencyInjection;
using Sentinel.NLogViewer.App.ViewModels;
Expand All @@ -19,16 +20,37 @@ public MainWindow(MainViewModel viewModel)
_viewModel = viewModel ?? throw new ArgumentNullException(nameof(viewModel));
InitializeComponent();
DataContext = _viewModel;

// Set window title with version
SetWindowTitleWithVersion();

// Setup keyboard shortcuts
this.InputBindings.Add(new KeyBinding(_viewModel.OpenFileCommand,
this.InputBindings.Add(new KeyBinding(_viewModel.OpenFileCommand,
new KeyGesture(Key.O, ModifierKeys.Control)));
this.InputBindings.Add(new KeyBinding(_viewModel.OpenSettingsCommand,
this.InputBindings.Add(new KeyBinding(_viewModel.OpenSettingsCommand,
new KeyGesture(Key.OemComma, ModifierKeys.Control)));

#if DEBUG
AddDebugMenu();
#endif
}

#if DEBUG
private void AddDebugMenu()
{
var testLoggingItem = new MenuItem { Header = "_Test logging" };
testLoggingItem.Click += (s, _) =>
{
using var scope = App.ServiceProvider!.CreateScope();
var window = scope.ServiceProvider.GetRequiredService<TestLoggingWindow>();
window.Owner = this;
window.Show();
};
var debugMenu = new MenuItem { Header = "_Debug" };
debugMenu.Items.Add(testLoggingItem);
MainMenu.Items.Insert(MainMenu.Items.Count - 1, debugMenu);
}
#endif

/// <summary>
/// Sets the window title with the application version
Expand Down
3 changes: 2 additions & 1 deletion app/Sentinel.NLogViewer.App/Sentinel.NLogViewer.App.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,15 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="NLog" Version="5.2.4" />
<PackageReference Include="NLog" Version="6.0.0" />
<PackageReference Include="System.Reactive" Version="6.0.0" />
<PackageReference Include="SharpVectors" Version="1.8.5" />
<PackageReference Include="System.Text.Json" Version="9.0.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\ui\Sentinel.NLogViewer.Wpf\Sentinel.NLogViewer.Wpf.csproj" />
<ProjectReference Include="..\Sentinel.NLogViewer.TestLogging\Sentinel.NLogViewer.TestLogging.csproj" />
</ItemGroup>

<ItemGroup>
Expand Down
6 changes: 5 additions & 1 deletion app/Sentinel.NLogViewer.App/Services/ConfigurationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ public AppConfiguration LoadConfiguration()
Ports = new List<string> { "udp://0.0.0.0:4000" },
Language = string.Empty, // Empty means auto-detect on first start
MaxLogEntriesPerTab = 10000,
AutoStartListening = false
AutoStartListening = false,
AutoStartTestLogging = false
};
}

Expand All @@ -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;

/// <summary>When true (Debug only), auto-start the test log generator on startup.</summary>
public bool AutoStartTestLogging { get; set; } = false;
}
}
61 changes: 61 additions & 0 deletions app/Sentinel.NLogViewer.App/Services/TestLoggingService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System.ComponentModel;
using Sentinel.NLogViewer.TestLogging;

namespace Sentinel.NLogViewer.App.Services;

/// <summary>
/// Holds and controls the test log generator runner for the debug panel and optional auto-start.
/// </summary>
public sealed class TestLoggingService : INotifyPropertyChanged
{
private TestLogGeneratorRunner? _runner;

/// <summary>Whether the test log generator is currently running.</summary>
public bool IsRunning => _runner?.IsRunning ?? false;

/// <summary>Total normal messages generated (0 if not running).</summary>
public int MessageCount => _runner?.MessageCount ?? 0;

/// <summary>Total exception logs generated (0 if not running).</summary>
public int ExceptionCount => _runner?.ExceptionCount ?? 0;

/// <summary>
/// Starts the test log generator with the given options. Stops any existing runner first.
/// </summary>
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));
}

/// <summary>Stops and disposes the current runner.</summary>
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));
}
81 changes: 81 additions & 0 deletions app/Sentinel.NLogViewer.App/TestLoggingWindow.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<Window x:Class="Sentinel.NLogViewer.App.TestLoggingWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:Sentinel.NLogViewer.App.ViewModels"
mc:Ignorable="d"
Title="Test Logging (Debug)"
Height="380"
Width="400"
WindowStartupLocation="CenterOwner"
ResizeMode="CanResize"
d:DataContext="{d:DesignInstance viewModels:TestLoggingViewModel}">
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>

<GroupBox Header="Parameters" Grid.Row="0" Margin="0,0,0,10">
<Grid Margin="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>

<TextBlock Grid.Row="0" Grid.Column="0" Text="NLog target:" VerticalAlignment="Center" Margin="0,4" />
<ComboBox Grid.Row="0" Grid.Column="1" Margin="0,4"
ItemsSource="{Binding TargetNames}"
SelectedItem="{Binding TargetName, UpdateSourceTrigger=PropertyChanged}"
IsEditable="True"
MinWidth="120" />

<TextBlock Grid.Row="1" Grid.Column="0" Text="UDP host:" VerticalAlignment="Center" Margin="0,4" />
<TextBox Grid.Row="1" Grid.Column="1" Margin="0,4" Padding="4,2"
Text="{Binding UdpHost, UpdateSourceTrigger=PropertyChanged}" />

<TextBlock Grid.Row="2" Grid.Column="0" Text="UDP port:" VerticalAlignment="Center" Margin="0,4" />
<TextBox Grid.Row="2" Grid.Column="1" Margin="0,4" Padding="4,2"
Text="{Binding UdpPort, UpdateSourceTrigger=PropertyChanged}" />

<TextBlock Grid.Row="3" Grid.Column="0" Text="Interval (ms):" VerticalAlignment="Center" Margin="0,4" />
<TextBox Grid.Row="3" Grid.Column="1" Margin="0,4" Padding="4,2"
Text="{Binding MessageIntervalMs, UpdateSourceTrigger=PropertyChanged}" />

<TextBlock Grid.Row="4" Grid.Column="0" Text="Exception % (0-100):" VerticalAlignment="Center" Margin="0,4" />
<TextBox Grid.Row="4" Grid.Column="1" Margin="0,4" Padding="4,2"
Text="{Binding ExceptionProbabilityPercent, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</GroupBox>

<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="0,0,0,10">
<Button Content="Start" Command="{Binding StartCommand}" Padding="12,6" Margin="0,0,8,0" />
<Button Content="Stop" Command="{Binding StopCommand}" Padding="12,6" />
</StackPanel>

<TextBlock Grid.Row="2" Margin="0,0,0,4">
<Run Text="Status: " FontWeight="SemiBold" />
<Run Text="{Binding StatusText, Mode=OneWay}" />
</TextBlock>
<TextBlock Grid.Row="3" Margin="0,0,0,4">
<Run Text="Messages: " />
<Run Text="{Binding MessageCount, Mode=OneWay}" FontWeight="SemiBold" />
</TextBlock>
<TextBlock Grid.Row="4" Margin="0,0,0,4">
<Run Text="Exceptions: " />
<Run Text="{Binding ExceptionCount, Mode=OneWay}" FontWeight="SemiBold" />
</TextBlock>
</Grid>
</Window>
Loading
Loading