Skip to content

feat: downgrade uncaught error log when reconciler handles the error#3494

Merged
csviri merged 5 commits into
mainfrom
loggin-improve-warn-retry
Jul 16, 2026
Merged

feat: downgrade uncaught error log when reconciler handles the error#3494
csviri merged 5 commits into
mainfrom
loggin-improve-warn-retry

Conversation

@csviri

@csviri csviri commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Problem

When a Reconciler throws an exception that it deliberately handles in updateErrorStatus and classifies as expected & retryable (e.g. "waiting for a dependency that isn't present yet"), there was no way to keep JOSDK's
native retry (exponential backoff via @GradualRetry) while suppressing or downgrading the EventProcessor "Uncaught error during event processing" WARN. Retry and that log were the same code path.

In ReconciliationDispatcher.handleErrorStatusHandler, the updateErrorStatus result only avoided re-throwing when isNoRetry() was set; any retry-enabled control ended with throw e, which became
exceptionDuringExecution and triggered EventProcessor.handleRetryOnException — the single method that both schedules the retry and emits the WARN via retryAwareErrorLogging.

The only built-in escape was withNoRetry() (also implied by rescheduleAfter()), which suppresses the log but disables native retry: you lose the @GradualRetry exponential backoff and the retry counter. With
@GradualRetry(maxAttempts = -1) this was especially bad: isLastAttempt() is never true, so every attempt hit the WARN branch — a continuous stream of WARN + stack trace for an entirely expected condition, on top of
duplicate logging since the exception was already logged inside updateErrorStatus.

Change

When updateErrorStatus returns any ErrorStatusUpdateControl other than ErrorStatusUpdateControl.defaultErrorProcessing(), the error is now considered handled by the reconciler. Native retry (including
@GradualRetry backoff and the retry counter) is kept fully intact, but while retry attempts remain the framework logs the error on DEBUG level instead of emitting the "Uncaught error during event processing" WARN.

Crucially, this only downgrades transient, still-retrying failures. On the last retry attempt — or when no retry is configured — the failure is final, so the framework preserves its existing higher-severity WARN/ERROR
logging. This avoids hiding a non-recoverable error from operators once retries are exhausted.

Returning ErrorStatusUpdateControl.defaultErrorProcessing() preserves the previous behavior in all cases, including the warning. This follows the precedent already in this area, where the status-patch failure path and
retryAwareErrorLogging already do selective, level-aware downgrading.

Behavior matrix

updateErrorStatus returns Retry Framework log (retries remaining) Framework log (last attempt / no retry)
defaultErrorProcessing() yes WARN (unchanged) ERROR (unchanged)
patchStatus() / noStatusUpdate() (retry enabled) yes, backoff intact DEBUG (new) WARN/ERROR (unchanged)
withNoRetry() / rescheduleAfter() no (unchanged)

Implementation

  • PostExecutionControl carries a new errorHandledByReconciler flag.
  • ReconciliationDispatcher marks the exception control as handled instead of rethrowing when a non-default control still wants retry.
  • EventProcessor.retryAwareErrorLogging downgrades to DEBUG only when errorHandledByReconciler && !retry.isLastAttempt(); otherwise it falls through to the existing WARN/ERROR logic.
  • EventProcessor.logErrorIfNoRetryConfigured keeps logging at ERROR regardless of the flag, since without retry every failure is final.

Notes for reviewers

This makes the downgrade implicit for any non-default control, rather than an explicit opt-in (e.g. withoutDefaultErrorLogging()). It's the simplest approach and matches the proposal in the issue, but it does change the
log level for existing users who return patchStatus() with retry enabled (while retries remain). If we'd prefer an explicit opt-in method on ErrorStatusUpdateControl, that's a small addition on top of this.

Docs

Updated error-handling-retries.md to describe the new behavior, including the last-attempt / no-retry exception.

Copilot AI review requested due to automatic review settings July 14, 2026 14:27
@openshift-ci
openshift-ci Bot requested review from metacosm and xstefank July 14, 2026 14:27
@csviri
csviri force-pushed the loggin-improve-warn-retry branch 2 times, most recently from 00e465d to 35504ad Compare July 14, 2026 14:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot couldn't run its full agentic review because no GitHub Actions runner was available. Make sure your repository has a runner available to run Copilot's review, or add a copilot-setup-steps.yml file specifying one with the runs-on attribute. See the docs for more details.

This PR changes error handling so that when updateErrorStatus returns a non-default ErrorStatusUpdateControl, the framework treats the error as “handled by the reconciler” and downgrades framework-level logging while keeping retry/backoff behavior intact.

