From df1acc8f0cbe57f4d4a047924946b992af37ec45 Mon Sep 17 00:00:00 2001 From: Renato Alcara Date: Sun, 26 Apr 2026 11:36:21 -0300 Subject: [PATCH] =?UTF-8?q?chore(logs):=20reduce=20decrypt-error=20noise?= =?UTF-8?q?=20(~75%=20fewer=20lines=20per=20recover=E2=80=A6=20(#391)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit chore(logs): reduce decrypt-error noise (~75% fewer lines per recover… (#391) --- src/Utils/auth-utils.ts | 13 ++++++++++++- src/Utils/decode-wa-message.ts | 25 +++++++++++++++++++++++-- src/index.ts | 13 +++++++++++-- 3 files changed, 46 insertions(+), 5 deletions(-) diff --git a/src/Utils/auth-utils.ts b/src/Utils/auth-utils.ts index 204e16be..adabf548 100644 --- a/src/Utils/auth-utils.ts +++ b/src/Utils/auth-utils.ts @@ -341,7 +341,18 @@ export const addTransactionCapability = ( return result } catch (error) { - logger.error({ error }, 'transaction failed, rolling back') + // SessionError is part of the normal Bad MAC recovery flow + // (retry receipt → sender resends as pkmsg → new session within ~1.3s). + // Logging it as ERROR creates 2 noise lines per recoverable Bad MAC cycle. + // Downgrade to debug for SessionError; keep ERROR for everything else. + // The error is still re-thrown — recovery behavior is unchanged. + const errName = (error as { name?: string })?.name + if (errName === 'SessionError') { + logger.debug({ error }, 'transaction failed (SessionError — recoverable via retry receipt)') + } else { + logger.error({ error }, 'transaction failed, rolling back') + } + throw error } }) diff --git a/src/Utils/decode-wa-message.ts b/src/Utils/decode-wa-message.ts index a88e06be..5ebf5520 100644 --- a/src/Utils/decode-wa-message.ts +++ b/src/Utils/decode-wa-message.ts @@ -58,7 +58,11 @@ export const BAD_MAC_ERROR_TEXT = 'Bad MAC' export const DECRYPTION_RETRY_CONFIG = { maxRetries: 3, baseDelayMs: 100, - sessionRecordErrors: ['No session record', 'SessionError: No session record'], + // 'No matching sessions found' is the libsignal error when decryptWithSessions exhausts + // all stored sessions for a JID. Same recovery flow (retry receipt → pkmsg → new session) + // — categorise it as session-record so the caller logs DEBUG on retry, ERROR only when + // retries are exhausted (instead of dumping the full stack as an unknown error). + sessionRecordErrors: ['No session record', 'SessionError: No session record', 'No matching sessions found'], corruptedSessionErrors: ['Bad MAC', 'MessageCounterError', MISSING_KEYS_ERROR_TEXT] } @@ -421,9 +425,26 @@ export const decryptMessageNode = ( const isCorrupted = isCorruptedSessionError(originalError) const isSessionRecord = isSessionRecordError(originalError) + // Slim error projection — keep name/message/type for diagnosis, + // drop `stack` which adds 4-5 lines of node_modules paths per log + // for known-recoverable libsignal errors. + // + // CRITICAL: only slim for KNOWN-RECOVERABLE categories (corrupted / + // session-record). The unknown-error branch keeps the full Error so + // protobuf/parsing/runtime bugs still emit a stack trace where it + // matters most. Catches Copilot/Codex P2 review on PR #391. + const slimErr = originalError + ? { + name: (originalError as { name?: string }).name, + message: (originalError as { message?: string }).message, + type: (originalError as { type?: string }).type + } + : undefined + const isRecoverableCategory = isCorrupted || isSessionRecord + const errorContext = { key: fullMessage.key, - err: originalError, + err: isRecoverableCategory ? slimErr : originalError, messageType: tag === 'plaintext' ? 'plaintext' : attrs.type, sender, author, diff --git a/src/index.ts b/src/index.ts index e997ddfd..fc3a701c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -28,7 +28,16 @@ console.info = function (...args: unknown[]) { // Track errors by type + JID to avoid duplicates (using Map for better performance) const _errorTimestamps = new Map() -const DEDUP_WINDOW_MS = 150 +// Dedup window for repeated decrypt-error console lines (Bad MAC / Counter / etc). +// Was 150ms, but retry attempts of the SAME message are typically ~300-1000ms apart, +// so the second attempt fell outside the window and double-printed. +// +// TRADE-OFF: dedup key is `errorType + JID` (no message-id). With 5s, a burst of +// errors for the SAME JID — even of slightly different categories or different +// messages — collapses to one log line every 5s. This is intentional for a noisy +// production stream; if you need per-message visibility, set BAILEYS_LOG_LEVEL=debug +// to bypass this console-side dedup and see the structured pino logs in full. +const DEDUP_WINDOW_MS = 5000 console.error = function (...args: unknown[]) { if (args.length > 0 && typeof args[0] === 'string') { @@ -70,7 +79,7 @@ console.error = function (...args: unknown[]) { const lastTime = _errorTimestamps.get(dedupeKey) if (lastTime && now - lastTime < DEDUP_WINDOW_MS) { - return // Skip duplicate within 150ms window + return // Skip duplicate within DEDUP_WINDOW_MS window } _errorTimestamps.set(dedupeKey, now)