refactor(agent): unified kebab-case log file naming + date-less agent.log

The agent log directory mixed three conventions (agent_20260817.log,
bootstrap_agent.log, msi-bootstrap.log) while everything else in the
diagnostics ZIP is already kebab-case. All log files now follow
<component>-<content>.log:

  agent.log               (was agent_YYYYMMDD.log; segments agent-NNN.log)
  bootstrap-script.log    (was bootstrap_agent.log; script v2.4)
  bootstrap-msi.log       (was msi-bootstrap.log)
  crash-<utc>.log         (was crash_<utc>.log)
  gather-rules-debug.log  (was gather_rules_debug.log)
  ime-pattern-matches.log (was ime_pattern_matches.log)

The date is dropped from the main log: the agent only lives for one
enrollment, and date rotation split the chronology at local midnight -
which can fall mid-enrollment while the clock is still on the OOBE
default timezone. The existing 50MB size rotation stays as the growth
bound. Legacy dated files are ignored by the rotation probe; crash
detection still picks up legacy crash_*.log once so an in-place
self-update cannot lose a pending crash classification.

Diag collectors glob *.log and need no change. MCP zip-map and docs
note the legacy names for archives from pre-rename agents. Settings UI
hint paths updated. MSI rebuild + bootstrap script publish (v2.4) +
MCP redeploy required to ship.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Oliver Kieselbach
2026-08-18 02:55:06 +02:00
parent a2c4949473
commit 6ebdfa41af
19 changed files with 70 additions and 57 deletions
+1 -1
View File
@@ -44,7 +44,7 @@ tenant-config report.
`RemoteConfigMerger` projects the bool onto a runtime path (same pattern as
`EnableImeMatchLog`): `true` → `Constants.GatherRuleDebugLogPath` =
`%ProgramData%\AutopilotMonitor\Logs\gather_rules_debug.log`; `false` → null, unless a
`%ProgramData%\AutopilotMonitor\Logs\gather-rules-debug.log`; `false` → null, unless a
`--gather-debug-log <path>` CLI override supplied a non-default path. The field is not
security-sensitive and deliberately **survives the offline config cache**.
+1 -1
View File
@@ -53,7 +53,7 @@ every line; the spool fsyncs only items flagged `RequiresImmediateFlush`.
| Event sequence | `State\event-sequence.json` | `{ LastAssignedSequence }` | atomic | none; survives WhiteGlove Part-2 resume |
| Spool | `Spool\spool.jsonl` | JSONL (`TelemetryItem`/line) | only `RequiresImmediateFlush` items | none — never truncated, cursor skips uploaded items |
| Upload cursor | `Spool\upload-cursor.json` | `{ LastUploadedItemId }` | atomic | idempotent, never regresses |
| Agent trace log | `Logs\agent_YYYYMMDD[_NNN].log` | text `[utc] [LEVEL] msg` | no | 50 MB → suffix rotation; consumed by `/diag-agent-log` |
| Agent trace log | `Logs\agent.log` (segments `agent-NNN.log`) | text `[utc] [LEVEL] msg` | no | 50 MB → suffix rotation; consumed by `/diag-agent-log` |
| Crash dump + record | `Crashes\<stem>.dmp` + `.json` | MiniDump + JSON | n/a (process dying) | 5 files / 7 days |
| Startup-event gate | `State\startup-event-state.json` | JSON dict | no (atomic) | cross-reboot dedup of one-shot probes |
| Bandwidth state | `State\bandwidth-state.json` | JSON samples/counters | no (atomic) | reboot-survivor for the DO bandwidth estimate |
+1 -1
View File
@@ -146,7 +146,7 @@ snapshots) are executed by the `EffectRunner` — the reducer itself is pure. De
| `\Agent\` | deployed binaries |
| `\State\` | decision persistence (SignalLog, Journal, Snapshot), `enrollment-complete.marker`, `final-status.json`, self-update state, `.quarantine/`, `.part1-<ts>/` |
| `\Spool\` | telemetry spool (offline queue) |
| `\Logs\` | agent trace log, `crash_*.log`, `ime_pattern_matches.log` |
| `\Logs\` | agent trace log (`agent.log`), `crash-*.log`, `ime-pattern-matches.log` |
| `\Config\` | `remote-config.json` cache |
| `\Agent-Update\`, `\Updates\` | self-update staging/download |
+1
View File
@@ -2,6 +2,7 @@
## 2026-08-18
* **Update**: `agent/logs-and-persistence.md`, `agent/overview.md`, `agent/gather-rule-debug-log.md` — log files renamed to unified kebab-case: `agent.log` (date rotation dropped; size segments `agent-NNN.log`), `bootstrap-script.log` (was `bootstrap_agent.log`), `bootstrap-msi.log` (was `msi-bootstrap.log`), `crash-*.log`, `gather-rules-debug.log`, `ime-pattern-matches.log`. Old names still appear in archives from pre-rename agents.
* **New**: `agent/registry-app-state-pillar.md` — the registry second pillar for IME app state (audit follow-up): snapshot-and-diff watcher on `HKLM\...\IntuneManagementExtension` (Win32Apps / EspTrackingWin32Apps / StatusServiceReports), `registry_app_state` + `app_state_reconciliation` events, silent startup baseline, WDP reuse notes.
## 2026-08-17
@@ -95,6 +95,8 @@
Organizations). SKIP-safe when the API is absent (< 1809).
Removed legacy Content-MD5 fallback; SHA-256 manifest verification is
now mandatory (no hash -> bootstrap aborts, no unverified install).
2026-08-18 v2.4 Renamed log file bootstrap_agent.log -> bootstrap-script.log
(unified kebab-case log naming across the agent log directory).
2026-05-09 v2.0 Generic bootstrap: agent owns its own defaults (e.g. 600 s
TenantId-wait), so the script only calls `--install` plain.
Hardened post-install with a 10 s runtime-process verify.
@@ -126,13 +128,13 @@ param(
)
# Script version (bump on meaningful changes; see .CHANGELOG above)
$ScriptVersion = "2.3"
$ScriptVersion = "2.4"
# Configuration - Everything in ProgramData for easy cleanup
$AgentBasePath = "$env:ProgramData\AutopilotMonitor"
$AgentBinPath = "$AgentBasePath\Agent"
$AgentLogPath = "$AgentBasePath\Logs"
$LogFile = "$AgentLogPath\bootstrap_agent.log"
$LogFile = "$AgentLogPath\bootstrap-script.log"
# Write-Host (NOT Write-Output): log calls happen inside functions that return
# decision objects; Write-Output would leak every log line into those return values.
@@ -15,7 +15,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Logging
/// </summary>
public sealed class AgentLoggerTests
{
private static string TodayBaseName => $"agent_{DateTime.Now:yyyyMMdd}";
private const string BaseName = "agent";
[Fact]
public void Log_writes_to_unsuffixed_base_file_when_dir_is_empty()
@@ -25,7 +25,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Logging
logger.Info("hello");
var basePath = Path.Combine(tmp.Path, TodayBaseName + ".log");
var basePath = Path.Combine(tmp.Path, BaseName + ".log");
Assert.True(File.Exists(basePath), $"expected base file at {basePath}");
Assert.Contains("hello", File.ReadAllText(basePath));
}
@@ -39,17 +39,17 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Logging
// Each line ~80 bytes after timestamp/severity envelope; 30 lines clears 1 KB
// even at the smallest payload. The first lines fill the base file; once the
// FileInfo.Length read sees >= 1024 the next call rotates to _002.
// FileInfo.Length read sees >= 1024 the next call rotates to -002.
for (int i = 0; i < 60; i++)
{
logger.Info($"line-{i:D4}-padded-with-some-extra-context-to-cross-1KB-quickly");
}
var files = Directory.GetFiles(tmp.Path, TodayBaseName + "*.log").OrderBy(s => s).ToArray();
var files = Directory.GetFiles(tmp.Path, BaseName + "*.log").OrderBy(s => s).ToArray();
Assert.True(files.Length >= 2, $"expected at least 2 segments, got: {string.Join(",", files.Select(Path.GetFileName))}");
// The unsuffixed file is the first segment; rotated segments use _002 onward.
var rotated = files.FirstOrDefault(p => Path.GetFileNameWithoutExtension(p).EndsWith("_002"));
// The unsuffixed file is the first segment; rotated segments use -002 onward.
var rotated = files.FirstOrDefault(p => Path.GetFileNameWithoutExtension(p).EndsWith("-002"));
Assert.NotNull(rotated);
}
@@ -64,8 +64,8 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Logging
logger.Info($"pad-{i:D4}-content-content-content-content-content-content");
}
var rotatedPath = Path.Combine(tmp.Path, TodayBaseName + "_002.log");
Assert.True(File.Exists(rotatedPath), "expected agent_YYYYMMDD_002.log to exist");
var rotatedPath = Path.Combine(tmp.Path, BaseName + "-002.log");
Assert.True(File.Exists(rotatedPath), "expected agent-002.log to exist");
var first = File.ReadAllLines(rotatedPath).FirstOrDefault();
Assert.NotNull(first);
Assert.Contains("AgentLogger: rotated", first);
@@ -75,23 +75,23 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Logging
public void Log_continues_existing_rotation_on_restart_without_overwriting()
{
using var tmp = new TempDirectory();
// Pre-seed: looks like an earlier process already rotated up to _003.
File.WriteAllText(Path.Combine(tmp.Path, TodayBaseName + ".log"), "older-base\n");
File.WriteAllText(Path.Combine(tmp.Path, TodayBaseName + "_002.log"), "older-002\n");
File.WriteAllText(Path.Combine(tmp.Path, TodayBaseName + "_003.log"), "older-003\n");
// Pre-seed: looks like an earlier process already rotated up to -003.
File.WriteAllText(Path.Combine(tmp.Path, BaseName + ".log"), "older-base\n");
File.WriteAllText(Path.Combine(tmp.Path, BaseName + "-002.log"), "older-002\n");
File.WriteAllText(Path.Combine(tmp.Path, BaseName + "-003.log"), "older-003\n");
var logger = new AgentLogger(tmp.Path, AgentLogLevel.Info, maxFileSizeBytes: 1024L * 1024);
logger.Info("after-restart");
// New entries must land in the highest existing segment (_003), not in a fresh
// New entries must land in the highest existing segment (-003), not in a fresh
// base file. Older content in _003 must be preserved.
var resumed = File.ReadAllText(Path.Combine(tmp.Path, TodayBaseName + "_003.log"));
var resumed = File.ReadAllText(Path.Combine(tmp.Path, BaseName + "-003.log"));
Assert.Contains("older-003", resumed);
Assert.Contains("after-restart", resumed);
// Older segments are left alone.
Assert.Equal("older-base\n", File.ReadAllText(Path.Combine(tmp.Path, TodayBaseName + ".log")));
Assert.Equal("older-002\n", File.ReadAllText(Path.Combine(tmp.Path, TodayBaseName + "_002.log")));
Assert.Equal("older-base\n", File.ReadAllText(Path.Combine(tmp.Path, BaseName + ".log")));
Assert.Equal("older-002\n", File.ReadAllText(Path.Combine(tmp.Path, BaseName + "-002.log")));
}
[Fact]
@@ -105,7 +105,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Logging
// as plain ASCII, while genuine non-ASCII payload stays untouched.
logger.Info("check failed \u2014 range 1\u20132 \u2192 done\u2026 \u2018a\u2019 \u201Cb\u201D nb\u00A0sp f\u00FCr M\u00FCller");
var text = File.ReadAllText(Path.Combine(tmp.Path, TodayBaseName + ".log"));
var text = File.ReadAllText(Path.Combine(tmp.Path, BaseName + ".log"));
Assert.Contains("check failed - range 1-2 -> done... 'a' \"b\" nb sp f\u00FCr M\u00FCller", text);
}
@@ -115,8 +115,8 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Logging
using var tmp = new TempDirectory();
var logger = new AgentLogger(tmp.Path, AgentLogLevel.Info, maxFileSizeBytes: 1024L * 1024);
// Hold an exclusive lock on today's file to force AppendAllText to throw.
var lockedPath = Path.Combine(tmp.Path, TodayBaseName + ".log");
// Hold an exclusive lock on the log file to force AppendAllText to throw.
var lockedPath = Path.Combine(tmp.Path, BaseName + ".log");
File.WriteAllText(lockedPath, ""); // ensure exists
using var holder = new FileStream(lockedPath, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
@@ -25,7 +25,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Monitoring.Gather
public void Dispose() => _tmp.Dispose();
private string LogPath => Path.Combine(_tmp.Path, "sub", "gather_rules_debug.log");
private string LogPath => Path.Combine(_tmp.Path, "sub", "gather-rules-debug.log");
[Fact]
public void Write_produces_pipe_delimited_line_and_creates_directory_lazily()
@@ -73,7 +73,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Monitoring.Gather
for (int i = 0; i < 20; i++)
log.Write("RULE-1", GatherRuleDebugLog.StageExec, new string('x', 100));
var oldPath = Path.Combine(Path.GetDirectoryName(LogPath), "gather_rules_debug.old.log");
var oldPath = Path.Combine(Path.GetDirectoryName(LogPath), "gather-rules-debug.old.log");
Assert.True(File.Exists(oldPath), "expected one .old rotation generation");
Assert.True(File.Exists(LogPath));
Assert.Contains("-- rotated", File.ReadAllText(LogPath) + File.ReadAllText(oldPath));
@@ -28,7 +28,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Monitoring.Gather
public GatherRuleExecutorDebugLogTests()
{
var logger = new AgentLogger(_tmp.Path, AgentLogLevel.Info);
_debugLogPath = Path.Combine(_tmp.Path, "gather_rules_debug.log");
_debugLogPath = Path.Combine(_tmp.Path, "gather-rules-debug.log");
_executor = new GatherRuleExecutor(
"sess", "tenant", _ => { }, logger,
debugLogPath: _debugLogPath)
@@ -32,7 +32,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Monitoring.Gather
{
Directory.CreateDirectory(_outsideUsers);
var logger = new AgentLogger(_tmp.Path, AgentLogLevel.Info);
_debugLogPath = Path.Combine(_tmp.Path, "gather_rules_debug.log");
_debugLogPath = Path.Combine(_tmp.Path, "gather-rules-debug.log");
_context = new GatherRuleContext(
logger, "sess", "tenant",
evt => _events.Add(evt),
@@ -53,7 +53,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Program
using var tmp = new TempDirectory();
var logDir = Path.Combine(tmp.Path, "logs");
Directory.CreateDirectory(logDir);
var crashPath = Path.Combine(logDir, "crash_20260421_100000.log");
var crashPath = Path.Combine(logDir, "crash-20260421-100000.log");
File.WriteAllText(crashPath, "[2026-04-21T10:00:00Z] FATAL: InvalidOperationException: simulated crash");
var summary = AutopilotMonitor.Agent.V2.Program.DetectPreviousExit(tmp.Path, logDir);
@@ -312,7 +312,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Program
AutopilotMonitor.Agent.V2.Program.WriteCrashLog(logDir, new InvalidOperationException("boom"));
var files = Directory.GetFiles(logDir, "crash_*.log");
var files = Directory.GetFiles(logDir, "crash-*.log");
Assert.Single(files);
var content = File.ReadAllText(files[0]);
Assert.Contains("FATAL:", content);
@@ -34,9 +34,9 @@ namespace AutopilotMonitor.Agent.V2.Core.Logging
private readonly long _maxFileSizeBytes;
private readonly string _logDirectory;
private readonly string _logFileBaseName; // "agent_20260425"
private string _logFilePath; // mutable — rotates to "_002", "_003" ...
private int _rotationSuffix; // 0 = base file, 2+ = "_002" etc.
private readonly string _logFileBaseName; // "agent"
private string _logFilePath; // mutable — rotates to "-002", "-003" ...
private int _rotationSuffix; // 0 = base file, 2+ = "-002" etc.
/// <summary>
/// When true, log entries are also written to Console.Out (same format as the log file).
@@ -55,14 +55,19 @@ namespace AutopilotMonitor.Agent.V2.Core.Logging
Directory.CreateDirectory(logDirectory);
}
_logFileBaseName = $"agent_{DateTime.Now:yyyyMMdd}";
// One canonical name, no date: the agent only lives for the duration of an
// enrollment, and a date-based split rotates at local midnight — which can fall
// mid-enrollment (local time may still be on the OOBE default timezone). Size
// rotation below bounds pathological growth. Legacy `agent_YYYYMMDD*.log` files
// from a pre-rename agent are ignored by the suffix probe and left in place.
_logFileBaseName = "agent";
_rotationSuffix = ProbeNextRotationSuffix(logDirectory, _logFileBaseName);
_logFilePath = BuildLogFilePath();
}
// Picks up the highest existing rotation suffix on the same day so a process restart
// doesn't overwrite previous segments. Returns 0 when no log file exists yet — the
// first segment uses the unsuffixed name `agent_YYYYMMDD.log`.
// Picks up the highest existing rotation suffix so a process restart doesn't
// overwrite previous segments. Returns 0 when no log file exists yet — the
// first segment uses the unsuffixed name `agent.log`.
private static int ProbeNextRotationSuffix(string dir, string baseName)
{
if (!Directory.Exists(dir)) return 0;
@@ -80,7 +85,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Logging
continue;
}
if (name.Length > baseName.Length + 1 &&
name.StartsWith(baseName + "_", StringComparison.OrdinalIgnoreCase) &&
name.StartsWith(baseName + "-", StringComparison.OrdinalIgnoreCase) &&
int.TryParse(name.Substring(baseName.Length + 1), out var suffix) &&
suffix > max)
{
@@ -97,7 +102,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Logging
{
var name = _rotationSuffix < 2
? $"{_logFileBaseName}.log"
: $"{_logFileBaseName}_{_rotationSuffix:D3}.log";
: $"{_logFileBaseName}-{_rotationSuffix:D3}.log";
return Path.Combine(_logDirectory, name);
}
@@ -244,7 +244,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Enrollment.SystemSignals
result.DurationMs = (int)(DateTime.UtcNow - started).TotalMilliseconds;
// Always log locally for diagnostics — the agent_YYYYMMDD.log is the always-on observability.
// Always log locally for diagnostics — agent.log is the always-on observability.
var anomaliesStr = result.Anomalies.Count > 0
? $"anomalies=[{string.Join(",", result.Anomalies)}]"
: "quiet";
@@ -972,7 +972,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Runtime
/// <summary>
/// Appends a log line to the main agent log file (logger isn't initialized yet at update time).
/// Uses the same date-based naming and format as AgentLogger: agent_YYYYMMDD.log
/// Uses the same naming and format as AgentLogger: agent.log
/// </summary>
private static void LogToFile(string message)
{
@@ -980,8 +980,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Runtime
{
var logDir = Environment.ExpandEnvironmentVariables(Constants.LogDirectory);
Directory.CreateDirectory(logDir);
var logFileName = $"agent_{DateTime.Now:yyyyMMdd}.log";
var logPath = Path.Combine(logDir, logFileName);
var logPath = Path.Combine(logDir, "agent.log");
File.AppendAllText(logPath, $"[{DateTime.UtcNow:yyyy-MM-dd HH:mm:ss.fff}] [INFO] {message}{Environment.NewLine}");
}
catch
@@ -26,7 +26,7 @@ namespace AutopilotMonitor.Agent.V2
/// cleanup and exit.</item>
/// <item><b>Session-age emergency break</b> — absolute cap (<c>AbsoluteMaxSessionHours</c>)
/// across restarts prevents zombie agents from persisting forever.</item>
/// <item><b>Crash log writer</b> — writes a last-resort crash_*.log next to the agent log
/// <item><b>Crash log writer</b> — writes a last-resort crash-*.log next to the agent log
/// directory when an uncaught exception escapes <c>Main</c>.</item>
/// </list>
/// </summary>
@@ -34,7 +34,10 @@ namespace AutopilotMonitor.Agent.V2
{
internal const string CleanExitMarkerFileName = "clean-exit.marker";
internal const string EnrollmentCompleteMarkerFileName = "enrollment-complete.marker";
private const string CrashLogPrefix = "crash_";
private const string CrashLogPrefix = "crash-";
// Pre-rename agents (< 2026-08-18) wrote crash_*.log; still detected once so an
// in-place self-update does not lose a pending crash classification.
private const string LegacyCrashLogPrefix = "crash_";
// ----------------------------------------------------------------- Multi-instance guard
@@ -102,7 +105,7 @@ namespace AutopilotMonitor.Agent.V2
}
/// <summary>
/// Reads <c>clean-exit.marker</c> + <c>crash_*.log</c> + session files + (if needed) the
/// Reads <c>clean-exit.marker</c> + <c>crash-*.log</c> + session files + (if needed) the
/// Windows event log to classify the previous exit. Deletes the marker and crash logs so
/// the next cycle starts fresh. Legacy parity — same rules, same outputs.
/// </summary>
@@ -114,6 +117,8 @@ namespace AutopilotMonitor.Agent.V2
var hadCleanExit = File.Exists(cleanExitMarker);
var crashLogs = Directory.Exists(logDirectory)
? Directory.GetFiles(logDirectory, CrashLogPrefix + "*.log")
.Concat(Directory.GetFiles(logDirectory, LegacyCrashLogPrefix + "*.log"))
.ToArray()
: Array.Empty<string>();
if (hadCleanExit)
@@ -406,7 +411,7 @@ namespace AutopilotMonitor.Agent.V2
try
{
Directory.CreateDirectory(logDirectory);
var crashPath = Path.Combine(logDirectory, $"{CrashLogPrefix}{DateTime.UtcNow:yyyyMMdd_HHmmss}.log");
var crashPath = Path.Combine(logDirectory, $"{CrashLogPrefix}{DateTime.UtcNow:yyyyMMdd-HHmmss}.log");
File.WriteAllText(crashPath, $"[{DateTime.UtcNow:u}] FATAL: {ex}");
}
catch { /* nowhere left to log */ }
@@ -7,14 +7,14 @@
# server-updatable without re-shipping the MSI.
#
# Fail-soft by design: the runner always exits 0 so a transient network failure never
# marks the LOB install as failed; diagnostics go to msi-bootstrap.log.
# marks the LOB install as failed; diagnostics go to bootstrap-msi.log.
# ASCII only - no special characters in this file.
$ErrorActionPreference = 'Stop'
$baseDir = Join-Path $env:ProgramData 'AutopilotMonitor'
$logDir = Join-Path $baseDir 'Logs'
New-Item -ItemType Directory -Force -Path $logDir | Out-Null
$log = Join-Path $logDir 'msi-bootstrap.log'
$log = Join-Path $logDir 'bootstrap-msi.log'
function Write-Log([string]$message) {
Add-Content -Path $log -Value ("[{0}] {1}" -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $message)
@@ -39,7 +39,7 @@
<!-- Deferred + no-impersonate: runs as SYSTEM after files land. Return=ignore keeps
the LOB install green even if the runner hits a transient error (the runner is
additionally fail-soft itself); diagnostics live in msi-bootstrap.log. -->
additionally fail-soft itself); diagnostics live in bootstrap-msi.log. -->
<CustomAction Id="RunBootstrap"
Directory="INSTALLFOLDER"
ExeCommand="&quot;[System64Folder]WindowsPowerShell\v1.0\powershell.exe&quot; -NoProfile -ExecutionPolicy Bypass -File &quot;[INSTALLFOLDER]Invoke-BootstrapDownload.ps1&quot;"
@@ -28,8 +28,8 @@ export const DIAG_ZIP_MAP = {
appliesTo: 'v2',
},
{
path: 'AgentLogs/agent_YYYYMMDD.log',
what: 'Full client chronology: startup → config → collectors → enrollment tracking → completion → upload. Rotated per day on multi-day enrollments — read every dated file.',
path: 'AgentLogs/agent.log',
what: 'Full client chronology: startup → config → collectors → enrollment tracking → completion → upload. Size-rotated segments agent-002.log onward. Pre-2026-08-18 agents wrote date-rotated agent_YYYYMMDD.log instead — glob agent*.log to catch both.',
read: 'grep-errors-first',
grepHints: ['"\\[WARN\\]\\|\\[ERROR\\]"', '-A 5 -B 2 "\\[ERROR\\]"', '"DecisionStep: ord="', '"EventUploadService"', '"EnrollmentTracker"', '"DiagnosticsCollector"'],
priority: 2,
@@ -57,7 +57,7 @@ export const DIAG_ZIP_MAP = {
appliesTo: 'v2',
},
{
path: 'AgentLogs/ime_pattern_matches.log (v2) / ime-matches.log (v1)',
path: 'AgentLogs/ime-pattern-matches.log (older v2 agents: ime_pattern_matches.log; v1: ime-matches.log)',
what: 'IME pattern matches. Format: [sourceFileName] [patternId] rawLogLine. Read when the question is about apps / ESP phases / IME patterns.',
read: 'full',
priority: 6,
@@ -113,5 +113,6 @@ export const DIAG_ZIP_MAP = {
'V1 agent archives have NO AgentState/ or AgentSpool/ folders (DecisionCore is v2-only).',
'Pre-provisioned (White Glove) blobs carry a -preprov suffix — that is normal.',
'No archive ≠ a problem: upload mode may be Off or OnFailure (so successful sessions have none).',
'Log naming is kebab-case since 2026-08-18: agent.log, bootstrap-script.log (was bootstrap_agent.log), bootstrap-msi.log (was msi-bootstrap.log), crash-*.log (was crash_*.log), gather-rules-debug.log, ime-pattern-matches.log. Archives from older agents/bootstraps carry the old names.',
],
} as const;
@@ -60,14 +60,14 @@ namespace AutopilotMonitor.Shared
/// <summary>
/// Default path for the IME pattern match log file (debugging/diagnostics)
/// </summary>
public const string ImeMatchLogPath = @"%ProgramData%\AutopilotMonitor\Logs\ime_pattern_matches.log";
public const string ImeMatchLogPath = @"%ProgramData%\AutopilotMonitor\Logs\ime-pattern-matches.log";
/// <summary>
/// Default path for the gather rule debug trace file (debugging/diagnostics).
/// Written when the tenant enables EnableGatherRuleDebugLog; explains every
/// gather-rule evaluation decision, including outcomes that produce no event.
/// </summary>
public const string GatherRuleDebugLogPath = @"%ProgramData%\AutopilotMonitor\Logs\gather_rules_debug.log";
public const string GatherRuleDebugLogPath = @"%ProgramData%\AutopilotMonitor\Logs\gather-rules-debug.log";
/// <summary>
/// Scheduled Task name used to run the agent as SYSTEM
@@ -194,7 +194,7 @@ export default function AgentSettingsSection({
<p className="font-medium text-gray-900">IME Pattern Match Log</p>
<p className="text-sm text-gray-500">
Write every matched IME log line to a local file for diagnostics
{enableImeMatchLog && <span className="block text-xs text-gray-400 mt-0.5 font-mono">%ProgramData%\AutopilotMonitor\Logs\ime_pattern_matches.log</span>}
{enableImeMatchLog && <span className="block text-xs text-gray-400 mt-0.5 font-mono">%ProgramData%\AutopilotMonitor\Logs\ime-pattern-matches.log</span>}
</p>
</div>
<button onClick={() => setEnableImeMatchLog(!enableImeMatchLog)}
@@ -209,7 +209,7 @@ export default function AgentSettingsSection({
<p className="font-medium text-gray-900">Gather Rule Debug Log</p>
<p className="text-sm text-gray-500">
Trace gather rule evaluation to a local file to diagnose rules that produce no timeline events. The trace never leaves the device.
{enableGatherRuleDebugLog && <span className="block text-xs text-gray-400 mt-0.5 font-mono">%ProgramData%\AutopilotMonitor\Logs\gather_rules_debug.log</span>}
{enableGatherRuleDebugLog && <span className="block text-xs text-gray-400 mt-0.5 font-mono">%ProgramData%\AutopilotMonitor\Logs\gather-rules-debug.log</span>}
</p>
</div>
<button onClick={() => setEnableGatherRuleDebugLog(!enableGatherRuleDebugLog)}