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 <noreply@anthropic.com>
This commit is contained in:
Ednunp
2026-07-14 19:18:18 +01:00
co-authored by Claude Opus 4.8
parent e3e57affb4
commit 91f5fc74c0
3 changed files with 79 additions and 2 deletions
+32
View File
@@ -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)";
}
/// <summary>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.</summary>
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));