From 6c181f4a8b1710bc42b90f1bee30f57c89f705ad Mon Sep 17 00:00:00 2001 From: Lance Erickson Date: Thu, 12 Feb 2026 12:54:51 -0600 Subject: [PATCH 1/2] Rough cut of Rage integration --- lib/scout_apm.rb | 11 +- lib/scout_apm/environment.rb | 3 + lib/scout_apm/framework_integrations/rage.rb | 56 +++ lib/scout_apm/instrument_manager.rb | 2 + lib/scout_apm/instruments/rage.rb | 35 ++ .../instruments/rage_telemetry_handler.rb | 123 +++++++ lib/scout_apm/request_manager.rb | 26 +- test/unit/environment_test.rb | 6 + test/unit/instruments/rage_test.rb | 338 ++++++++++++++++++ 9 files changed, 593 insertions(+), 7 deletions(-) create mode 100644 lib/scout_apm/framework_integrations/rage.rb create mode 100644 lib/scout_apm/instruments/rage.rb create mode 100644 lib/scout_apm/instruments/rage_telemetry_handler.rb create mode 100644 test/unit/instruments/rage_test.rb diff --git a/lib/scout_apm.rb b/lib/scout_apm.rb index c77244af..0a101001 100644 --- a/lib/scout_apm.rb +++ b/lib/scout_apm.rb @@ -72,6 +72,7 @@ module ScoutApm require 'scout_apm/framework_integrations/rails_2' require 'scout_apm/framework_integrations/rails_3_or_4' +require 'scout_apm/framework_integrations/rage' require 'scout_apm/framework_integrations/sinatra' require 'scout_apm/framework_integrations/ruby' @@ -102,6 +103,7 @@ module ScoutApm require 'scout_apm/instruments/middleware_detailed' # Disabled by default, see the file for more details require 'scout_apm/instruments/rails_router' require 'scout_apm/instruments/grape' +require 'scout_apm/instruments/rage' require 'scout_apm/instruments/sinatra' require 'allocations' @@ -233,7 +235,7 @@ class Railtie < Rails::Railtie if defined?(Prism) || defined?(Parser::TreeRewriter) ScoutApm::Agent.instance.context.logger.debug("AutoInstruments is enabled.") require 'scout_apm/auto_instrument' - else + else # AutoInstruments is turned on, but we don't have the prerequisites to use it # Prism should be available for Ruby >= 3.3.0 ScoutApm::Agent.instance.context.logger.debug("AutoInstruments is enabled, but Parser::TreeRewriter is missing. Update 'parser' gem to >= 2.5.0.") @@ -259,6 +261,13 @@ class Railtie < Rails::Railtie end end end +elsif defined?(::Rage) && defined?(::Rage::Configuration) + # Rage framework integration. Install the agent in an after_initialize hook + # so it runs before Rage::Telemetry.__setup compiles the tracer methods. + # This is analogous to the Rails Railtie initializer above. + ::Rage.config.after_initialize do + ScoutApm::Agent.instance.install + end else ScoutApm::Agent.instance.install end diff --git a/lib/scout_apm/environment.rb b/lib/scout_apm/environment.rb index 3d3d54ad..864d0edd 100644 --- a/lib/scout_apm/environment.rb +++ b/lib/scout_apm/environment.rb @@ -38,6 +38,7 @@ class Environment FRAMEWORK_INTEGRATIONS = [ ScoutApm::FrameworkIntegrations::Rails2.new, ScoutApm::FrameworkIntegrations::Rails3Or4.new, + ScoutApm::FrameworkIntegrations::Rage.new, ScoutApm::FrameworkIntegrations::Sinatra.new, ScoutApm::FrameworkIntegrations::Ruby.new, # Fallback if none match ] @@ -111,6 +112,8 @@ def framework_root RAILS_ROOT.to_s elsif framework == :rails3_or_4 Rails.root + elsif framework == :rage + ::Rage.root.to_s elsif framework == :sinatra Sinatra::Application.root || "." else diff --git a/lib/scout_apm/framework_integrations/rage.rb b/lib/scout_apm/framework_integrations/rage.rb new file mode 100644 index 00000000..5170349d --- /dev/null +++ b/lib/scout_apm/framework_integrations/rage.rb @@ -0,0 +1,56 @@ +module ScoutApm + module FrameworkIntegrations + class Rage + def name + :rage + end + + def human_name + "Rage" + end + + def version + ::Rage::VERSION + end + + def present? + defined?(::Rage) && defined?(::Rage::VERSION) && !defined?(::Rails) + end + + def application_name + nil + end + + def env + ::Rage.env.to_s + end + + def database_engine + return @database_engine if @database_engine + default = :postgres + + @database_engine = if defined?(ActiveRecord::Base) + adapter = raw_database_adapter + + case adapter.to_s + when "postgres" then :postgres + when "postgresql" then :postgres + when "postgis" then :postgres + when "sqlite3" then :sqlite + when "sqlite" then :sqlite + when "mysql" then :mysql + when "mysql2" then :mysql + when "sqlserver" then :sqlserver + else default + end + else + default + end + end + + def raw_database_adapter + ActiveRecord::Base.connection_db_config.configuration_hash[:adapter] rescue nil + end + end + end +end diff --git a/lib/scout_apm/instrument_manager.rb b/lib/scout_apm/instrument_manager.rb index 0f100ce6..d2e77e8d 100644 --- a/lib/scout_apm/instrument_manager.rb +++ b/lib/scout_apm/instrument_manager.rb @@ -23,6 +23,8 @@ def install! else install_instrument(ScoutApm::Instruments::MiddlewareSummary) end + when :rage then + install_instrument(ScoutApm::Instruments::Rage) end install_instrument(ScoutApm::Instruments::ActionView) diff --git a/lib/scout_apm/instruments/rage.rb b/lib/scout_apm/instruments/rage.rb new file mode 100644 index 00000000..439ec287 --- /dev/null +++ b/lib/scout_apm/instruments/rage.rb @@ -0,0 +1,35 @@ +# Instrument wrapper for the Rage framework. +# +# Follows the standard Scout instrument interface (initialize/install/installed?) +# so it can be managed by InstrumentManager. The actual instrumentation is done +# by RageTelemetryHandler, which is registered with Rage's telemetry system. + +module ScoutApm + module Instruments + class Rage + attr_reader :context + + def initialize(context) + @context = context + @installed = false + end + + def installed? + @installed + end + + def install(prepend: false) + return unless defined?(::Rage::Telemetry::Handler) + return if @installed + + require 'scout_apm/instruments/rage_telemetry_handler' + + handler = ScoutApm::Instruments::RageTelemetryHandler.new + ::Rage.config.telemetry.use(handler) + + @installed = true + context.logger.info("Instrumenting Rage via Telemetry Handler") + end + end + end +end diff --git a/lib/scout_apm/instruments/rage_telemetry_handler.rb b/lib/scout_apm/instruments/rage_telemetry_handler.rb new file mode 100644 index 00000000..454fe9a1 --- /dev/null +++ b/lib/scout_apm/instruments/rage_telemetry_handler.rb @@ -0,0 +1,123 @@ +# Rage telemetry handler that creates Scout APM layers for controller actions, +# cable/WebSocket actions, and deferred tasks (background jobs). +# +# Registered with Rage's telemetry system via: +# Rage.config.telemetry.use(ScoutApm::Instruments::RageTelemetryHandler.new) +# +# Each handler method wraps a Rage span with Scout layers, enabling automatic +# tracking of request timing, database queries, external HTTP calls, and errors. + +module ScoutApm + module Instruments + class RageTelemetryHandler < ::Rage::Telemetry::Handler + handle "controller.action.process", with: :track_controller + handle "cable.connection.process", with: :track_cable_connection + handle "cable.action.process", with: :track_cable_action + handle "deferred.task.process", with: :track_deferred_task + + # Instruments HTTP controller actions. + # Creates a "Controller" root layer (e.g. "UsersController#index"). + def track_controller(name:, request:, env:) + req = ScoutApm::RequestManager.lookup + req.annotate_request(:uri => request.path) rescue nil + + if ScoutApm::Agent.instance.context.config.value("collect_remote_ip") + req.context.add_user(:ip => env["REMOTE_ADDR"]) rescue nil + end + + layer = ScoutApm::Layer.new("Controller", name) + req.start_layer(layer) + begin + result = yield + if result.error? + req.error! + capture_error(result.exception, env) + end + result + rescue => e + req.error! + raise + ensure + req.stop_layer + end + end + + # Instruments cable connection lifecycle (connect/disconnect). + # Creates a "Controller" layer (e.g. "ApplicationCable::Connection#connect"). + def track_cable_connection(name:, env:) + req = ScoutApm::RequestManager.lookup + layer = ScoutApm::Layer.new("Controller", name) + req.start_layer(layer) + begin + result = yield + if result.error? + req.error! + capture_error(result.exception, env) + end + result + rescue => e + req.error! + raise + ensure + req.stop_layer + end + end + + # Instruments cable channel actions (e.g. receiving a message). + # Creates a "Controller" layer (e.g. "ChatChannel#receive"). + def track_cable_action(name:, env:) + req = ScoutApm::RequestManager.lookup + layer = ScoutApm::Layer.new("Controller", name) + req.start_layer(layer) + begin + result = yield + if result.error? + req.error! + capture_error(result.exception, env) + end + result + rescue => e + req.error! + raise + ensure + req.stop_layer + end + end + + # Instruments deferred task execution (Rage's background job system). + # Creates a "Queue" + "Job" layer pair (e.g. "SendEmailTask#perform"). + def track_deferred_task(name:, task_class:) + req = ScoutApm::RequestManager.lookup + req.start_layer(ScoutApm::Layer.new("Queue", "rage_deferred")) + started_queue = true + req.start_layer(ScoutApm::Layer.new("Job", name)) + started_job = true + begin + result = yield + req.error! if result.error? + result + rescue => e + req.error! + raise + ensure + req.stop_layer if started_job + req.stop_layer if started_queue + end + end + + private + + def capture_error(exception, env) + return unless exception + return unless ScoutApm::ErrorService.enabled? + + context = ScoutApm::Agent.instance.context + return if context.ignored_exceptions.ignore?(exception) + + context.error_buffer.capture(exception, env) + rescue + # Don't let error capture failures affect the request + end + end + end +end diff --git a/lib/scout_apm/request_manager.rb b/lib/scout_apm/request_manager.rb index 91f4835c..ccd41b1b 100644 --- a/lib/scout_apm/request_manager.rb +++ b/lib/scout_apm/request_manager.rb @@ -1,15 +1,20 @@ # Request manager handles the threadlocal variable that holds the current -# request. If there isn't one, then create one +# request. If there isn't one, then create one. +# +# Under Rage (fiber-per-request concurrency), uses Fiber-local storage +# instead of Thread-local storage to isolate concurrent requests. module ScoutApm class RequestManager + STORAGE_KEY = :scout_request + def self.lookup find || create end - # Get the current Thread local, and detecting, and not returning a stale request + # Get the current request, detecting and not returning a stale request def self.find - req = Thread.current[:scout_request] + req = storage[STORAGE_KEY] if req && (req.stopping? || req.recorded?) nil @@ -18,12 +23,21 @@ def self.find end end - # Create a new TrackedRequest object for this thread - # XXX: Figure out who is in charge of creating a `FakeStore` - previously was here + # Create a new TrackedRequest object for this fiber/thread def self.create agent_context = ScoutApm::Agent.instance.context store = agent_context.store - Thread.current[:scout_request] = TrackedRequest.new(agent_context, store) + storage[STORAGE_KEY] = TrackedRequest.new(agent_context, store) + end + + # Use Fiber-local storage under Rage (fiber-per-request), + # Thread-local storage everywhere else (thread-per-request). + def self.storage + if defined?(::Rage) && !defined?(::Rails) + Fiber + else + Thread.current + end end end end diff --git a/test/unit/environment_test.rb b/test/unit/environment_test.rb index e8b98062..d3b442ac 100644 --- a/test/unit/environment_test.rb +++ b/test/unit/environment_test.rb @@ -3,9 +3,15 @@ require 'scout_apm/environment' class EnvironmentTest < Minitest::Test + def setup + # Ensure no stubs from other test files leak in + Object.send(:remove_const, :Rage) if defined?(::Rage) + end + def teardown clean_fake_rails clean_fake_sinatra + Object.send(:remove_const, :Rage) if defined?(::Rage) end def test_framework_rails diff --git a/test/unit/instruments/rage_test.rb b/test/unit/instruments/rage_test.rb new file mode 100644 index 00000000..ff33602f --- /dev/null +++ b/test/unit/instruments/rage_test.rb @@ -0,0 +1,338 @@ +require 'test_helper' + +# Helper to set up and tear down Rage stubs. +# Rage stubs are only defined during tests that need them. +def fake_rage + return if defined?(::Rage::VERSION) + + Object.const_set(:Rage, Module.new) + + ::Rage.const_set(:VERSION, "1.20.0") + + ::Rage.define_singleton_method(:env) { "test" } + ::Rage.define_singleton_method(:root) { Pathname.new("/tmp/rage_test_app") } + + # Telemetry system stubs + ::Rage.const_set(:Telemetry, Module.new) + + handler_klass = Class.new do + class << self + attr_accessor :handlers_map + + def handle(*span_ids, with:, except: nil) + @handlers_map ||= {} + span_ids.each do |span_id| + @handlers_map[span_id] ||= Set.new + @handlers_map[span_id] << with + end + end + + def inherited(klass) + klass.handlers_map = @handlers_map&.dup || {} + end + end + end + ::Rage::Telemetry.const_set(:Handler, handler_klass) + + span_result_klass = Struct.new(:exception) do + def error? + !!exception + end + + def success? + !error? + end + end + ::Rage::Telemetry.const_set(:SpanResult, span_result_klass) + + # Configuration stubs + config_klass = Class.new do + def initialize + @telemetry_config = Object.new + telemetry_handlers = [] + @telemetry_config.define_singleton_method(:use) { |h| telemetry_handlers << h } + @telemetry_config.define_singleton_method(:handlers) { telemetry_handlers } + end + + def telemetry + @telemetry_config + end + end + ::Rage.const_set(:Configuration, config_klass) + + config_instance = ::Rage::Configuration.new + ::Rage.define_singleton_method(:config) { config_instance } +end + +def clean_fake_rage + Object.send(:remove_const, :Rage) if defined?(::Rage) +end + +# Load the Rage instrument files (they need ::Rage::Telemetry::Handler to be defined) +# We define the stubs, load the files, then tests can clean up and re-stub as needed. +fake_rage +require 'scout_apm/instruments/rage' +require 'scout_apm/instruments/rage_telemetry_handler' + +class RageFrameworkIntegrationTest < Minitest::Test + def setup + super + clean_fake_rails + fake_rage + end + + def teardown + super + clean_fake_rails + clean_fake_rage + end + + def test_present_when_rage_defined + integration = ScoutApm::FrameworkIntegrations::Rage.new + assert integration.present?, "Rage integration should be present when ::Rage is defined" + end + + def test_name + integration = ScoutApm::FrameworkIntegrations::Rage.new + assert_equal :rage, integration.name + end + + def test_human_name + integration = ScoutApm::FrameworkIntegrations::Rage.new + assert_equal "Rage", integration.human_name + end + + def test_env + integration = ScoutApm::FrameworkIntegrations::Rage.new + assert_equal "test", integration.env + end + + def test_version + integration = ScoutApm::FrameworkIntegrations::Rage.new + assert_equal "1.20.0", integration.version + end + + def test_not_present_when_rails_also_defined + fake_rails(7) + integration = ScoutApm::FrameworkIntegrations::Rage.new + assert_equal false, integration.present? + end +end + +class RageInstrumentTest < Minitest::Test + def setup + super + clean_fake_rails + fake_rage + @context = ScoutApm::AgentContext.new + end + + def teardown + super + clean_fake_rails + clean_fake_rage + end + + def test_install_registers_telemetry_handler + instrument = ScoutApm::Instruments::Rage.new(@context) + instrument.install + + assert instrument.installed? + handlers = ::Rage.config.telemetry.handlers + assert handlers.any? { |h| h.is_a?(ScoutApm::Instruments::RageTelemetryHandler) } + end + + def test_install_is_idempotent + instrument = ScoutApm::Instruments::Rage.new(@context) + instrument.install + instrument.install + + handlers = ::Rage.config.telemetry.handlers + rage_handlers = handlers.select { |h| h.is_a?(ScoutApm::Instruments::RageTelemetryHandler) } + assert_equal 1, rage_handlers.size + end +end + +class RageTelemetryHandlerTest < Minitest::Test + def setup + super + clean_fake_rails + fake_rage + @handler = ScoutApm::Instruments::RageTelemetryHandler.new + end + + def teardown + super + clean_fake_rails + clean_fake_rage + Fiber[:scout_request] = nil + end + + def test_handler_registers_for_controller_span + map = ScoutApm::Instruments::RageTelemetryHandler.handlers_map + assert map.key?("controller.action.process"), "Should handle controller.action.process" + end + + def test_handler_registers_for_cable_connection_span + map = ScoutApm::Instruments::RageTelemetryHandler.handlers_map + assert map.key?("cable.connection.process"), "Should handle cable.connection.process" + end + + def test_handler_registers_for_cable_action_span + map = ScoutApm::Instruments::RageTelemetryHandler.handlers_map + assert map.key?("cable.action.process"), "Should handle cable.action.process" + end + + def test_handler_registers_for_deferred_task_span + map = ScoutApm::Instruments::RageTelemetryHandler.handlers_map + assert map.key?("deferred.task.process"), "Should handle deferred.task.process" + end + + def test_track_controller_creates_controller_layer + fake_request = Struct.new(:path).new("/users") + fake_env = { "REMOTE_ADDR" => "127.0.0.1" } + + @handler.track_controller( + name: "UsersController#index", + request: fake_request, + env: fake_env + ) { ::Rage::Telemetry::SpanResult.new(nil) } + + req = Fiber[:scout_request] + assert req, "Should have created a tracked request" + assert_equal "Controller", req.root_layer.type + assert_equal "UsersController#index", req.root_layer.name + end + + def test_track_controller_annotates_uri + fake_request = Struct.new(:path).new("/users/42") + fake_env = {} + + @handler.track_controller( + name: "UsersController#show", + request: fake_request, + env: fake_env + ) { ::Rage::Telemetry::SpanResult.new(nil) } + + req = Fiber[:scout_request] + assert_equal "/users/42", req.annotations[:uri] + end + + def test_track_controller_marks_error_on_exception_result + fake_request = Struct.new(:path).new("/users") + fake_env = {} + error = RuntimeError.new("something broke") + + @handler.track_controller( + name: "UsersController#index", + request: fake_request, + env: fake_env + ) { ::Rage::Telemetry::SpanResult.new(error) } + + req = Fiber[:scout_request] + assert req.error?, "Request should be marked as errored" + end + + def test_track_controller_reraises_on_exception + fake_request = Struct.new(:path).new("/users") + fake_env = {} + + assert_raises(RuntimeError) do + @handler.track_controller( + name: "UsersController#index", + request: fake_request, + env: fake_env + ) { raise RuntimeError, "boom" } + end + + req = Fiber[:scout_request] + assert req.error?, "Request should be marked as errored on raised exception" + end + + def test_track_cable_connection_creates_controller_layer + fake_env = {} + + @handler.track_cable_connection( + name: "ApplicationCable::Connection#connect", + env: fake_env + ) { ::Rage::Telemetry::SpanResult.new(nil) } + + req = Fiber[:scout_request] + assert_equal "Controller", req.root_layer.type + assert_equal "ApplicationCable::Connection#connect", req.root_layer.name + end + + def test_track_cable_action_creates_controller_layer + fake_env = {} + + @handler.track_cable_action( + name: "ChatChannel#receive", + env: fake_env + ) { ::Rage::Telemetry::SpanResult.new(nil) } + + req = Fiber[:scout_request] + assert_equal "Controller", req.root_layer.type + assert_equal "ChatChannel#receive", req.root_layer.name + end + + def test_track_deferred_task_creates_queue_and_job_layers + @handler.track_deferred_task( + name: "SendEmailTask#perform", + task_class: "SendEmailTask" + ) { ::Rage::Telemetry::SpanResult.new(nil) } + + req = Fiber[:scout_request] + # The root layer should be Queue, with Job as a child + assert_equal "Queue", req.root_layer.type + assert_equal "rage_deferred", req.root_layer.name + + job_layer = req.root_layer.children.first + assert job_layer, "Should have a child Job layer" + assert_equal "Job", job_layer.type + assert_equal "SendEmailTask#perform", job_layer.name + end + + def test_track_deferred_task_marks_error + error = RuntimeError.new("job failed") + + @handler.track_deferred_task( + name: "SendEmailTask#perform", + task_class: "SendEmailTask" + ) { ::Rage::Telemetry::SpanResult.new(error) } + + req = Fiber[:scout_request] + assert req.error?, "Request should be marked as errored" + end +end + +class RequestManagerFiberStorageTest < Minitest::Test + def setup + super + clean_fake_rails + fake_rage + end + + def teardown + super + clean_fake_rails + clean_fake_rage + Fiber[:scout_request] = nil + end + + def test_uses_fiber_storage_under_rage + storage = ScoutApm::RequestManager.storage + assert_equal Fiber, storage, "Should use Fiber storage when Rage is defined and Rails is not" + end + + def test_uses_thread_storage_under_rails + fake_rails(7) + storage = ScoutApm::RequestManager.storage + assert_equal Thread.current, storage, "Should use Thread.current when Rails is defined" + end + + def test_lookup_creates_request_in_fiber_storage + req = ScoutApm::RequestManager.lookup + assert_instance_of ScoutApm::TrackedRequest, req + assert_equal req, Fiber[:scout_request] + end +end From 4f577071cb4734bc72a4941efc0850dd2dbe8453 Mon Sep 17 00:00:00 2001 From: Lance Erickson Date: Fri, 13 Feb 2026 10:14:38 -0600 Subject: [PATCH 2/2] Fixes --- lib/scout_apm.rb | 13 ++++++++ lib/scout_apm/environment.rb | 1 + lib/scout_apm/error_service.rb | 4 +-- .../instruments/rage_telemetry_handler.rb | 7 +++-- lib/scout_apm/server_integrations/iodine.rb | 30 +++++++++++++++++++ 5 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 lib/scout_apm/server_integrations/iodine.rb diff --git a/lib/scout_apm.rb b/lib/scout_apm.rb index 0a101001..fb1f23f6 100644 --- a/lib/scout_apm.rb +++ b/lib/scout_apm.rb @@ -57,6 +57,7 @@ module ScoutApm require 'scout_apm/server_integrations/thin' require 'scout_apm/server_integrations/unicorn' require 'scout_apm/server_integrations/webrick' +require 'scout_apm/server_integrations/iodine' require 'scout_apm/server_integrations/null' require 'scout_apm/background_job_integrations/sidekiq' @@ -268,6 +269,18 @@ class Railtie < Rails::Railtie ::Rage.config.after_initialize do ScoutApm::Agent.instance.install end +elsif defined?(::Rage) && !defined?(::Rails) + # Rage gem is loaded but rage/all.rb hasn't run yet, so Rage::Configuration + # isn't available. Intercept Rage.configure (called after rage/all.rb loads) + # to register our after_initialize hook at the right time. + ::Rage.singleton_class.prepend(Module.new do + def configure(&block) + super(&block) + config.after_initialize do + ScoutApm::Agent.instance.install + end + end + end) else ScoutApm::Agent.instance.install end diff --git a/lib/scout_apm/environment.rb b/lib/scout_apm/environment.rb index 864d0edd..2f98434b 100644 --- a/lib/scout_apm/environment.rb +++ b/lib/scout_apm/environment.rb @@ -20,6 +20,7 @@ class Environment ScoutApm::ServerIntegrations::Puma.new(STDOUT_LOGGER), ScoutApm::ServerIntegrations::Thin.new(STDOUT_LOGGER), ScoutApm::ServerIntegrations::Webrick.new(STDOUT_LOGGER), + ScoutApm::ServerIntegrations::Iodine.new(STDOUT_LOGGER), ScoutApm::ServerIntegrations::Null.new(STDOUT_LOGGER), # must be last ] diff --git a/lib/scout_apm/error_service.rb b/lib/scout_apm/error_service.rb index fc7b63f4..98054433 100644 --- a/lib/scout_apm/error_service.rb +++ b/lib/scout_apm/error_service.rb @@ -18,9 +18,9 @@ def self.capture(exception, params = {}) return if disabled? context = ScoutApm::Agent.instance.context - return if context.ignored_exceptions.ignore?(exception) + return if context.ignored_exceptions.ignored?(exception) - context.errors_buffer.capture(exception, env) + context.error_buffer.capture(exception, params) end def self.enabled? diff --git a/lib/scout_apm/instruments/rage_telemetry_handler.rb b/lib/scout_apm/instruments/rage_telemetry_handler.rb index 454fe9a1..43359b01 100644 --- a/lib/scout_apm/instruments/rage_telemetry_handler.rb +++ b/lib/scout_apm/instruments/rage_telemetry_handler.rb @@ -94,7 +94,10 @@ def track_deferred_task(name:, task_class:) started_job = true begin result = yield - req.error! if result.error? + if result.error? + req.error! + capture_error(result.exception, {custom_controller: name}) + end result rescue => e req.error! @@ -112,7 +115,7 @@ def capture_error(exception, env) return unless ScoutApm::ErrorService.enabled? context = ScoutApm::Agent.instance.context - return if context.ignored_exceptions.ignore?(exception) + return if context.ignored_exceptions.ignored?(exception) context.error_buffer.capture(exception, env) rescue diff --git a/lib/scout_apm/server_integrations/iodine.rb b/lib/scout_apm/server_integrations/iodine.rb new file mode 100644 index 00000000..791393f7 --- /dev/null +++ b/lib/scout_apm/server_integrations/iodine.rb @@ -0,0 +1,30 @@ +module ScoutApm + module ServerIntegrations + class Iodine + attr_reader :logger + + def initialize(logger) + @logger = logger + end + + def name + :iodine + end + + def forking? + false + end + + def present? + defined?(::Iodine) && defined?(::Iodine::VERSION) + end + + def install + end + + def found? + true + end + end + end +end