Changes:

  • Add an errorHandledByReconciler flag to PostExecutionControl and propagate it from ReconciliationDispatcher.
  • Adjust ReconciliationDispatcher to return an exception PostExecutionControl (marked handled) instead of rethrowing in the “handled-but-still-retry” scenario.
  • Downgrade relevant EventProcessor error logs to DEBUG when the handled flag is set, and add tests + docs for the new behavior.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcherTest.java Adds test coverage for the new “handled by reconciler” flag behavior.
operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java Marks exception as handled (via PostExecutionControl) rather than rethrowing in the handled+retry path.
operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/PostExecutionControl.java Introduces errorHandledByReconciler flag with setter/getter to carry handling state downstream.
operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventProcessor.java Threads handled flag into retry + no-retry log paths and downgrades framework logs to DEBUG accordingly.
docs/content/en/docs/documentation/error-handling-retries.md Documents the new semantics and logging behavior when updateErrorStatus returns non-default control.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Comment thread docs/content/en/docs/documentation/error-handling-retries.md Outdated
@csviri
csviri force-pushed the loggin-improve-warn-retry branch from 28bf1d6 to d6004cf Compare July 15, 2026 12:57
@csviri
csviri requested a review from Copilot July 15, 2026 12:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Comment on lines +432 to +436
} else if (errorHandledByReconciler && !retry.isLastAttempt()) {
// The reconciler already handled the error in updateErrorStatus (and had the chance to log it
// as needed), so while there are retries remaining the framework only logs it on debug level.
// On the last attempt we still fall through to the higher-severity logging below, so a final,
// non-recoverable failure is not hidden from operators.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

csviri added 5 commits July 16, 2026 09:51
When a reconciler's updateErrorStatus returns any ErrorStatusUpdateControl
other than defaultErrorProcessing(), the error is now considered handled by
the reconciler. Native retry (including @GradualRetry exponential backoff)
is kept intact, but the framework logs the error on DEBUG level instead of
emitting the "Uncaught error during event processing" WARN.

This lets a reconciler keep retrying an expected, recoverable condition
without producing a continuous stream of WARN messages, while it remains
free to log the error at whatever level it wants inside updateErrorStatus.
Returning defaultErrorProcessing() preserves the previous behavior,
including the warning.

- PostExecutionControl carries an errorHandledByReconciler flag
- ReconciliationDispatcher marks the exception control as handled instead
  of rethrowing when a non-default control still wants retry
- EventProcessor downgrades the retry-aware and no-retry-configured error
  logs to DEBUG when the error was handled by the reconciler

Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
Previously errorHandledByReconciler downgraded the error log to DEBUG
unconditionally, which also hid final, non-recoverable failures on the
last retry attempt.

Now the downgrade only applies while retry attempts remain
(errorHandledByReconciler && !retry.isLastAttempt()). On the last attempt
the existing higher-severity WARN/ERROR logging is preserved. Likewise,
when no retry is configured every failure is final, so it keeps the ERROR
log regardless of whether the reconciler handled the error.

Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
The error-handling docs claimed native retry is still performed for any
non-default ErrorStatusUpdateControl, which is inaccurate for controls
that disable retry (withNoRetry() / rescheduleAfter()). Qualify the
paragraph so it only claims retry/backoff is preserved when a retry is
configured and not disabled by the returned control.

Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
retryAwareErrorLogging checked errorHandledByReconciler before the
HTTP_CONFLICT special-case, so handled errors that were also
KubernetesClientException conflicts no longer emitted the conflict-specific
INFO message. The conflict branch is already low-noise (DEBUG + INFO) and
provides actionable info, so it now keeps precedence over the handled-error
downgrade.
@csviri
csviri force-pushed the loggin-improve-warn-retry branch from 8329130 to 36ce6fd Compare July 16, 2026 07:51
@csviri

csviri commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator Author

Sonar fails with

JRE provisioning: os[linux], arch[x86_64]
Error:  Failed to query JRE metadata: GET https://api.sonarcloud.io/analysis/jres?os=linux&arch=x86_64 failed with HTTP 504 Gateway Timeout

What is unrelated to this PR.

@csviri
csviri merged commit 645c77b into main Jul 16, 2026
29 of 32 checks passed
@csviri
csviri deleted the loggin-improve-warn-retry branch July 16, 2026 11:53
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.

Allow suppressing/downgrading the "Uncaught error during event processing" log without disabling retry (for exceptions handled in updateErrorStatus)

3 participants