From c58332c02addb19734e783c27068491291a2d76c Mon Sep 17 00:00:00 2001 From: Johannes Krobath Date: Fri, 10 Jul 2026 17:55:43 +0200 Subject: [PATCH] fix: collect repeated send failures instead of flooding the log A device that stops responding (left the network, powered off) turns every send into an error line: the ember driver rejects with 'Delivery failed' and no error code, so filterError logs each attempt at error level. With an automation still commanding the device this fills the log with one identical line per ~5 seconds - tens of thousands of lines per day for a single dead bulb, drowning every other message, while not even showing when the problem started. The adapter already has the right tool for exactly this situation: the stashed-errors collection in the states controller (used for the min/max value warnings and unknown models) - first occurrence is logged, repeats only bump a counter with first/last-seen timestamps, visible in the errors view of the admin tab and in info.lasterror. This change routes the error-level branches of filterError (no error code, unknown code, E_ERROR, malformed) through that collection when the caller provides a stash key; without a key the behaviour is unchanged. The send path in publishFromState passes 'send:' as key, and clears it again on the first successful send to that device (new unstashError, which also recalculates hasData so the errors button disappears once everything is resolved) - a later outage starts with a fresh error line again. The first logged line carries a short hint that repeats are counted, not logged. The info/warn/debug severities of the error code table are deliberately untouched - those are already rate-appropriate. Co-Authored-By: Claude Fable 5 --- lib/statescontroller.js | 12 ++++++++++++ lib/zigbeecontroller.js | 7 ++++++- main.js | 23 ++++++++++++++++++----- 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/lib/statescontroller.js b/lib/statescontroller.js index b70a186b..ffa4e55e 100644 --- a/lib/statescontroller.js +++ b/lib/statescontroller.js @@ -461,6 +461,18 @@ class StatesController extends EventEmitter { catch { /* */ } } + // Drop a stashed error once its cause is resolved (e.g. a send to the device succeeded + // again), so the next occurrence is logged as a fresh error instead of silently counting. + unstashError(key) { + try { + if (this.stashedErrors.errors.hasOwnProperty(key)) { + delete this.stashedErrors.errors[key]; + this.stashedErrors.hasData = Object.keys(this.stashedErrors.errors).length > 0 || Object.keys(this.stashedErrors.unknownModels).length > 0; + } + } + catch { /* */ } + } + stashUnknownModel(model, msg) { try { if (!this.stashedErrors.unknownModels.hasOwnProperty(model)) { diff --git a/lib/zigbeecontroller.js b/lib/zigbeecontroller.js index 5531abdb..94f15e1f 100644 --- a/lib/zigbeecontroller.js +++ b/lib/zigbeecontroller.js @@ -1724,6 +1724,9 @@ class ZigbeeController extends EventEmitter { } } retry = 0; + // the send went through - a stashed send failure for this device is + // resolved, the next failure is logged as a fresh error again + this.adapter.stController?.unstashError(`send:${String(deviceId).replace('0x', '')}`); if (readKeys) { for (const readKey of readKeys) { if (readKey.converter) { @@ -1753,8 +1756,10 @@ class ZigbeeController extends EventEmitter { } else { retry = 0; + // stash key: repeated send failures to the same device (e.g. while it + // is unreachable) are counted instead of logged, until a send succeeds this.adapter.filterError(`Error ${error.code} on send command to '${this.devLabel(deviceId)}'.` + - ` Error: ${error.message}`, `Send command to ${deviceId} failed with`, error); + ` Error: ${error.message}`, `Send command to ${deviceId} failed with`, error, `send:${String(deviceId).replace('0x', '')}`); } } } while (retry > 0); diff --git a/main.js b/main.js index 7e874cff..3b154925 100644 --- a/main.js +++ b/main.js @@ -166,11 +166,24 @@ class Zigbee extends adapterCore.Adapter { } */ } - filterError(errormessage, message, error) { + // Error-level output of filterError. With a stash key, repeated identical errors + // (e.g. every failed send to an unreachable device, one per ~5 seconds) are collected + // by the states controller instead of flooding the log: the first one is logged as an + // error, the repeats only update the counter shown in the errors view of the admin + // tab and in info.lasterror. The caller clears the key once the cause is resolved. + logFilteredError(stashKey, text) { + if (stashKey && this.stController) { + this.stController.stashErrors(stashKey, `${text} - repeats are counted, not logged (errors view in the admin tab)`, true); + } else { + this.log.error(text); + } + } + + filterError(errormessage, message, error, stashKey) { if (error != null && error.code == undefined) { let em = error.stack.match(/failed \((.+?)\) at/); em = em || error.stack.match(/failed \((.+?)\)/); - this.log.error(`${message} no error code (${(em ? em[1] : 'undefined')})`); + this.logFilteredError(stashKey, `${message} no error code (${(em ? em[1] : 'undefined')})`); this.sendError(error, `${message} no error code`); if (this.debugActive) this.log.debug(`Stack trace for ${em}: ${error.stack}`); return; @@ -178,7 +191,7 @@ class Zigbee extends adapterCore.Adapter { const ecode = errorCodes[error.code]; if (ecode === undefined) { - this.log.error(errormessage); + this.logFilteredError(stashKey, errormessage); this.sendError(error, errormessage); return; } @@ -194,11 +207,11 @@ class Zigbee extends adapterCore.Adapter { this.log.warn(`${message}: Code ${error.code} (${ecode.message})`); break; case E_ERROR: - this.log.error(`${message}: Code ${error.code} (${ecode.message})`); + this.logFilteredError(stashKey, `${message}: Code ${error.code} (${ecode.message})`); this.sendError(error, `${message}: Code ${error.code} (${ecode.message})`); break; default: - this.log.error(`${message}: Code ${error.code} (malformed error)`); + this.logFilteredError(stashKey, `${message}: Code ${error.code} (malformed error)`); this.sendError(error, `${message}: Code ${error.code} (malformed error)`); break; }