Skip to content

Wire metrics_reporter into the OTLP exporter and flush it on shutdown - #117

Merged
kxzk merged 5 commits into
simplepractice:mainfrom
Alan-Marx:fix/otlp-exporter-metrics-reporter
Sep 5, 2026
Merged

kxzk merged 5 commits into
simplepractice:mainfrom
Alan-Marx:fix/otlp-exporter-metrics-reporter

Conversation

@Alan-Marx

@Alan-Marx Alan-Marx commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Problem

OtelSetup.build_otlp_exporter constructs OpenTelemetry::Exporter::OTLP::Exporter without passing metrics_reporter::

def build_otlp_exporter(config)
  OpenTelemetry::Exporter::OTLP::Exporter.new(
    endpoint: "#{config.base_url}/api/public/otel/v1/traces",
    headers: build_headers(config.public_key, config.secret_key),
    compression: "gzip"
  )
end

opentelemetry-exporter-otlp's exporter defaults metrics_reporter: nil to its own no-op MetricsReporter when not given one. So a user who configures config.metrics_reporter and exports over OTLP never receives otel.otlp_exporter.failure or otel.otlp_exporter.message.compressed_size - despite SpanProcessor already threading config.metrics_reporter through correctly for the otel.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.shutdown never gives the configured metrics_reporter a 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

  1. build_otlp_exporter now passes metrics_reporter: ResilientMetricsReporter.wrap(config.metrics_reporter, logger: config.logger), matching the existing pattern in SpanProcessor.
  2. ResilientMetricsReporter gains an optional #shutdown that forwards to the wrapped reporter only if it responds to :shutdown, rescued the same way as its other three methods.
  3. Langfuse.shutdown calls it after OtelSetup.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 both otel.otlp_exporter.failure and otel.otlp_exporter.message.compressed_size reach 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 #shutdown passthrough, including the no-shutdown-method and raising-reporter cases.

Both changes are backward compatible - nil metrics_reporter still resolves to the SDK's own no-op reporter, and Langfuse.shutdown is a no-op addition for anyone not using metrics_reporter.

Full suite (bundle exec rspec, bundle exec rubocop) passes locally.


Note

Low Risk
Observability plumbing only: shared reporter injection and documentation of app-owned lifecycle, with no changes to export auth or span data paths.

Overview
config.metrics_reporter now feeds both the batch span processor and the default OTLP exporter, so OpenTelemetry export metrics such as otel.otlp_exporter.failure and otel.otlp_exporter.message.compressed_size reach the same reporter as otel.bsp.*.

OtelSetup wraps the configured reporter once with ResilientMetricsReporter and passes that instance into SpanProcessor and build_otlp_exporter. SpanProcessor no longer wraps the reporter itself; it takes metrics_reporter: from setup.

Docs now describe the broader metric scope and state explicitly that Langfuse does not flush, close, or shut down the reporter—call Langfuse.shutdown first, then flush/close backends like DogStatsD. New and updated specs cover shared wiring, real OTLP failure/compressed-size reporting, final batch metrics on shutdown, and that Langfuse.shutdown/reset! do not invoke reporter shutdown.

Reviewed by Cursor Bugbot for commit 4e2b4ef. Bugbot is set up for automated code reviews on this repo. Configure here.

Alan Marx added 3 commits August 24, 2026 15:30
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.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ 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.

Comment thread lib/langfuse.rb Outdated
@kxzk

kxzk commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

@Alan-Marx I updated the branch in commit 854307f.

The refactor passes one resilient metrics_reporter instance to both the batch processor and the default OTLP exporter. Langfuse.shutdown flushes the tracing pipeline but does not flush, close, or shut down the application-owned reporter.

Please verify this sequence with your real reporter integration before we merge:

  1. Create and finish a Langfuse observation.
  2. Call Langfuse.shutdown.
  3. Flush and close the metrics reporter.
  4. Confirm that the final otel.bsp.* and otel.otlp_exporter.* metrics reach your metrics backend.
  5. Confirm that Langfuse never calls the reporter's shutdown method.

Please reply when you have verified the reporter behavior.

@kxzk
kxzk merged commit 4475f7f into simplepractice:main Sep 5, 2026
12 checks passed
@kxzk kxzk mentioned this pull request Sep 5, 2026
4 tasks
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