Skip to content

Commit af2f196

Browse files
committed
Settings: log level, log folder actions, Support ID reset, OK/Cancel
AppLogger now owns the Serilog configuration so the level can change at runtime and the log files can be released and deleted. Settings offers Off / Errors only / Normal / Detailed, defaulting to Detailed on alpha and beta builds and Normal on stable. The dialog shows the log folder path with Open and Delete buttons, and the Support ID with Copy and Reset; resetting the ID also removes the local report copies that carry the old one. Diagnostics and logging choices are pending until OK; Cancel discards them.
1 parent 96e4360 commit af2f196

7 files changed

Lines changed: 296 additions & 56 deletions

File tree

‎App.xaml.cs‎

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@ protected override async void OnStartup(StartupEventArgs e)
2121
{
2222
base.OnStartup(e);
2323

24-
ConfigureSerilog();
24+
// Settings are read before the host exists because the log level lives there.
25+
var earlySettings = new SettingsService();
26+
AppLogger.Configure(AppLogger.ResolveLevel(earlySettings.Current.LogLevel));
2527

2628
_host = Host.CreateDefaultBuilder()
2729
.UseSerilog()
28-
.ConfigureServices(RegisterServices)
30+
.ConfigureServices(s => RegisterServices(s, earlySettings))
2931
.Build();
3032

3133
await _host.StartAsync();
@@ -93,11 +95,11 @@ protected override async void OnExit(ExitEventArgs e)
9395

9496
// ── Service registration ─────────────────────────────────────────────
9597

96-
private static void RegisterServices(IServiceCollection services)
98+
private static void RegisterServices(IServiceCollection services, SettingsService earlySettings)
9799
{
98100
// Infrastructure
99101
services.AddSingleton<AppLogger>();
100-
services.AddSingleton<SettingsService>();
102+
services.AddSingleton(earlySettings);
101103
services.AddSingleton(new HttpClient { Timeout = TimeSpan.FromSeconds(5) });
102104
services.AddSingleton<TelemetryService>();
103105

@@ -123,26 +125,4 @@ private static void RegisterServices(IServiceCollection services)
123125
services.AddTransient<MainWindow>();
124126
}
125127

126-
// ── Logging setup ────────────────────────────────────────────────────
127-
128-
private static void ConfigureSerilog()
129-
{
130-
var logDir = Path.Combine(
131-
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
132-
BrandingInfo.SuiteName,
133-
BrandingInfo.AppName,
134-
BrandingInfo.LogFolder);
135-
136-
Directory.CreateDirectory(logDir);
137-
138-
Log.Logger = new LoggerConfiguration()
139-
.MinimumLevel.Debug()
140-
.WriteTo.File(
141-
path: Path.Combine(logDir, BrandingInfo.LogFileName),
142-
rollingInterval: RollingInterval.Day,
143-
retainedFileCountLimit: 7,
144-
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] {Message:lj}{NewLine}{Exception}")
145-
.WriteTo.Debug()
146-
.CreateLogger();
147-
}
148128
}

‎CHANGELOG.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ Version numbers: patch (x.x.1) for landed features and fixes, minor (x.1.0) for
1919
telemetry endpoint: a startup report and a per-test-run report with radio model, serial settings,
2020
USB cable identifiers, and test outcomes. Every report is stored locally and viewable under
2121
Help > View collected data. Support ID shown in Help > About.
22-
- Settings window now has content: the diagnostics toggle, data viewer, and privacy link
22+
- Settings window: diagnostics toggle, log level (Off / Errors only / Normal / Detailed —
23+
Detailed by default on test builds), log folder path with Open and Delete buttons, Support ID
24+
with Copy and Reset. Changes apply on OK; Cancel discards them.
2325
- Unhandled exceptions are logged, reported if diagnostics are on, and shown to the operator
2426
before the app closes
2527

‎Logging/AppLogger.cs‎

Lines changed: 89 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,103 @@
11
using Serilog;
2+
using Serilog.Events;
3+
using System.IO;
24

35
namespace RigCheck.Logging;
46

