Skip to content

Comments

Improves error logging robustness#406

Open
w3nl wants to merge 2 commits intomainfrom
feature-exit-on-error
Open

Improves error logging robustness#406
w3nl wants to merge 2 commits intomainfrom
feature-exit-on-error

Conversation

@w3nl
Copy link
Contributor

@w3nl w3nl commented Feb 19, 2026

Adds a fallback mechanism to ensure errors are logged even if the initial attempt to stringify the error object fails, preventing potential logging failures.

This change enhances the reliability of error logging by providing a more resilient approach to handling error objects, ensuring that even in cases where stringification or object processing fails, a minimal amount of error information is still captured and logged.

Summary by CodeRabbit

Bug Fixes

  • Enhanced error handling with improved fallback mechanisms to strengthen system reliability during edge cases.

Adds a fallback mechanism to ensure errors are logged even if the initial attempt to stringify the error object fails, preventing potential logging failures.

This change enhances the reliability of error logging by providing a more resilient approach to handling error objects, ensuring that even in cases where stringification or object processing fails, a minimal amount of error information is still captured and logged.
@coderabbitai
Copy link

coderabbitai bot commented Feb 19, 2026

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The logger's Error path is enhanced with try-catch blocks to safely handle message extraction and info object construction. If wrapping fails, a fallback chain progressively attempts minimal safe logging formats before delegating to the original unwrapped logger as a last resort.

Changes

Cohort / File(s) Summary
Error Handling Robustness
src/logger.js
Introduces try-catch blocks around error message extraction and info construction in the Error logging path, with cascading fallback strategies for wrapping failures and a final delegation to the original logger.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 Errors now wrapped in safety's embrace,
Fallbacks and catches in just the right place,
When wrapping stumbles, we gracefully fall,
Back to basics—the logger handles it all! 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Improves error logging robustness' directly aligns with the main change: adding fallback mechanisms to handle error logging failures gracefully.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature-exit-on-error

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Improves resilience of the Winston level wrappers in src/logger.js so that logging an Error won’t fail completely if error wrapping/serialization throws.

Changes:

  • Wraps the Error-first-argument logging path in a try/catch and adds a fallback minimal log payload.
  • Adds a last-resort path that calls the original level method if even the fallback logging fails.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +89 to +90
// Last resort: use original logger without wrapping
return orig(first)
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the last-resort fallback, orig(first) drops any additional arguments (...rest) that may contain important metadata for the log entry. Consider calling the original method with the full argument list so context isn’t lost when the wrapper fails.

Suggested change
// Last resort: use original logger without wrapping
return orig(first)
// Last resort: use original logger without wrapping, preserving all args
return orig(first, ...rest)

