From f011500e94c7bb21ab497371c8155e1e261306a7 Mon Sep 17 00:00:00 2001 From: Lance Erickson Date: Tue, 27 Jan 2026 10:28:58 -0600 Subject: [PATCH 1/8] Add Ruby 4 checks - h/t @jlsync --- lib/scout_apm/environment.rb | 14 ++++++++++++-- lib/scout_apm/layer.rb | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/scout_apm/environment.rb b/lib/scout_apm/environment.rb index 52702a37..3d3d54ad 100644 --- a/lib/scout_apm/environment.rb +++ b/lib/scout_apm/environment.rb @@ -190,6 +190,16 @@ def ruby_3? @ruby_3 = defined?(RUBY_VERSION) && RUBY_VERSION.match(/^3/) end + def ruby_4? + return @ruby_4 if defined?(@ruby_4) + @ruby_4 = defined?(RUBY_VERSION) && RUBY_VERSION.match(/^4/) + end + + def ruby_2_or_above? + ruby_2? || ruby_3? || ruby_4? + end + + def ruby_minor return @ruby_minor if defined?(@ruby_minor) @ruby_minor = defined?(RUBY_VERSION) && RUBY_VERSION.split(".")[1].to_i @@ -197,12 +207,12 @@ def ruby_minor # Returns true if this Ruby version supports Module#prepend. def supports_module_prepend? - ruby_2? || ruby_3? + ruby_2_or_above? end # Returns true if this Ruby version makes positional and keyword arguments incompatible def supports_kwarg_delegation? - ruby_3? || (ruby_2? && ruby_minor >= 7) + ruby_4? || ruby_3? || (ruby_2? && ruby_minor >= 7) end # Returns a string representation of the OS (ex: darwin, linux) diff --git a/lib/scout_apm/layer.rb b/lib/scout_apm/layer.rb index f780d8a8..467f4e42 100644 --- a/lib/scout_apm/layer.rb +++ b/lib/scout_apm/layer.rb @@ -116,7 +116,7 @@ def capture_backtrace! # In Ruby 2.0+, we can pass the range directly to the caller to reduce the memory footprint. def caller_array # omits the first several callers which are in the ScoutAPM stack. - if ScoutApm::Agent.instance.context.environment.ruby_2? || ScoutApm::Agent.instance.context.environment.ruby_3? + if ScoutApm::Agent.instance.context.environment.ruby_2_or_above? caller(3...BACKTRACE_CALLER_LIMIT) else caller[3...BACKTRACE_CALLER_LIMIT] From fb0389e054784622cba817c5f9af81000729ae71 Mon Sep 17 00:00:00 2001 From: Lance Erickson Date: Tue, 27 Jan 2026 12:07:24 -0600 Subject: [PATCH 2/8] Add Ruby 4 test matrix - Require minitest-mock for testing --- .github/workflows/test.yml | 6 ++++++ scout_apm.gemspec | 1 + 2 files changed, 7 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1492d213..d441fa9c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -55,6 +55,12 @@ jobs: gemfile: gems/sqlite3-v2.gemfile - ruby: 3.4 gemfile: gems/sqlite3-v2.gemfile + - ruby:: 4.0 + - ruby: 4.0 + prepend: true + - ruby: 4.0 + gemfile: gems/instruments.gemfile + test_features: "instruments" env: BUNDLE_GEMFILE: ${{ matrix.gemfile }} SCOUT_TEST_FEATURES: ${{ matrix.test_features }} diff --git a/scout_apm.gemspec b/scout_apm.gemspec index bfd5249f..7addac55 100644 --- a/scout_apm.gemspec +++ b/scout_apm.gemspec @@ -22,6 +22,7 @@ Gem::Specification.new do |s| s.required_ruby_version = '>= 2.1' s.add_development_dependency "minitest" + s.add_development_dependency "minitest-mock" s.add_development_dependency "mocha" s.add_development_dependency "pry" s.add_development_dependency "simplecov" From ec125bb2dcb914593d41fff4840b42a26c6b30d6 Mon Sep 17 00:00:00 2001 From: Lance Erickson Date: Tue, 27 Jan 2026 13:44:43 -0600 Subject: [PATCH 3/8] Fixes for test matrix --- .github/workflows/test.yml | 2 +- scout_apm.gemspec | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d441fa9c..0feeee85 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -55,7 +55,7 @@ jobs: gemfile: gems/sqlite3-v2.gemfile - ruby: 3.4 gemfile: gems/sqlite3-v2.gemfile - - ruby:: 4.0 + - ruby: 4.0 - ruby: 4.0 prepend: true - ruby: 4.0 diff --git a/scout_apm.gemspec b/scout_apm.gemspec index 7addac55..20615110 100644 --- a/scout_apm.gemspec +++ b/scout_apm.gemspec @@ -21,8 +21,8 @@ Gem::Specification.new do |s| s.required_ruby_version = '>= 2.1' + s.add_development_dependency "minitest" - s.add_development_dependency "minitest-mock" s.add_development_dependency "mocha" s.add_development_dependency "pry" s.add_development_dependency "simplecov" @@ -41,4 +41,9 @@ Gem::Specification.new do |s| s.add_development_dependency "guard" s.add_development_dependency "guard-minitest" s.add_development_dependency "m" + + if RUBY_VERSION >= "3.1" + s.add_development_dependency "minitest-mock" + s.add_development_dependency "ostruct" + end end From 0da0d4cefc441a53ed4810f7ebb06de89b0c698f Mon Sep 17 00:00:00 2001 From: Lance Erickson Date: Tue, 27 Jan 2026 14:17:55 -0600 Subject: [PATCH 4/8] More test gem wrangling --- .github/workflows/test.yml | 4 +++- gems/instruments-ruby4.gemfile | 12 ++++++++++++ gems/sqlite3-v2.gemfile | 1 + scout_apm.gemspec | 4 ---- 4 files changed, 16 insertions(+), 5 deletions(-) create mode 100644 gems/instruments-ruby4.gemfile diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0feeee85..ebea87ba 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -56,10 +56,12 @@ jobs: - ruby: 3.4 gemfile: gems/sqlite3-v2.gemfile - ruby: 4.0 + gemfile: gems/sqlite3-v2.gemfile - ruby: 4.0 prepend: true + gemfile: gems/sqlite3-v2.gemfile - ruby: 4.0 - gemfile: gems/instruments.gemfile + gemfile: gems/instruments-ruby4.gemfile test_features: "instruments" env: BUNDLE_GEMFILE: ${{ matrix.gemfile }} diff --git a/gems/instruments-ruby4.gemfile b/gems/instruments-ruby4.gemfile new file mode 100644 index 00000000..9ce6fb0b --- /dev/null +++ b/gems/instruments-ruby4.gemfile @@ -0,0 +1,12 @@ +eval_gemfile("../Gemfile") + +gem 'minitest-mock' +gem 'ostruct' +gem "sqlite3", ">= 2.1" +gem 'httpclient' +gem 'http' +gem 'redis' +gem 'moped' +gem 'actionpack' +gem 'actionview' +gem 'httpx' diff --git a/gems/sqlite3-v2.gemfile b/gems/sqlite3-v2.gemfile index 4f5b704c..c6b93477 100644 --- a/gems/sqlite3-v2.gemfile +++ b/gems/sqlite3-v2.gemfile @@ -2,3 +2,4 @@ eval_gemfile("../Gemfile") gem "sqlite3", ">= 2.1" gem 'minitest-mock' +gem 'ostruct' diff --git a/scout_apm.gemspec b/scout_apm.gemspec index 20615110..5620c9fa 100644 --- a/scout_apm.gemspec +++ b/scout_apm.gemspec @@ -42,8 +42,4 @@ Gem::Specification.new do |s| s.add_development_dependency "guard-minitest" s.add_development_dependency "m" - if RUBY_VERSION >= "3.1" - s.add_development_dependency "minitest-mock" - s.add_development_dependency "ostruct" - end end From 0639b80312c71aa986c6b0b126ea8de1d66952cd Mon Sep 17 00:00:00 2001 From: Lance Erickson Date: Tue, 27 Jan 2026 14:34:41 -0600 Subject: [PATCH 5/8] Use old bundler for a minute --- .github/workflows/test.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ebea87ba..32db170a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -56,13 +56,16 @@ jobs: - ruby: 3.4 gemfile: gems/sqlite3-v2.gemfile - ruby: 4.0 + bundler: 2 gemfile: gems/sqlite3-v2.gemfile - ruby: 4.0 prepend: true gemfile: gems/sqlite3-v2.gemfile + bundler: 2 - ruby: 4.0 gemfile: gems/instruments-ruby4.gemfile test_features: "instruments" + bundler: 2 env: BUNDLE_GEMFILE: ${{ matrix.gemfile }} SCOUT_TEST_FEATURES: ${{ matrix.test_features }} From 76e4ce25801aca875abe1c199e1ccd4f151014b4 Mon Sep 17 00:00:00 2001 From: Lance Erickson Date: Wed, 28 Jan 2026 10:17:31 -0600 Subject: [PATCH 6/8] Consider no sqlite version in gemspec --- scout_apm.gemspec | 2 +- test/unit/instruments/action_view_test.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scout_apm.gemspec b/scout_apm.gemspec index 5620c9fa..481f73e4 100644 --- a/scout_apm.gemspec +++ b/scout_apm.gemspec @@ -35,7 +35,7 @@ Gem::Specification.new do |s| # tests. Specific versions are pulled in using specific gemfiles, e.g. # `gems/rails3.gemfile`. s.add_development_dependency "activerecord" - s.add_development_dependency "sqlite3", "~> 1.4" + s.add_development_dependency "sqlite3" s.add_development_dependency "rubocop" s.add_development_dependency "guard" diff --git a/test/unit/instruments/action_view_test.rb b/test/unit/instruments/action_view_test.rb index 2a9b3795..c61c2d87 100644 --- a/test/unit/instruments/action_view_test.rb +++ b/test/unit/instruments/action_view_test.rb @@ -4,9 +4,9 @@ if (ENV["SCOUT_TEST_FEATURES"] || "").include?("instruments") require 'test_helper' - require 'action_view' require 'action_pack' require 'action_controller' + require 'action_view' FIXTURE_LOAD_PATH = File.expand_path("fixtures", __dir__) From 0520691d3ef5ddab4a645a944f6378a216982e57 Mon Sep 17 00:00:00 2001 From: Lance Erickson Date: Thu, 29 Jan 2026 10:37:51 -0600 Subject: [PATCH 7/8] Stub Rails in questionable way for ActionView --- test/test_helper.rb | 5 ++++- test/unit/instruments/action_view_test.rb | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/test/test_helper.rb b/test/test_helper.rb index 9261dba6..b54c4313 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -81,7 +81,10 @@ def remove_rails_namespace def fake_rails(version) remove_rails_namespace if (ENV["SCOUT_TEST_FEATURES"] || "").include?("instruments") - Kernel.const_set("Rails", Module.new) + Kernel.const_set("Rails", Module.new { + # ActionView 8.1+ StructuredEventSubscriber calls Rails.try(:root) + def self.root; nil; end + }) Kernel.const_set("ActionController", Module.new) r = Kernel.const_get("Rails") r.const_set("VERSION", Module.new) diff --git a/test/unit/instruments/action_view_test.rb b/test/unit/instruments/action_view_test.rb index c61c2d87..2b6006b9 100644 --- a/test/unit/instruments/action_view_test.rb +++ b/test/unit/instruments/action_view_test.rb @@ -4,6 +4,19 @@ if (ENV["SCOUT_TEST_FEATURES"] || "").include?("instruments") require 'test_helper' + + # Rails 8.1+ ActionView::StructuredEventSubscriber calls Rails.try(:root) + # https://github.com/rails/rails/blob/3ad79fcede4f9b620f03b9fd76507d9fb3c07e95/actionview/lib/action_view/structured_event_subscriber.rb#L67 + # which raises NameError if Rails is not defined. Define a minimal stub. + # Maybe this can get fixed at some point. + unless defined?(Rails) + module Rails + def self.root + nil + end + end + end + require 'action_pack' require 'action_controller' require 'action_view' @@ -62,6 +75,9 @@ class RenderTest < ActionController::TestCase def setup super + # Ensure Rails exists - other tests may have called clean_fake_rails + fake_rails(8) + @controller.logger = ActiveSupport::Logger.new(nil) ActionView::Base.logger = ActiveSupport::Logger.new(nil) @@ -75,6 +91,7 @@ def teardown ActionView::Base.logger = nil ActionController::Base.view_paths = @old_view_paths + clean_fake_rails end def test_partial_instrumentation From e4ef75ebb2d28d56ef10de97776ab31f791f8b09 Mon Sep 17 00:00:00 2001 From: Lance Erickson Date: Fri, 30 Jan 2026 10:53:40 -0600 Subject: [PATCH 8/8] Don't force bundler 2 in later Ruby --- .github/workflows/test.yml | 3 --- CHANGELOG.markdown | 4 ++++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 32db170a..ebea87ba 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -56,16 +56,13 @@ jobs: - ruby: 3.4 gemfile: gems/sqlite3-v2.gemfile - ruby: 4.0 - bundler: 2 gemfile: gems/sqlite3-v2.gemfile - ruby: 4.0 prepend: true gemfile: gems/sqlite3-v2.gemfile - bundler: 2 - ruby: 4.0 gemfile: gems/instruments-ruby4.gemfile test_features: "instruments" - bundler: 2 env: BUNDLE_GEMFILE: ${{ matrix.gemfile }} SCOUT_TEST_FEATURES: ${{ matrix.test_features }} diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown index df0c3f02..c426104d 100644 --- a/CHANGELOG.markdown +++ b/CHANGELOG.markdown @@ -1,3 +1,7 @@ +# Pending + +- Ruby 4 support + # 6.0.2 - Fix `endpoint_sample_rate` and `job_sample_rate` to support float values