57
/// <summary>
6-
/// Thin wrapper around Serilog for structured logging.
7-
/// Provides a contextual logger for classes that need one.
8-
/// Mirrors the PortPane AppLogger pattern.
8+
/// Owns the Serilog configuration so the level can change at runtime from
9+
/// Settings and the log files can be released and deleted on request.
10+
/// Instance members are a thin contextual wrapper for classes that take
11+
/// a logger by injection.
912
/// </summary>
1013
public class AppLogger
1114
{
12-
public ILogger ForContext<T>() =>
13-
Log.ForContext<T>();
15+
// ── Levels offered in Settings ────────────────────────────────────────
16+
// Stored as these strings; empty means "channel default".
17+
public const string LevelOff = "Off";
18+
public const string LevelErrors = "Error";
19+
public const string LevelNormal = "Information";
20+
public const string LevelDetailed = "Debug";
1421

15-
public ILogger ForContext(string propertyName, object value) =>
16-
Log.ForContext(propertyName, value);
22+
public static string LogDirectory { get; } = Path.Combine(
23+
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
24+
BrandingInfo.SuiteName, BrandingInfo.AppName, BrandingInfo.LogFolder);
1725

18-
public void Info(string messageTemplate, params object[] args) =>
19-
Log.Information(messageTemplate, args);
26+
public static string CurrentLevel { get; private set; } = LevelNormal;
2027

21-
public void Debug(string messageTemplate, params object[] args) =>
22-
Log.Debug(messageTemplate, args);
28+
/// <summary>Test-channel builds log in detail by default; stable logs normally.</summary>
29+
public static string ChannelDefaultLevel =>
30+
BuildInfo.IsAlpha || BuildInfo.IsBeta ? LevelDetailed : LevelNormal;
2331

24-
public void Warn(string messageTemplate, params object[] args) =>
25-
Log.Warning(messageTemplate, args);
32+
/// <summary>Resolve a stored setting ("" = channel default) to a concrete level.</summary>
33+
public static string ResolveLevel(string? stored) =>
34+
string.IsNullOrEmpty(stored) ? ChannelDefaultLevel : stored;
2635

27-
public void Error(Exception ex, string messageTemplate, params object[] args) =>
28-
Log.Error(ex, messageTemplate, args);
36+
// ── Configuration ─────────────────────────────────────────────────────
37+
38+
/// <summary>Build (or rebuild) the global logger at the given level. Safe to call repeatedly.</summary>
39+
public static void Configure(string level)
40+
{
41+
Log.CloseAndFlush();
42+
CurrentLevel = level;
43+
44+
if (level == LevelOff)
45+
{
46+
Log.Logger = new LoggerConfiguration().CreateLogger();
47+
return;
48+
}
49+
50+
Directory.CreateDirectory(LogDirectory);
51+
52+
var minimum = level switch
53+
{
54+
LevelErrors => LogEventLevel.Error,
55+
LevelDetailed => LogEventLevel.Debug,
56+
_ => LogEventLevel.Information,
57+
};
58+
59+
Log.Logger = new LoggerConfiguration()
60+
.MinimumLevel.Is(minimum)
61+
.WriteTo.File(
62+
path: Path.Combine(LogDirectory, BrandingInfo.LogFileName),
63+
rollingInterval: RollingInterval.Day,
64+
retainedFileCountLimit: 7,
65+
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] {Message:lj}{NewLine}{Exception}")
66+
.WriteTo.Debug()
67+
.CreateLogger();
68+
}
69+
70+
/// <summary>
71+
/// Delete every log file. The logger is closed first so the current
72+
/// day's file is released, then reopened at the same level.
73+
/// Returns the number of files removed.
74+
/// </summary>
75+
public static int DeleteLogs()
76+
{
77+
Log.CloseAndFlush();
78+
79+
var removed = 0;
80+
if (Directory.Exists(LogDirectory))
81+
{
82+
foreach (var file in Directory.GetFiles(LogDirectory, "*.log"))
83+
{
84+
try { File.Delete(file); removed++; }
85+
catch { /* a file held by another process stays; the rest go */ }
86+
}
87+
}
88+
89+
Configure(CurrentLevel);
90+
Log.Information("Log files deleted by user ({Count} removed)", removed);
91+
return removed;
92+
}
93+
94+
// ── Contextual wrapper ────────────────────────────────────────────────
95+
96+
public ILogger ForContext<T>() => Log.ForContext<T>();
97+
public ILogger ForContext(string propertyName, object value) => Log.ForContext(propertyName, value);
98+
99+
public void Info(string messageTemplate, params object[] args) => Log.Information(messageTemplate, args);
100+
public void Debug(string messageTemplate, params object[] args) => Log.Debug(messageTemplate, args);
101+
public void Warn(string messageTemplate, params object[] args) => Log.Warning(messageTemplate, args);
102+
public void Error(Exception ex, string messageTemplate, params object[] args) => Log.Error(ex, messageTemplate, args);
29103
}

