raygun4ruby 4.0.0 → 4.0.2

Sign up to get free protection for your applications and to get access to all the features.
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