Skip to content

Commit

Permalink
Merge pull request #178 from nikz/support-rails-error-handle
Browse files Browse the repository at this point in the history
Adds support for Rails' new error handling API
  • Loading branch information
nikz authored May 4, 2024
2 parents ec3d176 + 630ba91 commit c6a5037
Show file tree
Hide file tree
Showing 13 changed files with 140 additions and 23 deletions.
7 changes: 6 additions & 1 deletion lib/raygun.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
require "raygun/middleware/breadcrumbs_store_initializer"
require "raygun/middleware/javascript_exception_tracking"
require "raygun/demo_exception"
require "raygun/error_subscriber"
require "raygun/error"
require "raygun/affected_user"
require "raygun/services/apply_whitelist_filter_to_payload"
Expand Down Expand Up @@ -64,11 +65,13 @@ def track_exception(exception_instance, env = {}, user = nil, retry_count = 1)

exception_instance.set_backtrace(caller) if exception_instance.is_a?(Exception) && exception_instance.backtrace.nil?

if configuration.send_in_background
result = if configuration.send_in_background
track_exception_async(exception_instance, env, user, retry_count)
else
track_exception_sync(exception_instance, env, user, retry_count)
end

result
end

def track_exceptions
Expand Down Expand Up @@ -136,6 +139,8 @@ def track_exception_async(exception_instance, env, user, retry_count)
@@active_futures.delete(future)
end, :call)
@@active_futures << future

future
end

def track_exception_sync(exception_instance, env, user, retry_count)
Expand Down
1 change: 0 additions & 1 deletion lib/raygun/breadcrumbs/store.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ def self.should_record?(crumb)
levels = Raygun::Breadcrumbs::BREADCRUMB_LEVELS

active_level = levels.index(Raygun.configuration.breadcrumb_level)
puts crumb.inspect if crumb.is_a?(Array)
crumb_level = levels.index(crumb.level) || -1

discard = crumb_level < active_level
Expand Down
7 changes: 2 additions & 5 deletions lib/raygun/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -221,12 +221,9 @@ def build_payload_hash(exception_instance, env = {}, user = nil)

# If we have breadcrumbs passed to us as context from another thread, then include them
# Otherwise, use the default store (which is thread-local)
store = if env.key?(:rg_breadcrumb_store)
::Raygun::Breadcrumbs::Store.initialize(with: env.delete(:rg_breadcrumb_store))
else
::Raygun::Breadcrumbs::Store
end
::Raygun::Breadcrumbs::Store.initialize(with: env.delete(:rg_breadcrumb_store)) if env.key?(:rg_breadcrumb_store)

store = ::Raygun::Breadcrumbs::Store
error_details[:breadcrumbs] = store.take_until_size(MAX_BREADCRUMBS_SIZE).map(&:build_payload) if store.any?

Raygun.log('set details and breadcrumbs')
Expand Down
3 changes: 3 additions & 0 deletions lib/raygun/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ def self.proc_config_option(name)
# How long to wait for the POST request to the API server before timing out
config_option :error_report_send_timeout

# Should we register an error handler with [Rails' built in API](https://edgeguides.rubyonrails.org/error_reporting.html)
config_option :register_rails_error_handler