‎Resources/Strings.resx‎

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,68 @@ Alpha builds stop running after {2} days so that testers always use current code
165165
<value>View collected data…</value>
166166
<comment>Button in Settings that opens the telemetry data viewer.</comment>
167167
</data>
168+
<data name="Settings_LoggingHeading" xml:space="preserve">
169+
<value>Logging</value>
170+
<comment>Section heading in Settings.</comment>
171+
</data>
172+
<data name="Settings_LogLevel" xml:space="preserve">
173+
<value>Write to log files:</value>
174+
<comment>Label before the log level dropdown.</comment>
175+
</data>
176+
<data name="LogLevel_Off" xml:space="preserve">
177+
<value>Off</value>
178+
</data>
179+
<data name="LogLevel_Error" xml:space="preserve">
180+
<value>Errors only</value>
181+
</data>
182+
<data name="LogLevel_Information" xml:space="preserve">
183+
<value>Normal</value>
184+
</data>
185+
<data name="LogLevel_Debug" xml:space="preserve">
186+
<value>Detailed (recommended for test builds)</value>
187+
</data>
188+
<data name="Settings_LogPath" xml:space="preserve">
189+
<value>Log files are stored in:</value>
190+
</data>
191+
<data name="Settings_OpenLogFolder" xml:space="preserve">
192+
<value>Open log folder</value>
193+
<comment>Button. Opens the log folder in Windows Explorer.</comment>
194+
</data>
195+
<data name="Settings_DeleteLogs" xml:space="preserve">
196+
<value>Delete log files</value>
197+
<comment>Button.</comment>
198+
</data>
199+
<data name="Settings_DeleteLogsConfirm" xml:space="preserve">
200+
<value>Delete all {0} log files? This cannot be undone.</value>
201+
<comment>Confirmation question. {0} is the app name.</comment>
202+
</data>
203+
<data name="Settings_DeleteLogsDone" xml:space="preserve">
204+
<value>{0} log file(s) deleted.</value>
205+
<comment>{0} is a number.</comment>
206+
</data>
207+
<data name="Settings_SupportIdHeading" xml:space="preserve">
208+
<value>Support ID</value>
209+
<comment>Section heading in Settings.</comment>
210+
</data>
211+
<data name="Settings_SupportIdHint" xml:space="preserve">
212+
<value>A random identifier created when you first ran the program. It is the only identifier ever sent in diagnostic reports, and lets a support request be matched to them. Resetting it breaks that link for all past reports and deletes their local copies.</value>
213+
</data>
214+
<data name="Settings_ResetId" xml:space="preserve">
215+
<value>Reset Support ID…</value>
216+
<comment>Button.</comment>
217+
</data>
218+
<data name="Settings_ResetIdConfirm" xml:space="preserve">
219+
<value>Create a new Support ID? Past diagnostic reports can no longer be matched to this installation, and their local copies will be deleted.</value>
220+
<comment>Confirmation question.</comment>
221+
</data>
222+
<data name="Common_OK" xml:space="preserve">
223+
<value>OK</value>
224+
<comment>Button. Saves changes and closes the dialog.</comment>
225+
</data>
226+
<data name="Common_Cancel" xml:space="preserve">
227+
<value>Cancel</value>
228+
<comment>Button. Discards changes and closes the dialog.</comment>
229+
</data>
168230
<data name="Menu_HelpViewData" xml:space="preserve">
169231
<value>_View collected data</value>
170232
<comment>Help menu item. Opens the telemetry data viewer.</comment>

‎Services/SettingsService.cs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ public class RigCheckSettings
5252
public string InstallId { get; set; } = Guid.NewGuid().ToString("D");
5353
public bool TelemetryEnabled { get; set; } = false;
5454
public bool TelemetryPrompted { get; set; } = false;
55+
56+
// Logging level; empty means the channel default (see AppLogger).
57+
public string LogLevel { get; set; } = string.Empty;
5558
}
5659

5760
// ── Settings service ──────────────────────────────────────────────────────

‎Views/SettingsDialog.xaml‎

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,19 @@
33
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
44
xmlns:loc="clr-namespace:RigCheck.Localization"
55
Title="{loc:Str Settings_Title}"
6-
Width="480" SizeToContent="Height"
6+
Width="520" SizeToContent="Height"
77
ResizeMode="NoResize"
88
WindowStartupLocation="CenterOwner"
99
ShowInTaskbar="False"
1010
Background="{StaticResource BrushBackground}"
1111
Foreground="{StaticResource BrushForeground}">
1212

13+
<!--
14+
Settings are held as pending values and written only on OK.
15+
Buttons that act on files (open folder, delete logs, reset ID) act
16+
immediately and confirm first where destructive.
17+
-->
18+
1319
<StackPanel Margin="24,20,24,16">
1420

1521
<!-- ── Anonymous diagnostics ── -->
@@ -19,13 +25,12 @@
1925
<CheckBox x:Name="TelemetryCheckBox"
2026
Content="{loc:Str Telemetry_Checkbox}"
2127
Foreground="{StaticResource BrushForeground}" FontSize="13"
22-
Margin="0,0,0,8"
23-
Checked="Telemetry_Changed" Unchecked="Telemetry_Changed" />
28+
Margin="0,0,0,8" />
2429

