Skip to content

fix(ruby): observability plugin compatibility — require server-sdk >= 8.11.0 and fix Rails Railtie load order#575

Open
ccschmitz-launchdarkly wants to merge 5 commits into
mainfrom
fix/ruby-observability-sdk-min-version
Open

fix(ruby): observability plugin compatibility — require server-sdk >= 8.11.0 and fix Rails Railtie load order#575
ccschmitz-launchdarkly wants to merge 5 commits into
mainfrom
fix/ruby-observability-sdk-min-version

Conversation

@ccschmitz-launchdarkly
Copy link
Copy Markdown
Contributor

@ccschmitz-launchdarkly ccschmitz-launchdarkly commented May 29, 2026

Summary

Two compatibility fixes for the Ruby observability plugin, both surfaced by a customer trying to get launchdarkly-observability working in a Rails app (Slack thread).

1. Require launchdarkly-server-sdk >= 8.11.0

The plugin includes LaunchDarkly::Interfaces::Plugins::Plugin at load time, but that interface was only added in server-sdk 8.11.0 ("Add experimental plugin support"). The gemspec allowed >= 8.0, so bundler could resolve an older, incompatible SDK and require 'launchdarkly_observability' would blow up with:

uninitialized constant LaunchDarkly::Interfaces::Plugins

  • Bump the gemspec dependency from >= 8.0 to >= 8.11.0.
  • Add a load-time guard that raises an actionable LoadError (naming the installed SDK version and the bundle update command) instead of the bare constant error, for anyone already sitting on an old SDK.
  • Update the README dependency note.

2. Fix Rails Railtie load-order crash on lazy require

After upgrading the SDK, the customer hit a second error:

NameError: uninitialized constant LaunchDarklyObservability::ControllerHelpers

The Railtie's config.after_initialize block registers via ActiveSupport's lazy load hooks, which run immediately if the :after_initialize event has already fired. The customer required the gem lazily from an autoloaded model (app/models/feature.rb) during a request — i.e. after Rails had booted — so the block executed synchronously while rails.rb was still loading. It referenced ControllerHelpers and the private attach_otel_log_bridge, both defined below the Railtie, so the require crashed. In a normal boot the block is deferred until the file finishes loading, which is why standard setups (and our existing tests) never hit it.

  • Move ControllerHelpers / ViewHelpers above the Railtie.
  • Move the Railtie's private class methods above config.after_initialize.
  • The require is now order-independent regardless of where/when the gem is loaded.

How did you test this change?

  • Reproduced the customer's exact ControllerHelpers crash with a regression test that simulates the already-booted condition (immediate after_initialize); it fails on the old ordering and passes on the new one.
  • Verified the min-version guard: require succeeds on server-sdk 8.12.2/8.13.0; a simulated 8.8.3 (no Plugins interface) raises the clear LoadError.
  • Confirmed default registration paths (explicit project_id and auto-extracted SDK key) work on server-sdk 8.13.0.
  • bundle exec rake test → 114 runs, 0 failures, 0 errors.
  • bundle exec rubocop on changed files → no offenses.

Are there any deployment considerations?

None — no migrations or data changes. The next launchdarkly-observability release will require server-sdk >= 8.11.0; users on older SDKs were already broken at require time and now get a clear upgrade message. The Railtie reordering is behavior-preserving for normal boots and only fixes the lazy-require case.


Note

Low Risk
Targeted dependency floor, load-time guard, and file reordering with regression coverage; normal Rails boots are behavior-preserving.

Overview
This PR fixes two customer-facing compatibility issues in launchdarkly-observability.

Server SDK minimum version: The gemspec, README, and Rails e2e lockfiles now require launchdarkly-server-sdk >= 8.11.0 (plugin APIs landed in 8.11.0). plugin.rb raises a clear LoadError with version and upgrade guidance if an older SDK is loaded at require time.

Rails lazy-load crash: rails.rb is reordered so ControllerHelpers, ViewHelpers, and the Railtie’s private attach_otel_log_bridge helpers are defined before the Railtie and its config.after_initialize hook. When the gem is required after boot, that hook can run synchronously during the file load; the old order caused uninitialized constant LaunchDarklyObservability::ControllerHelpers. A new rails_railtie_test.rb regression test simulates post-boot require with immediate after_initialize.

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

The observability plugin includes LaunchDarkly::Interfaces::Plugins::Plugin at
load time, but that interface was only added in launchdarkly-server-sdk 8.11.0.
The gemspec allowed >= 8.0, so installing against an older SDK (e.g. 8.8.3)
raised a bare 'uninitialized constant LaunchDarkly::Interfaces::Plugins' on
require 'launchdarkly_observability'.

- Bump the gemspec dependency to >= 8.11.0 so bundler resolves a compatible SDK
- Add a load-time guard that raises an actionable LoadError naming the installed
  SDK version and the upgrade command, instead of the cryptic constant error
- Update the README dependency note
@ccschmitz-launchdarkly ccschmitz-launchdarkly marked this pull request as ready for review May 29, 2026 16:57
@ccschmitz-launchdarkly ccschmitz-launchdarkly requested a review from a team as a code owner May 29, 2026 16:57
…uire crash

When the gem is required lazily after Rails has finished booting (e.g. from an
autoloaded model during a request), ActiveSupport's :after_initialize load hook
has already fired, so the Railtie's config.after_initialize block runs
synchronously while rails.rb is still being evaluated. The block referenced
ControllerHelpers and the private attach_otel_log_bridge method, both defined
*after* the Railtie in the file, so the require crashed with:

  uninitialized constant LaunchDarklyObservability::ControllerHelpers

In a normal boot (gem required before init completes) the block is deferred
until the file finishes loading, which is why standard setups never hit it.

- Move ControllerHelpers/ViewHelpers above the Railtie
- Move the Railtie's private class methods above config.after_initialize
- Add a regression test simulating the already-booted (immediate hook) condition
@ccschmitz-launchdarkly ccschmitz-launchdarkly changed the title fix(ruby): require launchdarkly-server-sdk >= 8.11.0 for plugin support fix(ruby): observability plugin compatibility — require server-sdk >= 8.11.0 and fix Rails Railtie load order May 29, 2026
… state

The regression test stubs a booted Rails environment, including a Rails.logger.
The LD SDK's Config.default_logger returns Rails.logger whenever Rails responds
to :logger, so a leaked stub logger was picked up by later tests (IntegrationTest)
and crashed client.close with 'undefined method info'. This only surfaced in CI
because of test ordering (random seed).

- Use a real ::Logger.new(File::NULL) instead of a bare Object
- Track and fully remove every constant and the :logger singleton method in teardown
- Extract Rails/ActionController/ActionView stubs into helpers
The Rails e2e apps depend on launchdarkly-observability as a path gem. Bumping the
gemspec's launchdarkly-server-sdk constraint to >= 8.11.0 left their committed
Gemfile.lock files out of date, so CI's frozen bundle install failed with exit 16
('gemspecs for path gems changed, but the lockfile can't be updated'). Re-resolved
both lockfiles; the only change is the recorded dependency constraint (resolved
server-sdk 8.13.0 already satisfies it).
Rubocop's Lint/DuplicateMethods flagged the repeated inline
`Class.new { def self.include(_mod); end }` stubs as duplicate definitions on
the enclosing scope (a static-analysis limitation with anonymous classes), failing
CI on the rubocop version it resolves. Extract a single noop_includable_class helper
so the no-op include is defined once.
@ccschmitz-launchdarkly ccschmitz-launchdarkly enabled auto-merge (squash) May 29, 2026 21:58
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