← back to Cncp Failure Collector
tick 2: heartbeat log every 10 quiet cycles, skip self in pm2 scan
495181cc6dfa3a96a76febb96d505977c33d24d3 · 2026-05-11 11:17:47 -0700 · SteveStudio2
Files touched
Diff
commit 495181cc6dfa3a96a76febb96d505977c33d24d3
Author: SteveStudio2 <steve@designerwallcoverings.com>
Date: Mon May 11 11:17:47 2026 -0700
tick 2: heartbeat log every 10 quiet cycles, skip self in pm2 scan
---
index.js | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/index.js b/index.js
index 7b0eaa0..8f40736 100644
--- a/index.js
+++ b/index.js
@@ -82,6 +82,11 @@ async function scanPm2(state) {
for (const p of procs) {
const name = p.name;
seenNames.add(name);
+ // Skip ourselves — a restart of the collector (intentional deploy) shouldn't flood the ledger.
+ if (name === 'cncp-failure-collector') {
+ state.pm2[name] = { restart_time: p.pm2_env?.restart_time || 0, status: p.pm2_env?.status || 'unknown' };
+ continue;
+ }
const prior = state.pm2[name];
const restartTime = p.pm2_env?.restart_time || 0;
const status = p.pm2_env?.status || 'unknown';
@@ -180,11 +185,32 @@ async function scanLaunchdErrLogs(state) {
}
}
+let cyclesSinceLastHeartbeat = 0;
+const HEARTBEAT_EVERY = 10; // log once every 10 cycles (~5 min at 30s poll) when quiet
+
async function cycle() {
const state = loadState();
+ const before = loadCurrentFailuresCount();
try { await scanPm2(state); } catch (e) { log('scanPm2 threw:', e.message); }
try { await scanLaunchdErrLogs(state); } catch (e) { log('scanLaunchdErrLogs threw:', e.message); }
saveState(state);
+ const after = loadCurrentFailuresCount();
+ cyclesSinceLastHeartbeat++;
+ if (after === before && cyclesSinceLastHeartbeat >= HEARTBEAT_EVERY) {
+ log(`heartbeat — ${cyclesSinceLastHeartbeat} quiet cycles, ${Object.keys(state.pm2).length} pm2 tracked, ${Object.keys(state.logOffsets).length} logs tracked`);
+ cyclesSinceLastHeartbeat = 0;
+ } else if (after !== before) {
+ cyclesSinceLastHeartbeat = 0;
+ }
+}
+
+function loadCurrentFailuresCount() {
+ // Read directly from CNCP's file (cheaper than HTTP). Returns 0 if unreadable.
+ try {
+ const p = path.join(os.homedir(), 'cncp-starter', 'cncp-failures.json');
+ if (fs.existsSync(p)) return JSON.parse(fs.readFileSync(p, 'utf-8')).length;
+ } catch {}
+ return 0;
}
async function main() {
← f20012e initial scaffold — pm2 + launchd failure collector for CNCP
·
back to Cncp Failure Collector
·
tick 3: launchd plist-driven .err.log tailing (97 com.steve. c57181c →