2530
<TextBlock x:Name="WhatIsSentText" TextWrapping="Wrap"
2631
Foreground="{StaticResource BrushMuted}" FontSize="12" Margin="0,0,0,6" />
2732
<TextBlock Text="{loc:Str Telemetry_WhatIsNotSent}" TextWrapping="Wrap"
28-
Foreground="{StaticResource BrushMuted}" FontSize="12" Margin="0,0,0,12" />
33+
Foreground="{StaticResource BrushMuted}" FontSize="12" Margin="0,0,0,10" />
2934

3035
<StackPanel Orientation="Horizontal" Margin="0,0,0,20">
3136
<Button Content="{loc:Str Settings_ViewData}"
@@ -38,9 +43,58 @@
3843
</Button>
3944
</StackPanel>
4045

41-
<Button Content="{loc:Str Common_Close}"
42-
Style="{StaticResource PrimaryButtonStyle}"
43-
HorizontalAlignment="Right" MinWidth="90"
44-
IsDefault="True" IsCancel="True" />
46+
<!-- ── Logging ── -->
47+
<TextBlock Text="{loc:Str Settings_LoggingHeading}"
48+
FontWeight="SemiBold" FontSize="14" Margin="0,0,0,8" />
49+
50+
<DockPanel Margin="0,0,0,8">
51+
<Label Content="{loc:Str Settings_LogLevel}" Style="{StaticResource FieldLabelStyle}" />
52+
<ComboBox x:Name="LogLevelCombo"
53+
SelectedValuePath="Value" DisplayMemberPath="Label"
54+
Style="{StaticResource ComboBoxStyle}" MinWidth="260" HorizontalAlignment="Left" />
55+
</DockPanel>
56+
57+
<TextBlock Text="{loc:Str Settings_LogPath}"
58+
Foreground="{StaticResource BrushMuted}" FontSize="12" />
59+
<TextBox x:Name="LogPathText"
60+
Style="{StaticResource TextBoxStyle}"
61+
IsReadOnly="True" FontFamily="Consolas" FontSize="12"
62+
Margin="0,4,0,8" />
63+
64+
<StackPanel Orientation="Horizontal" Margin="0,0,0,20">
65+
<Button Content="{loc:Str Settings_OpenLogFolder}"
66+
Style="{StaticResource SecondaryButtonStyle}" Click="OpenLogFolder_Click" Margin="0,0,8,0" />
67+
<Button Content="{loc:Str Settings_DeleteLogs}"
68+
Style="{StaticResource SecondaryButtonStyle}" Click="DeleteLogs_Click" Margin="0,0,12,0" />
69+
<TextBlock x:Name="LogActionStatus" VerticalAlignment="Center"
70+
Foreground="{StaticResource BrushMuted}" FontSize="12" />
71+
</StackPanel>
72+
73+
<!-- ── Support ID ── -->
74+
<TextBlock Text="{loc:Str Settings_SupportIdHeading}"
75+
FontWeight="SemiBold" FontSize="14" Margin="0,0,0,8" />
76+
77+
<DockPanel Margin="0,0,0,6">
78+
<Button DockPanel.Dock="Right" Content="{loc:Str Common_Copy}"
79+
Style="{StaticResource CopyButtonStyle}" Click="CopyId_Click" />
80+
<TextBox x:Name="SupportIdText"
81+
Style="{StaticResource TextBoxStyle}"
82+
IsReadOnly="True" FontFamily="Consolas" FontSize="12" />
83+
</DockPanel>
84+
<TextBlock Text="{loc:Str Settings_SupportIdHint}" TextWrapping="Wrap"
85+
Foreground="{StaticResource BrushMuted}" FontSize="12" Margin="0,0,0,8" />
86+
<Button Content="{loc:Str Settings_ResetId}"
87+
Style="{StaticResource SecondaryButtonStyle}" HorizontalAlignment="Left"
88+
Click="ResetId_Click" Margin="0,0,0,24" />
89+
90+
<!-- ── Footer ── -->
91+
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
92+
<Button Content="{loc:Str Common_OK}"
93+
Style="{StaticResource PrimaryButtonStyle}"
94+
MinWidth="90" IsDefault="True" Click="Ok_Click" Margin="0,0,8,0" />
95+
<Button Content="{loc:Str Common_Cancel}"
96+
Style="{StaticResource SecondaryButtonStyle}"
97+
MinWidth="90" IsCancel="True" />
98+
</StackPanel>
4599
</StackPanel>
46100
</Window>

0 commit comments

Comments
 (0)