# Exception classes to ignore by default
IGNORE_DEFAULT = ['ActiveRecord::RecordNotFound',
'ActionController::RoutingError',
Expand Down
21 changes: 21 additions & 0 deletions lib/raygun/error_subscriber.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Subscribes to errors using Rails' error reporting API
# https://edgeguides.rubyonrails.org/error_reporting.html
class Raygun::ErrorSubscriber
def report(error, handled:, severity:, context:, source: nil)
tags = context.delete(:tags) if context.is_a?(Hash)

data = {
custom_data: {
"rails.error": {
handled: handled,
severity: severity,
context: context,
source: source
},
},
tags: ["rails_error_reporter", *tags].compact
}

Raygun.track_exception(error, data)
end
end
8 changes: 8 additions & 0 deletions lib/raygun/railtie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,17 @@ class Raygun::Railtie < Rails::Railtie
config.to_prepare do
Raygun.default_configuration.logger = Rails.logger
Raygun.default_configuration.enable_reporting = Rails.env.production?

Raygun::Railtie.setup_error_subscriber
end

rake_tasks do
load "tasks/raygun.tasks"
end

def self.setup_error_subscriber
if ::Rails.version.to_f >= 7.0 && Raygun.configuration.register_rails_error_handler
Rails.error.subscribe(Raygun::ErrorSubscriber.new)
end
end
end
6 changes: 6 additions & 0 deletions test/rails_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
ENV['RAILS_ENV'] ||= 'test'
require "rails"

major_minor_patch = Rails::VERSION::STRING.split(".").first(3).join(".")

require_relative "../spec/rails_applications/#{major_minor_patch}/config/environment"
20 changes: 12 additions & 8 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
ENV['RACK_ENV'] = 'test'
require_relative "../lib/raygun.rb"
require "minitest/autorun"
require "minitest/pride"
require "timecop"
require "mocha/minitest"
require 'stringio'
require 'webmock/minitest'
require "stringio"
require "webmock/minitest"

require_relative "./rails_helper"
require_relative "../lib/raygun.rb"

# Ensure we start with a known state
Raygun.reset_configuration

class FakeLogger
def initialize
Expand Down Expand Up @@ -40,6 +45,8 @@ def setup
end

def teardown
Raygun.wait_for_futures
Raygun.reset_configuration
end

end
Expand All @@ -51,17 +58,14 @@ def setup
end

def teardown
reset_configuration
Raygun.wait_for_futures
Raygun.reset_configuration
end

def fake_successful_entry
stub_request(:post, 'https://api.raygun.com/entries').to_return(status: 202)
end

def reset_configuration
Raygun.configuration = Raygun::Configuration.new
end

def setup_logging
logger = FakeLogger.new
Raygun.configuration.debug = true
Expand Down
4 changes: 0 additions & 4 deletions test/unit/configuration_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ def setup
end
end

def teardown
Raygun.reset_configuration
end

def test_setting_api_key_and_version
assert_equal 9.9, Raygun.configuration.version
assert_equal "a test api key", Raygun.configuration.api_key
Expand Down
43 changes: 43 additions & 0 deletions test/unit/error_subscriber_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
require_relative "../test_helper.rb"

class ErrorSubscriberTest < Raygun::UnitTest

def setup
super
Raygun.configuration.send_in_background = false
end


def test_tracking_exception_via_subscriber
body_matcher = lambda do |body|
json = JSON.parse(body)
details = json["details"]

details["userCustomData"] &&
details["userCustomData"]["rails.error"] &&
details["userCustomData"]["rails.error"]["handled"] == true &&
details["tags"] == ["rails_error_reporter", "test_tag", "test"]
end

request_stub = stub_request(:post, 'https://api.raygun.com/entries')
.with(
body: body_matcher
)
.to_return(status: 202).times(1)

result = Raygun::ErrorSubscriber.new.report(
StandardError.new("test error"),
handled: true,
severity: "warning",
context: {
tags: ["test_tag"]
},
source: "application"
)

assert result && result.success?, "Expected success, got #{result.class}, #{result.inspect}"

assert_requested request_stub
end

end
29 changes: 29 additions & 0 deletions test/unit/raygun_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,35 @@ def test_failsafe_reported_on_timeout

end

class ErrorSubscriberTest < Raygun::UnitTest
def setup
Raygun.setup do |c|
c.api_key = "test"
c.silence_reporting = false
c.debug = true
c.register_rails_error_handler = true
end

Raygun::Railtie.setup_error_subscriber
end

def test_registers_with_rails
if ::Rails.version.to_f >= 7.0
assert Rails.error.instance_variable_get("@subscribers").any? { |s| s.is_a?(Raygun::ErrorSubscriber) }
end
end

def test_reports_exceptions
if ::Rails.version.to_f >= 7.0
stub_request(:post, "https://api.raygun.com/entries").to_return(status: 202)

Rails.error.handle do
raise StandardError.new("test rails handling")
end
end
end
end

def test_reset_configuration
Raygun.setup do |c|
c.api_url = "http://test.api"
Expand Down
7 changes: 5 additions & 2 deletions test/unit/resque_failure_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,21 @@ class ResqueFailureTest < Raygun::UnitTest

def setup
super
Raygun.configuration.send_in_background = false

stub_request(:post, 'https://api.raygun.com/entries').to_return(status: 202)
fake_successful_entry
end

def test_failure_backend_appears_to_work
assert Resque::Failure::Raygun.new(
result = Resque::Failure::Raygun.new(
StandardError.new("Worker Problem"),
"TestWorker PID 123",
"super_important_jobs",
class: "SendCookies", args: [ "nik" ]
).save.success?
).save

assert result && result.success?, "Expected success, got #{result.inspect}"
end

end
7 changes: 5 additions & 2 deletions test/unit/sidekiq_failure_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,20 @@ class SidekiqFailureTest < Raygun::UnitTest

def setup
super
Raygun.configuration.send_in_background = false

stub_request(:post, 'https://api.raygun.com/entries').to_return(status: 202)
fake_successful_entry
end

def test_failure_backend_appears_to_work
assert Raygun::SidekiqReporter.call(
response = Raygun::SidekiqReporter.call(
StandardError.new("Oh no! Your Sidekiq has failed!"),
{ sidekick_name: "robin" },
{} # config
).success?
)

assert response && response.success?, "Expected success, got #{response.class}: #{response.inspect}"
end

def test_we_are_in_sidekiqs_list_of_error_handlers
Expand Down

0 comments on commit c6a5037

Please sign in to comment.