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:
co-authored by
Claude Opus 4.8
parent
e3e57affb4
commit
91f5fc74c0
@@ -2219,6 +2219,8 @@ public sealed class MainForm : Form
|
|||||||
start.Click += (_, _) => ServiceAction(ServiceControl.StartVerb, "start", confirm: false);
|
start.Click += (_, _) => ServiceAction(ServiceControl.StartVerb, "start", confirm: false);
|
||||||
var stop = new ToolStripMenuItem("Sto&p service") { AccessibleName = "Stop service" };
|
var stop = new ToolStripMenuItem("Sto&p service") { AccessibleName = "Stop service" };
|
||||||
stop.Click += (_, _) => ServiceAction(ServiceControl.StopVerb, "stop", confirm: false);
|
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" };
|
var updateLog = new ToolStripMenuItem("View service update &log") { AccessibleName = "View service update log" };
|
||||||
updateLog.Click += (_, _) => OpenServiceUpdateLog();
|
updateLog.Click += (_, _) => OpenServiceUpdateLog();
|
||||||
|
|
||||||
@@ -2227,7 +2229,7 @@ public sealed class MainForm : Form
|
|||||||
status, new ToolStripSeparator(),
|
status, new ToolStripSeparator(),
|
||||||
configure, new ToolStripSeparator(),
|
configure, new ToolStripSeparator(),
|
||||||
install, uninstall, start, stop, new ToolStripSeparator(),
|
install, uninstall, start, stop, new ToolStripSeparator(),
|
||||||
updateLog,
|
activityLog, updateLog,
|
||||||
});
|
});
|
||||||
serviceMenu.DropDownOpening += (_, _) =>
|
serviceMenu.DropDownOpening += (_, _) =>
|
||||||
{
|
{
|
||||||
@@ -2258,12 +2260,32 @@ public sealed class MainForm : Form
|
|||||||
return serviceMenu;
|
return serviceMenu;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>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.</summary>
|
||||||
|
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()
|
private void OpenServiceUpdateLog()
|
||||||
{
|
{
|
||||||
var path = ServiceStore.UpdateLogPath;
|
var path = ServiceStore.UpdateLogPath;
|
||||||
if (!File.Exists(path))
|
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;
|
return;
|
||||||
}
|
}
|
||||||
try { System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(path) { UseShellExecute = true }); }
|
try { System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(path) { UseShellExecute = true }); }
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ internal static class SelfTest
|
|||||||
RunStep(results, "Service verb gate (normal launch stays load-safe)", ServiceVerbGate);
|
RunStep(results, "Service verb gate (normal launch stays load-safe)", ServiceVerbGate);
|
||||||
RunStep(results, "Service capability probe (feature-detect, cached, safe)", ServiceCapabilityProbe);
|
RunStep(results, "Service capability probe (feature-detect, cached, safe)", ServiceCapabilityProbe);
|
||||||
RunStep(results, "Menu shortcuts don't clash with controls", MenuShortcutsDontClashWithControls);
|
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 failed = results.Count(r => r.Status == "FAIL");
|
||||||
var skipped = results.Count(r => r.Status == "SKIP");
|
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)";
|
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) =>
|
private static bool IsAssemblyLoaded(string simpleName) =>
|
||||||
AppDomain.CurrentDomain.GetAssemblies().Any(a => string.Equals(a.GetName().Name, simpleName, StringComparison.OrdinalIgnoreCase));
|
AppDomain.CurrentDomain.GetAssemblies().Any(a => string.Equals(a.GetName().Name, simpleName, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Linq;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace RemSound.Core;
|
namespace RemSound.Core;
|
||||||
@@ -66,6 +67,28 @@ public static class ServiceStore
|
|||||||
/// <summary>True once a service profile has been configured.</summary>
|
/// <summary>True once a service profile has been configured.</summary>
|
||||||
public static bool IsConfigured() => File.Exists(ProfilePath);
|
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.
|
||||||
|
|
||||||
|
/// <summary>Where the service writes its diagnostic log when logging is on — a <c>logs</c> folder next to
|
||||||
|
/// its profile in ProgramData. Same absolute path for the SYSTEM service and the interactive user.</summary>
|
||||||
|
public static string LogsDirectory => Path.Combine(Directory, "logs");
|
||||||
|
|
||||||
|
/// <summary>The most recently written service log file, or null if none exist yet (logging never ran).
|
||||||
|
/// Never throws.</summary>
|
||||||
|
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) ===
|
// === Running status (written by the service on start, read by the app's Service menu) ===
|
||||||
private static string StatusPath => Path.Combine(Directory, "status.json");
|
private static string StatusPath => Path.Combine(Directory, "status.json");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user