raygun4ruby 4.0.0 → 4.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9b7c357c986e52b60665b3ee8af64d7a2c421747825e16b928fc91b76ae58f2a
4
- data.tar.gz: 671db6dc795dfe165f531b16443b483865ca659c83b25df617c09ce706f4ca48
3
+ metadata.gz: f1f1acdc84e42b8f39dd817b3e3b74e39dd18b07c675f46afa1cb57bf42bb435
4
+ data.tar.gz: 6ca0a6036d1d56d23e66105c5535ccc8687973b55b32e461d7643543693e475a
5
5
  SHA512:
6
- metadata.gz: 7287d1190a0fa828a3cd9acc0ac4935249e080f1749730ad2dc2c6b9ef868f42f0e0293ba6c9c1eb5a9828d8b52ba6cfd832783512938614cf1e6921d7365a73
7
- data.tar.gz: 9a35eeba3f42c226c98f077a27870096853e8a1e06073736e0b02bc1114725f1feeed66f2434adc35dc41f5c2b5c3181f7ab04da29886a18b638899e25460dee
6
+ metadata.gz: 318c63ba10cb3645f7c75b81f1f4da06de5f361650f3ae7b3a32cc229fa9670589b742952e3cbb1929cfb02d581bdac3fd45c120b957645bea5c9339096021ff
7
+ data.tar.gz: 396e058ef6bb5fe5a802a6430c548d3133e2b50000af67bdca300cb280c211d2f5464fb12afe4c3c694e658c5887c1983f1d7af08e4cbc2b535c88be75917558
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 4.0.1 (29/07/2024):
2
+
3
+ - Adds the ability to unwrap `Sidekiq::JobRetry::Handled` exceptions (or ignore them entirely) ([#185](https://github.com/MindscapeHQ/raygun4ruby/pull/185))
4
+
1
5
  ## 4.0.0 (20/05/2024):
2
6
 
3
7
  - BREAKING CHANGE: Remove support for end-of-life Ruby verisons and Rails versions prior to 6.0.0
data/README.md CHANGED
@@ -402,6 +402,10 @@ class FailingWorker
402
402
  end
403
403
  ```
404
404
 
405
+ ##### Sidekiq Retries
406
+
407
+ By default, Raygun4Ruby will unwrap `Sidekiq::JobRetry::Handled` exceptions and report the original error via `Exception#cause`. If you would prefer not to hear about retries, you can set `config.track_retried_sidekiq_jobs` to `false` in your Raygun configuration.
408
+
405
409
  ### Other Configuration options
406
410
 
407
411
  For a complete list of configuration options see the [configuration.rb](https://github.com/MindscapeHQ/raygun4ruby/blob/master/lib/raygun/configuration.rb) file
@@ -86,9 +86,18 @@ module Raygun
86
86
  # How long to wait for the POST request to the API server before timing out
87
87
  config_option :error_report_send_timeout
88
88
 
89
+ # How many times to try sending to Raygun before giving up
90
+ config_option :error_report_max_attempts
91
+
92
+ # Whether or not we should raise an exception if the error reporting fails
93
+ config_option :raise_on_failed_error_report
94
+
89
95
  # Should we register an error handler with [Rails' built in API](https://edgeguides.rubyonrails.org/error_reporting.html)
90
96
  config_option :register_rails_error_handler
91
97
 
98
+ # Should we track jobs that are retried in Sidekiq (ones that raise Sidekiq::JobRetry::Handled). Set to "false" to ignore.
99
+ config_option :track_retried_sidekiq_jobs
100
+
92
101
  # Exception classes to ignore by default
93
102
  IGNORE_DEFAULT = ['ActiveRecord::RecordNotFound',
94
103
  'ActionController::RoutingError',
@@ -143,7 +152,10 @@ module Raygun
143
152
  breadcrumb_level: :info,
144
153
  record_raw_data: false,
145
154
  send_in_background: false,
146
- error_report_send_timeout: 10
155
+ error_report_send_timeout: 10,
156
+ error_report_max_attempts: 1,
157
+ raise_on_failed_error_report: false,
158
+ track_retried_sidekiq_jobs: true
147
159
  )
148
160
  end
149
161
 
@@ -16,6 +16,10 @@ class Raygun::ErrorSubscriber
16
16
  tags: ["rails_error_reporter", *tags].compact
17
17
  }
18
18
 
19
- Raygun.track_exception(error, data)
19
+ if source == "job.sidekiq" && defined?(Sidekiq)
20
+ Raygun::SidekiqReporter.call(error, data)
21
+ else
22
+ Raygun.track_exception(error, data)
23
+ end
20
24
  end
21
25
  end
@@ -6,7 +6,7 @@
6
6
  module Raygun
7
7
 
8
8
  class SidekiqReporter
9
- def self.call(exception, context_hash, config)
9
+ def self.call(exception, context_hash = {}, config = nil)
10
10
  user = affected_user(context_hash)
11
11
  data = {
12
12
  custom_data: {
@@ -14,6 +14,16 @@ module Raygun
14
14
  },
15
15
  tags: ['sidekiq']
16
16
  }
17
+
18
+ if exception.is_a?(Sidekiq::JobRetry::Handled) && exception.cause
19
+ if Raygun.configuration.track_retried_sidekiq_jobs
20
+ data.merge!(sidekiq_retried: true)
21
+ exception = exception.cause
22
+ else
23
+ return false
24
+ end
25
+ end
26
+
17
27
  if exception.instance_variable_defined?(:@__raygun_correlation_id) && correlation_id = exception.instance_variable_get(:@__raygun_correlation_id)
18
28
  data.merge!(correlation_id: correlation_id)
19
29
  end
@@ -1,3 +1,3 @@
1
1
  module Raygun
2
- VERSION = "4.0.0"
2
+ VERSION = "4.0.2"
3
3
  end
data/lib/raygun.rb CHANGED
@@ -60,15 +60,15 @@ module Raygun
60
60
  !!configuration.api_key
61
61
  end
62
62
 
63
- def track_exception(exception_instance, env = {}, user = nil, retry_count = 1)
63
+ def track_exception(exception_instance, env = {}, user = nil, retries_remaining = configuration.error_report_max_attempts - 1)
64
64
  log('tracking exception')
65
65
 
66
66
  exception_instance.set_backtrace(caller) if exception_instance.is_a?(Exception) && exception_instance.backtrace.nil?
67
67
 
68
68
  result = if configuration.send_in_background
69
- track_exception_async(exception_instance, env, user, retry_count)
69
+ track_exception_async(exception_instance, env, user, retries_remaining)
70
70
  else
71
- track_exception_sync(exception_instance, env, user, retry_count)
71
+ track_exception_sync(exception_instance, env, user, retries_remaining)
72
72
  end
73
73
 
74
74
  result
@@ -128,10 +128,10 @@ module Raygun
128
128
 
129
129
  private
130
130
 
131
- def track_exception_async(exception_instance, env, user, retry_count)
131
+ def track_exception_async(exception_instance, env, user, retries_remaining)
132
132
  env[:rg_breadcrumb_store] = Raygun::Breadcrumbs::Store.take_until_size(Client::MAX_BREADCRUMBS_SIZE) if Raygun::Breadcrumbs::Store.any?
133
133
 
134
- future = Concurrent::Future.execute { track_exception_sync(exception_instance, env, user, retry_count) }
134
+ future = Concurrent::Future.execute { track_exception_sync(exception_instance, env, user, retries_remaining) }
135
135
  future.add_observer(lambda do |_, value, reason|
136
136
  if value == nil || !value.responds_to?(:response) || value.response.code != "202"
137
137
  log("unexpected response from Raygun, could indicate error: #{value.inspect}")
@@ -143,7 +143,7 @@ module Raygun
143
143
  future
144
144
  end
145
145
 
146
- def track_exception_sync(exception_instance, env, user, retry_count)
146
+ def track_exception_sync(exception_instance, env, user, retries_remaining)
147
147
  if should_report?(exception_instance)
148
148
  log('attempting to send exception')
149
149
  resp = Client.new.track_exception(exception_instance, env, user)
@@ -158,18 +158,25 @@ module Raygun
158
158
  failsafe_log("Problem reporting exception to Raygun: #{e.class}: #{e.message}\n\n#{e.backtrace.join("\n")}")
159
159
  end
160
160
 
161
- if retry_count > 0
161
+ if retries_remaining > 0
162
162
  new_exception = e.exception("raygun4ruby encountered an exception processing your exception")
163
163
  new_exception.set_backtrace(e.backtrace)
164
164
 
165
165
  env[:custom_data] ||= {}
166
- env[:custom_data].merge!(original_stacktrace: exception_instance.backtrace)
166
+ env[:custom_data].merge!(original_stacktrace: exception_instance.backtrace, retries_remaining: retries_remaining)
167
167
 
168
168
  ::Raygun::Breadcrumbs::Store.clear
169
169
 
170
- track_exception(new_exception, env, user, retry_count - 1)
170
+ track_exception(new_exception, env, user, retries_remaining - 1)
171
171
  else
172
- raise e
172
+ if configuration.raise_on_failed_error_report
173
+ raise e
174
+ else
175
+ retries = configuration.error_report_max_attempts - retries_remaining
176
+ if configuration.failsafe_logger
177
+ failsafe_log("Gave up reporting exception to Raygun after #{retries} #{retries == 1 ? "retry" : "retries"}: #{e.class}: #{e.message}\n\n#{e.backtrace.join("\n")}")
178
+ end
179
+ end
173
180
  end
174
181
  end
175
182
 
data/raygun4ruby.gemspec CHANGED
@@ -24,6 +24,7 @@ Gem::Specification.new do |spec|
24
24
  spec.add_runtime_dependency "json"
25
25
  spec.add_runtime_dependency "rack"
26
26
  spec.add_runtime_dependency "concurrent-ruby"
27
+ spec.add_runtime_dependency "ostruct"
27
28
 
28
29
  spec.add_development_dependency "bundler", ">= 2.3"
29
30
  spec.add_development_dependency "rake", ">= 12.3.3"
@@ -103,4 +103,43 @@ class RaygunTest < Raygun::UnitTest
103
103
  assert_equal Raygun.default_configuration.api_url, Raygun.configuration.api_url
104
104
  refute_equal original_api_url, Raygun.configuration.api_url
105
105
  end
106
+
107
+ def test_retries
108
+ failsafe_logger = FakeLogger.new
109
+ Raygun.setup do |c|
110
+ c.error_report_max_attempts = 3
111
+ c.failsafe_logger = failsafe_logger
112
+ end
113
+
114
+ WebMock.reset!
115
+ report_request = stub_request(:post, "https://api.raygun.com/entries").to_timeout
116
+
117
+ error = StandardError.new
118
+ Raygun.track_exception(error)
119
+
120
+ assert_requested report_request, times: 3
121
+
122
+ assert_match(/Gave up reporting exception to Raygun after 3 retries/, failsafe_logger.get)
123
+ ensure
124
+ Raygun.reset_configuration
125
+ end
126
+
127
+ def test_raising_on_retry_failure
128
+ Raygun.setup do |c|
129
+ c.error_report_max_attempts = 1
130
+ c.raise_on_failed_error_report = true
131
+ end
132
+
133
+ report_request = stub_request(:post, "https://api.raygun.com/entries").to_timeout
134
+
135
+ error = StandardError.new
136
+
137
+ assert_raises(StandardError) do
138
+ Raygun.track_exception(error)
139
+ assert_requested report_request
140
+ end
141
+
142
+ ensure
143
+ Raygun.reset_configuration
144
+ end
106
145
  end
@@ -1,6 +1,7 @@
1
1
  require_relative "../test_helper.rb"
2
2
 
3
3
  require "sidekiq"
4
+
4
5
  # Convince Sidekiq it's on a server :)
5
6
  module Sidekiq
6
7
  class << self
@@ -15,6 +16,8 @@ require "raygun/sidekiq"
15
16
  class SidekiqFailureTest < Raygun::UnitTest
16
17
 
17
18
  def setup
19
+ require "sidekiq/job_retry"
20
+
18
21
  super
19
22
  Raygun.configuration.send_in_background = false
20
23
 
@@ -32,6 +35,61 @@ class SidekiqFailureTest < Raygun::UnitTest
32
35
  assert response && response.success?, "Expected success, got #{response.class}: #{response.inspect}"
33
36
  end
34
37
 
38
+ def test_failure_backend_unwraps_retries
39
+ WebMock.reset!
40
+
41
+ unwrapped_stub = stub_request(:post, 'https://api.raygun.com/entries').
42
+ with(body: /StandardError/).
43
+ to_return(status: 202)
44
+
45
+ begin
46
+ raise StandardError.new("Some job in Sidekiq failed, oh dear!")
47
+ rescue
48
+ raise Sidekiq::JobRetry::Handled
49
+ end
50
+
51
+ rescue Sidekiq::JobRetry::Handled => e
52
+
53
+ response = Raygun::SidekiqReporter.call(
54
+ e,
55
+ { sidekick_name: "robin" },
56
+ {} # config
57
+ )
58
+
59
+ assert_requested unwrapped_stub
60
+ assert response && response.success?, "Expected success, got #{response.class}: #{response.inspect}"
61
+ end
62
+
63
+ def test_failured_backend_ignores_retries_if_configured
64
+ Raygun.configuration.track_retried_sidekiq_jobs = false
65
+
66
+ begin
67
+ raise StandardError.new("Some job in Sidekiq failed, oh dear!")
68
+ rescue
69
+ raise Sidekiq::JobRetry::Handled
70
+ end
71
+
72
+ rescue Sidekiq::JobRetry::Handled => e
73
+
74
+ refute Raygun::SidekiqReporter.call(e,
75
+ { sidekick_name: "robin" },
76
+ {} # config
77
+ )
78
+ ensure
79
+ Raygun.configuration.track_retried_sidekiq_jobs = true
80
+ end
81
+
82
+ # See https://github.com/MindscapeHQ/raygun4ruby/issues/183
83
+ # (This is how Sidekiq pre 7.1.5 calls error handlers: https://github.com/sidekiq/sidekiq/blob/1ba89bbb22d2fd574b11702d8b6ed63ae59e2256/lib/sidekiq/config.rb#L269)
84
+ def test_failure_backend_appears_to_work_without_config_argument
85
+ response = Raygun::SidekiqReporter.call(
86
+ StandardError.new("Oh no! Your Sidekiq has failed!"),
87
+ { sidekick_name: "robin" }
88
+ )
89
+
90
+ assert response && response.success?, "Expected success, got #{response.class}: #{response.inspect}"
91
+ end
92
+
35
93
  def test_we_are_in_sidekiqs_list_of_error_handlers
36
94
  # Sidekiq 7.x stores error handlers inside a configuration object, while 6.x and below stores them directly against the Sidekiq module
37
95
  error_handlers = Sidekiq.respond_to?(:error_handlers) ? Sidekiq.error_handlers : Sidekiq.default_configuration.error_handlers
@@ -39,4 +97,26 @@ class SidekiqFailureTest < Raygun::UnitTest
39
97
  assert error_handlers.include?(Raygun::SidekiqReporter)
40
98
  end
41
99
 
100
+ def test_rails_error_reporter_uses_sidekiq_reporter
101
+ WebMock.reset!
102
+
103
+ tagged_request = stub_request(:post, 'https://api.raygun.com/entries').
104
+ with(body: /"sidekiq"/). # should have a sidekiq tag!
105
+ to_return(status: 202)
106
+
107
+ error = StandardError.new("Oh no! Your Sidekiq has failed!")
108
+
109
+ response = Raygun::ErrorSubscriber.new.report(
110
+ error,
111
+ handled: true,
112
+ severity: "error",
113
+ context: { sidekick_name: "robin" },
114
+ source: "job.sidekiq"
115
+ )
116
+
117
+ assert response && response.success?, "Expected success, got #{response.class}: #{response.inspect}"
118
+
119
+ assert_requested tagged_request
120
+ end
121
+
42
122
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: raygun4ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.0
4
+ version: 4.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mindscape
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2024-05-19 00:00:00.000000000 Z
12
+ date: 2024-11-14 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: httparty
@@ -67,6 +67,20 @@ dependencies:
67
67
  - - ">="
68
68
  - !ruby/object:Gem::Version
69
69
  version: '0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: ostruct
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
70
84
  - !ruby/object:Gem::Dependency
71
85
  name: bundler
72
86
  requirement: !ruby/object:Gem::Requirement