Wire metrics_reporter into the OTLP exporter and flush it on shutdown - #117
Wire metrics_reporter into the OTLP exporter and flush it on shutdown#117Alan-Marx wants to merge 4 commits into
Conversation
build_otlp_exporter never passed metrics_reporter to OpenTelemetry::Exporter::OTLP::Exporter, so it silently fell back to the SDK's no-op reporter - otel.otlp_exporter.failure and otel.otlp_exporter.message.compressed_size never reached a configured reporter, unlike the otel.bsp.* metrics that SpanProcessor already threads through.
OtelSetup.shutdown can emit final metrics (e.g. otel.bsp.dropped_spans on a forced drain) through the configured metrics_reporter, but nothing gave that reporter a chance to flush before process exit. Langfuse.shutdown now calls an optional #shutdown on the reporter, resilient to a reporter that doesn't implement it or that raises.
The metrics_reporter section only described BatchSpanProcessor metrics and said the application fully owns the reporter lifecycle - both now incomplete after wiring the reporter into build_otlp_exporter and calling an optional #shutdown from Langfuse.shutdown.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 36df803. Configure here.
|
@Alan-Marx I updated the branch in commit The refactor passes one resilient Please verify this sequence with your real reporter integration before we merge:
Local verification passed with 1,646 examples, 96.94% coverage, RuboCop, a real UDP lifecycle test, and live Langfuse ingestion plus API readback. Please reply when you have verified the reporter behavior. |

Problem
OtelSetup.build_otlp_exporterconstructsOpenTelemetry::Exporter::OTLP::Exporterwithout passingmetrics_reporter::opentelemetry-exporter-otlp's exporter defaultsmetrics_reporter: nilto its own no-opMetricsReporterwhen not given one. So a user who configuresconfig.metrics_reporterand exports over OTLP never receivesotel.otlp_exporter.failureorotel.otlp_exporter.message.compressed_size- despiteSpanProcessoralready threadingconfig.metrics_reporterthrough correctly for theotel.bsp.*metrics. It's an easy gap to miss because everything appears wired: the config option exists,otel.bsp.*metrics work, and there's no error - the two OTLP-exporter-level metrics just silently go nowhere.Related:
Langfuse.shutdownnever gives the configuredmetrics_reportera chance to flush before process exit, even though it already has explicit shutdown boundaries for the client and tracer provider. For an async reporter (e.g. a DogStatsd client with a background sender thread), the last few metrics from a shutdown-time export can be lost.Fix
build_otlp_exporternow passesmetrics_reporter: ResilientMetricsReporter.wrap(config.metrics_reporter, logger: config.logger), matching the existing pattern inSpanProcessor.ResilientMetricsReportergains an optional#shutdownthat forwards to the wrapped reporter only if it responds to:shutdown, rescued the same way as its other three methods.Langfuse.shutdowncalls it afterOtelSetup.shutdown.Testing
spec/langfuse/otel_setup_spec.rb: a mock-based test on the exporter constructor call, plus a contract test that builds the real tracer provider + real OTLP exporter, stubs the HTTP endpoint to fail with WebMock, and asserts bothotel.otlp_exporter.failureandotel.otlp_exporter.message.compressed_sizereach a configured reporter through the actual export path (not just a mocked constructor call).spec/langfuse/resilient_metrics_reporter_spec.rb/spec/langfuse_spec.rb: cover the new#shutdownpassthrough, including the no-shutdown-method and raising-reporter cases.Both changes are backward compatible -
nilmetrics_reporter still resolves to the SDK's own no-op reporter, andLangfuse.shutdownis a no-op addition for anyone not usingmetrics_reporter.Full suite (
bundle exec rspec,bundle exec rubocop) passes locally.Note
Low Risk
Observability wiring and documentation only; tracing export behavior is unchanged aside from forwarding OTLP exporter metrics to an optional reporter the app already owns.
Overview
config.metrics_reporternow feeds both the batch span processor and the default OTLP exporter, so OTLP operational metrics (e.g.otel.otlp_exporter.failure,otel.otlp_exporter.message.compressed_size) reach the same reporter as existingotel.bsp.*metrics instead of silently using OpenTelemetry’s no-op reporter.OtelSetupwraps the configured reporter once viaResilientMetricsReporterand passes that instance intoSpanProcessorandOpenTelemetry::Exporter::OTLP::Exporter.SpanProcessorno longer wraps the reporter itself—it requires an injectedmetrics_reporter:.Docs and comments broaden the
metrics_reportercontract to cover BSP + OTLP, state that Langfuse does not flush, close, or shut down an application-owned reporter, and document shutdown order:Langfuse.shutdownfirst, then the app flushes/closes backends (e.g. DogStatsD).Tests cover shared wiring, real OTLP metric emission on failed export, final BSP metrics on shutdown, concurrent shutdown, a UDP integration proving post-shutdown app flush delivers BSP + OTLP metrics, and assertions that
Langfuse.shutdown/Langfuse.reset!never callshutdownon the reporter.Reviewed by Cursor Bugbot for commit 854307f. Bugbot is set up for automated code reviews on this repo. Configure here.