Copilot uses AI. Check for mistakes.
try {
return logger.log({
level: lvl,
message: first.message || String(first),
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallback path uses first.message || String(first), which can pass through a non-string message (if error.message was set to an object) and also overwrites an intentionally empty string message. To keep the fallback maximally safe/consistent, coerce message to a string using the same typeof === 'string' check (or String(...)) instead of relying on truthiness.

Suggested change
message: first.message || String(first),
message: typeof first.message === 'string' ? first.message : String(first),

Copilot uses AI. Check for mistakes.
Comment on lines +88 to +90
} catch {
// Last resort: use original logger without wrapping
return orig(first)
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this PR adds a new resilience/fallback behavior (the catch (wrapError) path), it would be good to add a test that forces the wrapper to fail (e.g., passing a meta object with a throwing getter so Object.assign throws) and asserts that logging still does not throw. This guards against regressions where the fallback/last-resort path still fails and causes logging to crash.

Suggested change
} catch {
// Last resort: use original logger without wrapping
return orig(first)
} catch (lastError) {
// Last resort: try original logger, and if that fails, fall back to console
try {
return orig(first)
} catch {
// eslint-disable-next-line no-console
console.warn('LOGGER_FALLBACK_FAILED', {
level: lvl,
originalError: first,
wrapError,
lastError
})
return undefined
}

Copilot uses AI. Check for mistakes.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/logger.js (1)

78-92: Reduce nesting depth — static analysis flag at line 80.

The inner try/catch inside the outer catch block pushes nesting to depth 3, which exceeds the project's configured maximum of 2. Extracting the fallback into a dedicated helper eliminates the violation without changing behaviour.

♻️ Proposed refactor — extract fallback helper
+function logFallback(lvl, orig, first, rest, wrapError) {
+  try {
+    return orig._logger.log({
+      level: lvl,
+      message: typeof first.message === 'string' ? first.message : String(first),
+      error: first,
+      stack: first.stack,
+      wrapError: wrapError.message
+    })
+  } catch {
+    return orig(first, ...rest)
+  }
+}

Then in wrapLevel:

         } catch (wrapError) {
           // Fallback: log with minimal safe data if wrapping fails
-          try {
-            return logger.log({
-              level: lvl,
-              message: first.message || String(first),
-              error: first,
-              stack: first.stack,
-              wrapError: wrapError.message
-            })
-          } catch {
-            // Last resort: use original logger without wrapping
-            return orig(first)
-          }
+          return logFallback(lvl, orig, first, rest, wrapError)
         }

Note: the helper needs access to logger.log — pass it as a parameter or close over it via the enclosing scope rather than using orig._logger.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/logger.js` around lines 78 - 92, The inner try/catch inside the outer
catch creates nesting depth >2; extract that fallback logging logic into a new
helper (e.g., safeFallbackLog) and call it from the catch in wrapLevel. The
helper should accept the logger.log function (or the logger object) plus the
variables used (lvl, first, wrapError) and perform the inner try/catch there,
returning whatever logger.log returns; update the catch to call
safeFallbackLog(logger.log, lvl, first, wrapError) and keep the final fallback
of returning orig(first) if the helper throws.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/logger.js`:
- Around line 83-90: The inner fallback currently uses first.message ||
String(first) and the final catch calls orig(first) dropping ...rest; update the
inner fallback in the catch block that builds the log object to coerce the
message to a string (e.g., use typeof first.message === 'string' ? first.message
: String(first)) so Winston always gets a string, and change the last-resort
return to pass through remaining metadata (call orig(first, ...rest) or
equivalent) so ...rest isn't discarded; reference the variables/functions first,
rest, orig and wrapError in your changes.

---

Nitpick comments:
In `@src/logger.js`:
- Around line 78-92: The inner try/catch inside the outer catch creates nesting
depth >2; extract that fallback logging logic into a new helper (e.g.,
safeFallbackLog) and call it from the catch in wrapLevel. The helper should
accept the logger.log function (or the logger object) plus the variables used
(lvl, first, wrapError) and perform the inner try/catch there, returning
whatever logger.log returns; update the catch to call
safeFallbackLog(logger.log, lvl, first, wrapError) and keep the final fallback
of returning orig(first) if the helper throws.

Comment on lines +83 to +90
message: first.message || String(first),
error: first,
stack: first.stack,
wrapError: wrapError.message
})
} catch {
// Last resort: use original logger without wrapping
return orig(first)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Two minor robustness gaps in the fallback path.

1. Non-string message in the inner fallback (line 83).
The outer try guards against first.message not being a string (lines 56–64), but the inner fallback drops that guard and uses first.message || String(first). If first.message is a truthy non-string (e.g., an object), it is passed as-is to Winston's message field, which expects a string and may behave unexpectedly.

2. Last-resort call drops ...rest (line 90).
orig(first) silently discards all metadata from ...rest. While this is the "last resort" path, the loss is invisible to the caller. Passing ...rest or at least documenting the omission would be safer.

🛡️ Proposed fixes
- message: first.message || String(first),
+ message: typeof first.message === 'string' ? first.message : String(first),
- return orig(first)
+ return orig(first, ...rest)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
message: first.message || String(first),
error: first,
stack: first.stack,
wrapError: wrapError.message
})
} catch {
// Last resort: use original logger without wrapping
return orig(first)
message: typeof first.message === 'string' ? first.message : String(first),
error: first,
stack: first.stack,
wrapError: wrapError.message
})
} catch {
// Last resort: use original logger without wrapping
return orig(first, ...rest)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/logger.js` around lines 83 - 90, The inner fallback currently uses
first.message || String(first) and the final catch calls orig(first) dropping
...rest; update the inner fallback in the catch block that builds the log object
to coerce the message to a string (e.g., use typeof first.message === 'string' ?
first.message : String(first)) so Winston always gets a string, and change the
last-resort return to pass through remaining metadata (call orig(first, ...rest)
or equivalent) so ...rest isn't discarded; reference the variables/functions
first, rest, orig and wrapError in your changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants