From 91f5fc74c03c2e934566cf04c8060fdd5b33c776 Mon Sep 17 00:00:00 2001 From: Ednunp <29843396+Ednunp@users.noreply.github.com> Date: Tue, 14 Jul 2026 19:18:18 +0100 Subject: [PATCH] Add "View service log" (diagnostic activity log), distinct from update log Tester couldn't see anything under "View service update log" -- that item only shows the SELF-UPDATE log (written when the service updates itself to a new version), so with no update it's empty. What he actually wanted is the service's ACTIVITY log, which records why the service is or isn't sending ("streaming N sources to M peers", "profile has no WASAPI send sources", "no reachable peers"), but there was no way to open it. - New Service menu item "View service log" opens the newest diagnostic log in ProgramData\RemSound\service\logs. If none exists it explains that service logging must be enabled first (Configure service profile -> Logging), so the path to getting a log is discoverable. - ServiceStore.LogsDirectory + NewestLogFile() to locate it (same absolute path for the SYSTEM service and the interactive user). - Clarified the update-log "nothing yet" message to point at the new item. This also unblocks diagnosing the pre-login send issue: enable service logging, reproduce, then View service log to see the exact reason. New self-test "Service log discovery": folder resolves under the service dir, newest .log is chosen, empty case handled. Gate 32/32. Co-Authored-By: Claude Opus 4.8 --- src/RemSound.App/MainForm.cs | 26 +++++++++++++++++++++++-- src/RemSound.App/SelfTest.cs | 32 +++++++++++++++++++++++++++++++ src/RemSound.Core/ServiceStore.cs | 23 ++++++++++++++++++++++ 3 files changed, 79 insertions(+), 2 deletions(-) diff --git a/src/RemSound.App/MainForm.cs b/src/RemSound.App/MainForm.cs index 243465c..073b33c 100644 --- a/src/RemSound.App/MainForm.cs +++ b/src/RemSound.App/MainForm.cs @@ -2219,6 +2219,8 @@ public sealed class MainForm : Form start.Click += (_, _) => ServiceAction(ServiceControl.StartVerb, "start", confirm: false); var stop = new ToolStripMenuItem("Sto&p service") { AccessibleName = "Stop service" }; stop.Click += (_, _) => ServiceAction(ServiceControl.StopVerb, "stop", confirm: false); + var activityLog = new ToolStripMenuItem("&View service log") { AccessibleName = "View service log" }; + activityLog.Click += (_, _) => OpenServiceLog(); var updateLog = new ToolStripMenuItem("View service update &log") { AccessibleName = "View service update log" }; updateLog.Click += (_, _) => OpenServiceUpdateLog(); @@ -2227,7 +2229,7 @@ public sealed class MainForm : Form status, new ToolStripSeparator(), configure, new ToolStripSeparator(), install, uninstall, start, stop, new ToolStripSeparator(), - updateLog, + activityLog, updateLog, }); serviceMenu.DropDownOpening += (_, _) => { @@ -2258,12 +2260,32 @@ public sealed class MainForm : Form return serviceMenu; } + /// Opens the service's DIAGNOSTIC log — the "what is the service doing / why isn't it sending" + /// activity log, which records the streaming decision on every resume (e.g. "streaming N sources to M + /// peers", or "profile has no WASAPI send sources"). Distinct from the update log (self-updates only). + /// The log is written only while service logging is enabled (Configure service profile → Logging), so if + /// there's nothing here yet, that's the first thing to turn on. + private void OpenServiceLog() + { + var path = ServiceStore.NewestLogFile(); + if (path is null) + { + var msg = ServiceStore.LoadLoggingEnabled() + ? "No service log yet. The service writes one once it starts with logging on — start (or restart) the service, then check back here." + : "No service log yet. Turn on logging first: Service menu → Configure service profile → Logging tab → enable service logging, then start (or restart) the service. The log records what the service does and why it is or isn't sending."; + MessageBox.Show(this, msg, AppName, MessageBoxButtons.OK, MessageBoxIcon.Information); + return; + } + try { System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(path) { UseShellExecute = true }); } + catch (Exception ex) { MessageBox.Show(this, $"Could not open the service log ({path}): {ex.Message}", AppName, MessageBoxButtons.OK, MessageBoxIcon.Warning); } + } + private void OpenServiceUpdateLog() { var path = ServiceStore.UpdateLogPath; if (!File.Exists(path)) { - MessageBox.Show(this, "No service update log yet — it's written the first time the service updates itself.", AppName, MessageBoxButtons.OK, MessageBoxIcon.Information); + MessageBox.Show(this, "No service update log yet — it's written the first time the service updates itself. (For what the service is doing day to day, use \"View service log\" instead.)", AppName, MessageBoxButtons.OK, MessageBoxIcon.Information); return; } try { System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(path) { UseShellExecute = true }); } diff --git a/src/RemSound.App/SelfTest.cs b/src/RemSound.App/SelfTest.cs index 0d205f2..6deb721 100644 --- a/src/RemSound.App/SelfTest.cs +++ b/src/RemSound.App/SelfTest.cs @@ -82,6 +82,7 @@ internal static class SelfTest RunStep(results, "Service verb gate (normal launch stays load-safe)", ServiceVerbGate); RunStep(results, "Service capability probe (feature-detect, cached, safe)", ServiceCapabilityProbe); RunStep(results, "Menu shortcuts don't clash with controls", MenuShortcutsDontClashWithControls); + RunStep(results, "Service log discovery (newest activity log)", ServiceLogDiscovery); var failed = results.Count(r => r.Status == "FAIL"); var skipped = results.Count(r => r.Status == "SKIP"); @@ -1312,6 +1313,37 @@ internal static class SelfTest return "normal launches stay load-safe; all five service verbs recognised (case-insensitive)"; } + /// The Service menu's "View service log" opens the newest diagnostic log — the log that says + /// why the service is or isn't sending (what the tester actually needed; the update log only records + /// self-updates). Verifies the log folder resolves under the service data dir and that the newest .log + /// is picked, with a clean "nothing yet" answer when logging never ran. + private static string? ServiceLogDiscovery() + { + var dir = Path.Combine(Path.GetTempPath(), "remsound-svclog-" + Guid.NewGuid().ToString("N")); + var prev = ServiceStore.TestDirectoryOverride; + try + { + ServiceStore.TestDirectoryOverride = dir; + Check(ServiceStore.LogsDirectory == Path.Combine(dir, "logs"), "the service log folder must sit under the service data dir"); + Check(ServiceStore.NewestLogFile() is null, "with no logs folder there must be no newest log file"); + + Directory.CreateDirectory(ServiceStore.LogsDirectory); + var older = Path.Combine(ServiceStore.LogsDirectory, "RemSound-old.log"); + var newer = Path.Combine(ServiceStore.LogsDirectory, "RemSound-new.log"); + File.WriteAllText(older, "old"); + File.WriteAllText(newer, "new"); + File.SetLastWriteTimeUtc(older, new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Utc)); + File.SetLastWriteTimeUtc(newer, new DateTime(2020, 1, 2, 0, 0, 0, DateTimeKind.Utc)); + Check(ServiceStore.NewestLogFile() == newer, "NewestLogFile must return the most recently written .log"); + return "log folder resolves under the service data dir; newest .log is found; empty case handled"; + } + finally + { + ServiceStore.TestDirectoryOverride = prev; + try { Directory.Delete(dir, true); } catch { /* best effort */ } + } + } + private static bool IsAssemblyLoaded(string simpleName) => AppDomain.CurrentDomain.GetAssemblies().Any(a => string.Equals(a.GetName().Name, simpleName, StringComparison.OrdinalIgnoreCase)); diff --git a/src/RemSound.Core/ServiceStore.cs b/src/RemSound.Core/ServiceStore.cs index d9528a1..0506172 100644 --- a/src/RemSound.Core/ServiceStore.cs +++ b/src/RemSound.Core/ServiceStore.cs @@ -1,3 +1,4 @@ +using System.Linq; using System.Text.Json; namespace RemSound.Core; @@ -66,6 +67,28 @@ public static class ServiceStore /// True once a service profile has been configured. public static bool IsConfigured() => File.Exists(ProfilePath); + // === Diagnostic log (the service's activity log, written ONLY when service logging is enabled) === + // This is the "what is the service actually doing / why isn't it sending" log — distinct from the + // update log below (which only records self-updates). The SYSTEM service writes here because Program.cs + // points its user-data dir at ServiceStore.Directory, so AppConfig.LogsDirectory lands in this folder. + + /// Where the service writes its diagnostic log when logging is on — a logs folder next to + /// its profile in ProgramData. Same absolute path for the SYSTEM service and the interactive user. + public static string LogsDirectory => Path.Combine(Directory, "logs"); + + /// The most recently written service log file, or null if none exist yet (logging never ran). + /// Never throws. + public static string? NewestLogFile() + { + try + { + var dir = new DirectoryInfo(LogsDirectory); + if (!dir.Exists) return null; + return dir.GetFiles("*.log").OrderByDescending(f => f.LastWriteTimeUtc).FirstOrDefault()?.FullName; + } + catch { return null; } + } + // === Running status (written by the service on start, read by the app's Service menu) === private static string StatusPath => Path.Combine(Directory, "status.json");