mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 04:32:32 +00:00
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:
@@ -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**.
|
||||
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
+2
-2
@@ -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));
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -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=""[System64Folder]WindowsPowerShell\v1.0\powershell.exe" -NoProfile -ExecutionPolicy Bypass -File "[INSTALLFOLDER]Invoke-BootstrapDownload.ps1""
|
||||
|
||||
@@ -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)}
|
||||
|
||||
Reference in New Issue
Block a user