Record error.type on failed collections in PeriodicMetricReader - #8650
Conversation
The self-observability sample for otel.sdk.metric_reader.collection.duration was always recorded with a null error: the local `error` variable was declared but never assigned, so a failed collectAllMetrics() was reported as a successful collection (error.type never set) - hiding exactly the failures an operator watches this signal for. Set `error` to the throwable's class name before it propagates, mirroring BatchSpanProcessor.exportCurrentBatch(), so the finally records the failure. Signed-off-by: Timur Rakhmatullin <174210871+TimurRakhmatullin86@users.noreply.github.com>
Pull request dashboard statusMerged · refreshed 2026-07-28 17:45 UTC Status above doesn't look right?
|
jack-berg
left a comment
There was a problem hiding this comment.
Nice catch. One small comment but looks good otherwise.
| // Record the failure on the self-observability sample before it propagates to the | ||
| // outer handler; otherwise error.type is never set and collection failures are | ||
| // reported as successful collections. |
There was a problem hiding this comment.
| // Record the failure on the self-observability sample before it propagates to the | |
| // outer handler; otherwise error.type is never set and collection failures are | |
| // reported as successful collections. |
The explanation in this comment will make no sense to a future reader since it explains code that is no longer problematic with this change. I would just drop the comment altogether since the code is self explanatory.
Per review: the comment explained behavior that is no longer a pitfall after this change, so it adds noise rather than clarity. The code is self-explanatory. Signed-off-by: Timur Rakhmatullin <174210871+TimurRakhmatullin86@users.noreply.github.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #8650 +/- ##
============================================
- Coverage 91.64% 91.47% -0.17%
- Complexity 10348 10458 +110
============================================
Files 1013 1021 +8
Lines 27380 27650 +270
Branches 3218 3242 +24
============================================
+ Hits 25092 25293 +201
- Misses 1558 1615 +57
- Partials 730 742 +12 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Thank you for your contribution @TimurRakhmatullin86! 🎉 We would like to hear from you about your experience contributing to OpenTelemetry by taking a few minutes to fill out this survey. |
Problem
In
PeriodicMetricReader.Scheduled.doRun()the self-observability sample forotel.sdk.metric_reader.collection.durationis always recorded with anullerror:erroris declared, initialized tonull, and never assigned anywhere in the method, soMetricReaderInstrumentation.recordCollection'sif (error != null)branch that attaches theerror.typeattribute is unreachable from this caller. WhencollectAllMetrics()throws, thefinallystill records the sample — as a successful collection with noerror.type— andthe exception propagates to the outer
catch (Throwable). The self-observability histogramtherefore reports error-free collections while the reader is exporting nothing, which is
exactly the signal an operator would use to detect the outage.
The sibling
BatchSpanProcessor.exportCurrentBatch()uses the sameString error = null; … finally { instrumentation…(…, error); }shape but assignserroron each failure path.Fix
Set
errorto the throwable's class name before it propagates:The rethrow keeps the existing control flow — the exception still reaches the same outer
catch (Throwable)— so the only change is that the recorded sample now carrieserror.type.(
ThrowableUtil.propagateIfFatalis intentionally not added: the outer catch already handlesall throwables, so it would be a no-op here.)
Tests
SdkMeterProviderMetricsTest.collectionFailureIsRecordedWithErrorType: registers aMetricProducerthat throws on the firstproduce()then succeeds, flushes twice, and assertsthe exported
otel.sdk.metric_reader.collection.durationpoint carrieserror.type = java.lang.IllegalStateException. It fails against the current code (the point hasno
error.type) and passes with the fix. Full:sdk:metrics:testsuite and:sdk:metrics:spotlessCheckpass; errorprone/NullAway clean.