feat(sdk): add otel.sdk.processor.log.processed self-diagnostics metric#3514
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3514 +/- ##
=======================================
- Coverage 82.9% 82.9% -0.1%
=======================================
Files 130 130
Lines 27311 27386 +75
=======================================
+ Hits 22659 22711 +52
- Misses 4652 4675 +23 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
ed9e3f4 to
9ed6cfd
Compare
…ic to BatchLogProcessor
9ed6cfd to
36d0e77
Compare
| let instance_id = INSTANCE_COUNTER.fetch_add(1, Ordering::Relaxed); | ||
| let component_name = format!("batching_log_processor/{instance_id}"); | ||
|
|
||
| let meter = opentelemetry::global::meter("otel.sdk"); |
There was a problem hiding this comment.
Follow-up suggestion (not a blocker for this PR): detect & warn when bound counters resolve to noop
If the user constructs the LoggerProvider before global::set_meter_provider, the three BoundCounters permanently bind to NoopBoundSyncInstrument, add(1) becomes a no-op, and no metric is ever emitted. The user gets no signal — just total == 0. The OTLP examples work around it by reordering setup, but the failure mode is silent.
The current public API can't detect this: MeterProvider is type-erased, doesn't extend Any, and has no is_noop() method. Detecting "was set_meter_provider called?" via a flag isn't enough either — NoopMeterProvider is pub and a valid MeterProvider, so a user can legitimately install it explicitly. We need to ask the actual provider whether it's a no-op.
Proposed addition: defaulted is_noop on MeterProvider
// opentelemetry/src/metrics/meter.rs
pub trait MeterProvider {
// existing methods...
/// Returns `true` if this provider is a no-op implementation that
/// silently discards all measurements. Defaults to `false`; the
/// built-in `NoopMeterProvider` overrides to `true`. SDK components
/// can use this to warn at construction when self-diagnostics metrics
/// would be silently dropped.
fn is_noop(&self) -> bool { false }
}
// opentelemetry/src/metrics/noop.rs
impl MeterProvider for NoopMeterProvider {
// existing methods...
fn is_noop(&self) -> bool { true }
}Backwards-compatible — existing MeterProvider impls don't need to change.
Usage in BatchLogProcessor::new
let provider = opentelemetry::global::meter_provider();
if provider.is_noop() {
otel_warn!(
name: "BatchLogProcessor.SelfDiagnosticsUnavailable",
message = "BatchLogProcessor obtained a no-op MeterProvider from the global \
registry; otel.sdk.processor.log.processed will not record. Call \
global::set_meter_provider with a real MeterProvider before \
constructing the LoggerProvider to enable self-diagnostics."
);
}
let meter = provider.meter("otel.sdk");
// ... build counter, bind() three times as todayBenefits
- Catches both the "forgot to set the provider" and "set it after the LoggerProvider" cases — the warning fires whenever the bound counters would silently drop measurements.
- One virtual call at construction; zero impact on
add()hot path. - Works on injected providers too, in case any SDK component later accepts a
MeterProviderparameter instead of reaching for the global. - Reusable by
BatchSpanProcessor,PeriodicReader, and any future component that emits self-diagnostics.
Does not fix the late-rebind case (user replaces the global provider with a different real one after construction) — that needs the late-binding global::meter() work already noted in docs/design/observability.md.
There was a problem hiding this comment.
agree. I think we need to solve the fundamental underlying issue - that is the proper solution!
|
@bantonsson PTAL! thanks in advance. |
6e1fbbe
Adds the
otel.sdk.processor.log.processedcounter toBatchLogProcessor, following the OTel SDK metrics semantic conventions.Gated behind the existing
experimental_metrics_bound_instrumentsfeature flag — bound instruments make this practical on the hot path (~1.8 ns per emit vs ~50 ns with unbound).Changes:
BatchLogProcessoremits a counter on everyemit()call with three attribute variants: success,queue_full, andalready_shutdowndocs/design/observability.mdtest.sh)Ordering requirement:
global::set_meter_provider()must be called before creating theLoggerProvider. Rust'sglobal::meter()returns a snapshot, not a delegate. See the design doc for details.