appsignal 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: 7a51d521ee51d28f13d297b5dd7e00291a701dda4e3e0a639779b5c52c9e0551
4
- data.tar.gz: ec09dc858c399b9004cebb9afcdb1e041ba4f0efd068f173c6177e2bb5d10b1c
3
+ metadata.gz: 898cc61dd09079372dd341fcc77815283720d773a249c951383ac4be7ce031d8
4
+ data.tar.gz: 2d3d8fa4dfe45a3a42c89955bd243cd2c7871b3f2ac2978e7bef8333b6518b5e
5
5
  SHA512:
6
- metadata.gz: c8d87696bdeb0bf26784c001b67c9fb1201120ad325671b959cf807317dc3c2e948a62c8ce3e5618abc44ae2b5f30b66ee2414e72ee7eb1c55728a001717665e
7
- data.tar.gz: 6da6a43068a8edf31158269f956be507f2125ec61dd8374192f228582c730862a5a659d5b2f530e7c995264e571d2e27daf049367089608dcdbbb63b5db16ef9
6
+ metadata.gz: 492ff15286cd7d5ce8064e57ca959dc9b308fc452237463a35b0d64d978bc1b6bff834b078af6a6a19fa0881e88ccb21d24c9f25d7f7a4c721435423d3b81d75
7
+ data.tar.gz: 52e9a83f45fe345d895453e38833047c305f0b474918d8510148959a87a8e1ae797773213082152a03a19f8fd9cd7adfc37b5b0b6c33572f97fd9fdf3d67b5e1
data/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # AppSignal for Ruby gem Changelog
2
2
 
3
+ ## 4.0.2
4
+
5
+ _Published on 2024-08-23._
6
+
7
+ ### Fixed
8
+
9
+ - Do not log a warning for `nil` data being added as sample data, but silently ignore it because we don't support it. (patch [0a658e5e](https://github.com/appsignal/appsignal-ruby/commit/0a658e5e523f23f87b7d6e0b88bf6d6bea529f06))
10
+ - Fix Rails session data not being reported. (patch [1565c7f0](https://github.com/appsignal/appsignal-ruby/commit/1565c7f0a55e8c2e51b615863b12d13a2b246949))
11
+
12
+ ## 4.0.1
13
+
14
+ _Published on 2024-08-23._
15
+
16
+ ### Fixed
17
+
18
+ - Do not report `Sidekiq::JobRetry::Skip` errors. These errors would be reported by our Rails error subscriber. This is an internal Sidekiq error we do not need to report. (patch [9ea2d3e8](https://github.com/appsignal/appsignal-ruby/commit/9ea2d3e83657d115baf166257a50c7e3394318aa))
19
+ - Do not report `SystemExit` errors from our `at_exit` error reporter. (patch [e9c0cad3](https://github.com/appsignal/appsignal-ruby/commit/e9c0cad3d672e68a63ca9c33cfa30a3434c77d04))
20
+
3
21
  ## 4.0.0
4
22
 
5
23
  _Published on 2024-08-23._
@@ -24,6 +24,7 @@ module Appsignal
24
24
  class AtExitCallback
25
25
  def self.call
26
26
  error = $! # rubocop:disable Style/SpecialGlobalVars
27
+ return if ignored_error?(error)
27
28
  return if Appsignal::Transaction.last_errors.include?(error)
28
29
 
29
30
  Appsignal.report_error(error) do |transaction|
@@ -31,6 +32,15 @@ module Appsignal
31
32
  end
32
33
  Appsignal.stop("at_exit")
33
34
  end
35
+
36
+ IGNORED_ERRORS = [
37
+ # Normal exits from the application we do not need to report
38
+ SystemExit
39
+ ].freeze
40
+
41
+ def self.ignored_error?(error)
42
+ IGNORED_ERRORS.include?(error.class)
43
+ end
34
44
  end
35
45
  end
36
46
  end
@@ -80,6 +80,8 @@ module Appsignal
80
80
  class RailsErrorReporterSubscriber
81
81
  class << self
82
82
  def report(error, handled:, severity:, context: {}, source: nil) # rubocop:disable Lint/UnusedMethodArgument
83
+ return if ignored_error?(error)
84
+
83
85
  is_rails_runner = source == "application.runner.railties"
84
86
  namespace, action_name, tags, custom_data = context_for(context.dup)
85
87
 
@@ -100,6 +102,16 @@ module Appsignal
100
102
 
101
103
  private
102
104
 
105
+ IGNORED_ERRORS = [
106
+ # We don't need to alert Sidekiq job skip errors.
107
+ # This is an internal Sidekiq error.
108
+ "Sidekiq::JobRetry::Skip"
109
+ ].freeze
110
+
111
+ def ignored_error?(error)
112
+ IGNORED_ERRORS.include?(error.class.name)
113
+ end
114
+
103
115
  def context_for(context)
104
116
  tags = {}
105
117
 
@@ -142,9 +142,7 @@ module Appsignal
142
142
  transaction.set_metadata("method", request_method) if request_method
143
143
 
144
144
  transaction.add_params { params_for(request) }
145
- transaction.add_session_data do
146
- request.session if request.respond_to?(:session)
147
- end
145
+ transaction.add_session_data { session_data_for(request) }
148
146
  transaction.add_headers do
149
147
  request.env if request.respond_to?(:env)
150
148
  end
@@ -174,6 +172,18 @@ module Appsignal
174
172
  nil
175
173
  end
176
174
 
175
+ def session_data_for(request)
176
+ return unless request.respond_to?(:session)
177
+
178
+ request.session.to_h
179
+ rescue => error
180
+ Appsignal.internal_logger.error(
181
+ "Exception while fetching session data from '#{@request_class}': " \
182
+ "#{error.class} #{error}"
183
+ )
184
+ nil
185
+ end
186
+
177
187
  def request_for(env)
178
188
  @request_class.new(env)
179
189
  end
@@ -28,7 +28,7 @@ module Appsignal
28
28
  end
29
29
 
30
30
  def value
31
- value = nil
31
+ value = UNSET_VALUE
32
32
  @blocks.map! do |block_or_value|
33
33
  new_value =
34
34
  if block_or_value.respond_to?(:call)
@@ -63,6 +63,8 @@ module Appsignal
63
63
 
64
64
  private
65
65
 
66
+ UNSET_VALUE = nil
67
+
66
68
  # Method called by `dup` and `clone` to create a duplicate instance.
67
69
  # Make sure the `@blocks` variable is also properly duplicated.
68
70
  def initialize_copy(original)
@@ -81,11 +83,13 @@ module Appsignal
81
83
 
82
84
  def merge_values(value_original, value_new)
83
85
  unless value_new.instance_of?(value_original.class)
84
- Appsignal.internal_logger.warn(
85
- "The sample data '#{@key}' changed type from " \
86
- "'#{value_original.class}' to '#{value_new.class}'. " \
87
- "These types can not be merged. Using new '#{value_new.class}' type."
88
- )
86
+ unless value_original == UNSET_VALUE
87
+ Appsignal.internal_logger.warn(
88
+ "The sample data '#{@key}' changed type from " \
89
+ "'#{value_original.class}' to '#{value_new.class}'. " \
90
+ "These types can not be merged. Using new '#{value_new.class}' type."
91
+ )
92
+ end
89
93
  return value_new
90
94
  end
91
95
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Appsignal
4
- VERSION = "4.0.0"
4
+ VERSION = "4.0.2"
5
5
  end
@@ -59,7 +59,7 @@ describe Appsignal::Hooks::AtExit::AtExitCallback do
59
59
  end
60
60
  end
61
61
 
62
- it "doesn't report the error if is also the last error reported" do
62
+ it "doesn't report the error if it is also the last error reported" do
63
63
  with_error(ExampleException, "error message") do |error|
64
64
  Appsignal.report_error(error)
65
65
  expect(created_transactions.count).to eq(1)
@@ -69,4 +69,15 @@ describe Appsignal::Hooks::AtExit::AtExitCallback do
69
69
  end.to_not change { created_transactions.count }.from(1)
70
70
  end
71
71
  end
72
+
73
+ it "doesn't report the error if it is a SystemExit exception" do
74
+ with_error(SystemExit, "error message") do |error|
75
+ Appsignal.report_error(error)
76
+ expect(created_transactions.count).to eq(1)
77
+
78
+ expect do
79
+ call_callback
80
+ end.to_not change { created_transactions.count }.from(1)
81
+ end
82
+ end
72
83
  end
@@ -17,154 +17,10 @@ describe Appsignal::Hooks::ResqueHook do
17
17
 
18
18
  if DependencyHelper.resque_present?
19
19
  describe "#install" do
20
- def perform_rescue_job(klass, options = {})
21
- payload = { "class" => klass.to_s }.merge(options)
22
- job = ::Resque::Job.new(queue, payload)
23
- keep_transactions { job.perform }
24
- end
25
-
26
- let(:queue) { "default" }
27
- let(:namespace) { Appsignal::Transaction::BACKGROUND_JOB }
28
- let(:options) { {} }
29
- before do
30
- start_agent(:options => options)
31
-
32
- class ResqueTestJob
33
- def self.perform(*_args)
34
- end
35
- end
36
-
37
- class ResqueErrorTestJob
38
- def self.perform
39
- raise "resque job error"
40
- end
41
- end
42
-
43
- expect(Appsignal).to receive(:stop) # Resque calls stop after every job
44
- end
45
- around do |example|
46
- keep_transactions { example.run }
47
- end
48
- after do
49
- Object.send(:remove_const, :ResqueTestJob)
50
- Object.send(:remove_const, :ResqueErrorTestJob)
51
- end
52
-
53
- it "tracks a transaction on perform" do
54
- perform_rescue_job(ResqueTestJob)
55
-
56
- transaction = last_transaction
57
- expect(transaction).to have_id
58
- expect(transaction).to have_namespace(namespace)
59
- expect(transaction).to have_action("ResqueTestJob#perform")
60
- expect(transaction).to_not have_error
61
- expect(transaction).to_not include_metadata
62
- expect(transaction).to_not include_breadcrumbs
63
- expect(transaction).to include_tags("queue" => queue)
64
- expect(transaction).to include_event("name" => "perform.resque")
65
- end
66
-
67
- context "with error" do
68
- it "tracks the error on the transaction" do
69
- expect do
70
- perform_rescue_job(ResqueErrorTestJob)
71
- end.to raise_error(RuntimeError, "resque job error")
72
-
73
- transaction = last_transaction
74
- expect(transaction).to have_id
75
- expect(transaction).to have_namespace(namespace)
76
- expect(transaction).to have_action("ResqueErrorTestJob#perform")
77
- expect(transaction).to have_error("RuntimeError", "resque job error")
78
- expect(transaction).to_not include_metadata
79
- expect(transaction).to_not include_breadcrumbs
80
- expect(transaction).to include_tags("queue" => queue)
81
- expect(transaction).to include_event("name" => "perform.resque")
82
- end
83
- end
84
-
85
- context "with arguments" do
86
- let(:options) { { :filter_parameters => ["foo"] } }
87
-
88
- it "filters out configured arguments" do
89
- perform_rescue_job(
90
- ResqueTestJob,
91
- "args" => [
92
- "foo",
93
- {
94
- "foo" => "secret",
95
- "bar" => "Bar",
96
- "baz" => { "1" => "foo" }
97
- }
98
- ]
99
- )
100
-
101
- transaction = last_transaction
102
- expect(transaction).to have_id
103
- expect(transaction).to have_namespace(namespace)
104
- expect(transaction).to have_action("ResqueTestJob#perform")
105
- expect(transaction).to_not have_error
106
- expect(transaction).to_not include_metadata
107
- expect(transaction).to_not include_breadcrumbs
108
- expect(transaction).to include_tags("queue" => queue)
109
- expect(transaction).to include_event("name" => "perform.resque")
110
- expect(transaction).to include_params(
111
- [
112
- "foo",
113
- {
114
- "foo" => "[FILTERED]",
115
- "bar" => "Bar",
116
- "baz" => { "1" => "foo" }
117
- }
118
- ]
119
- )
120
- end
121
- end
122
-
123
- context "with active job" do
124
- before do
125
- module ActiveJobMock
126
- module QueueAdapters
127
- module ResqueAdapter
128
- module JobWrapper
129
- class << self
130
- def perform(job_data)
131
- # Basic ActiveJob stub for this test.
132
- # I haven't found a way to run Resque in a testing mode.
133
- Appsignal.set_action(job_data["job_class"])
134
- end
135
- end
136
- end
137
- end
138
- end
139
- end
140
-
141
- stub_const "ActiveJob", ActiveJobMock
142
- end
143
- after { Object.send(:remove_const, :ActiveJobMock) }
144
-
145
- it "does not set arguments but lets the ActiveJob integration handle it" do
146
- perform_rescue_job(
147
- ResqueTestJob,
148
- "class" => "ActiveJob::QueueAdapters::ResqueAdapter::JobWrapper",
149
- "args" => [
150
- {
151
- "job_class" => "ResqueTestJobByActiveJob#perform",
152
- "arguments" => ["an argument", "second argument"]
153
- }
154
- ]
155
- )
20
+ before { start_agent }
156
21
 
157
- transaction = last_transaction
158
- expect(transaction).to have_id
159
- expect(transaction).to have_namespace(namespace)
160
- expect(transaction).to have_action("ResqueTestJobByActiveJob#perform")
161
- expect(transaction).to_not have_error
162
- expect(transaction).to_not include_metadata
163
- expect(transaction).to_not include_breadcrumbs
164
- expect(transaction).to include_tags("queue" => queue)
165
- expect(transaction).to include_event("name" => "perform.resque")
166
- expect(transaction).to_not include_params
167
- end
22
+ it "adds the ResqueIntegration module to Resque::Job" do
23
+ expect(Resque::Job.included_modules).to include(Appsignal::Integrations::ResqueIntegration)
168
24
  end
169
25
  end
170
26
  end
@@ -229,6 +229,17 @@ if DependencyHelper.rails_present?
229
229
  expect(last_transaction).to have_error("ExampleStandardError", "error message")
230
230
  end
231
231
 
232
+ it "ignores Sidekiq::JobRetry::Skip errors" do
233
+ require "sidekiq"
234
+ require "sidekiq/job_retry"
235
+
236
+ with_rails_error_reporter do
237
+ Rails.error.handle { raise Sidekiq::JobRetry::Skip, "error message" }
238
+ end
239
+
240
+ expect(last_transaction).to_not have_error
241
+ end
242
+
232
243
  context "when no transaction is active" do
233
244
  it "reports the error on a new transaction" do
234
245
  with_rails_error_reporter do
@@ -0,0 +1,155 @@
1
+ require "appsignal/integrations/resque"
2
+
3
+ if DependencyHelper.resque_present?
4
+ describe Appsignal::Integrations::ResqueIntegration do
5
+ def perform_rescue_job(klass, options = {})
6
+ payload = { "class" => klass.to_s }.merge(options)
7
+ job = ::Resque::Job.new(queue, payload)
8
+ keep_transactions { job.perform }
9
+ end
10
+
11
+ let(:queue) { "default" }
12
+ let(:namespace) { Appsignal::Transaction::BACKGROUND_JOB }
13
+ let(:options) { {} }
14
+ before do
15
+ start_agent(:options => options)
16
+
17
+ class ResqueTestJob
18
+ def self.perform(*_args)
19
+ end
20
+ end
21
+
22
+ class ResqueErrorTestJob
23
+ def self.perform
24
+ raise "resque job error"
25
+ end
26
+ end
27
+
28
+ expect(Appsignal).to receive(:stop) # Resque calls stop after every job
29
+ end
30
+ around do |example|
31
+ keep_transactions { example.run }
32
+ end
33
+ after do
34
+ Object.send(:remove_const, :ResqueTestJob)
35
+ Object.send(:remove_const, :ResqueErrorTestJob)
36
+ end
37
+
38
+ it "tracks a transaction on perform" do
39
+ perform_rescue_job(ResqueTestJob)
40
+
41
+ transaction = last_transaction
42
+ expect(transaction).to have_id
43
+ expect(transaction).to have_namespace(namespace)
44
+ expect(transaction).to have_action("ResqueTestJob#perform")
45
+ expect(transaction).to_not have_error
46
+ expect(transaction).to_not include_metadata
47
+ expect(transaction).to_not include_breadcrumbs
48
+ expect(transaction).to include_tags("queue" => queue)
49
+ expect(transaction).to include_event("name" => "perform.resque")
50
+ end
51
+
52
+ context "with error" do
53
+ it "tracks the error on the transaction" do
54
+ expect do
55
+ perform_rescue_job(ResqueErrorTestJob)
56
+ end.to raise_error(RuntimeError, "resque job error")
57
+
58
+ transaction = last_transaction
59
+ expect(transaction).to have_id
60
+ expect(transaction).to have_namespace(namespace)
61
+ expect(transaction).to have_action("ResqueErrorTestJob#perform")
62
+ expect(transaction).to have_error("RuntimeError", "resque job error")
63
+ expect(transaction).to_not include_metadata
64
+ expect(transaction).to_not include_breadcrumbs
65
+ expect(transaction).to include_tags("queue" => queue)
66
+ expect(transaction).to include_event("name" => "perform.resque")
67
+ end
68
+ end
69
+
70
+ context "with arguments" do
71
+ let(:options) { { :filter_parameters => ["foo"] } }
72
+
73
+ it "filters out configured arguments" do
74
+ perform_rescue_job(
75
+ ResqueTestJob,
76
+ "args" => [
77
+ "foo",
78
+ {
79
+ "foo" => "secret",
80
+ "bar" => "Bar",
81
+ "baz" => { "1" => "foo" }
82
+ }
83
+ ]
84
+ )
85
+
86
+ transaction = last_transaction
87
+ expect(transaction).to have_id
88
+ expect(transaction).to have_namespace(namespace)
89
+ expect(transaction).to have_action("ResqueTestJob#perform")
90
+ expect(transaction).to_not have_error
91
+ expect(transaction).to_not include_metadata
92
+ expect(transaction).to_not include_breadcrumbs
93
+ expect(transaction).to include_tags("queue" => queue)
94
+ expect(transaction).to include_event("name" => "perform.resque")
95
+ expect(transaction).to include_params(
96
+ [
97
+ "foo",
98
+ {
99
+ "foo" => "[FILTERED]",
100
+ "bar" => "Bar",
101
+ "baz" => { "1" => "foo" }
102
+ }
103
+ ]
104
+ )
105
+ end
106
+ end
107
+
108
+ context "with active job" do
109
+ before do
110
+ module ActiveJobMock
111
+ module QueueAdapters
112
+ module ResqueAdapter
113
+ module JobWrapper
114
+ class << self
115
+ def perform(job_data)
116
+ # Basic ActiveJob stub for this test.
117
+ # I haven't found a way to run Resque in a testing mode.
118
+ Appsignal.set_action(job_data["job_class"])
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
125
+
126
+ stub_const "ActiveJob", ActiveJobMock
127
+ end
128
+ after { Object.send(:remove_const, :ActiveJobMock) }
129
+
130
+ it "does not set arguments but lets the ActiveJob integration handle it" do
131
+ perform_rescue_job(
132
+ ResqueTestJob,
133
+ "class" => "ActiveJob::QueueAdapters::ResqueAdapter::JobWrapper",
134
+ "args" => [
135
+ {
136
+ "job_class" => "ResqueTestJobByActiveJob#perform",
137
+ "arguments" => ["an argument", "second argument"]
138
+ }
139
+ ]
140
+ )
141
+
142
+ transaction = last_transaction
143
+ expect(transaction).to have_id
144
+ expect(transaction).to have_namespace(namespace)
145
+ expect(transaction).to have_action("ResqueTestJobByActiveJob#perform")
146
+ expect(transaction).to_not have_error
147
+ expect(transaction).to_not include_metadata
148
+ expect(transaction).to_not include_breadcrumbs
149
+ expect(transaction).to include_tags("queue" => queue)
150
+ expect(transaction).to include_event("name" => "perform.resque")
151
+ expect(transaction).to_not include_params
152
+ end
153
+ end
154
+ end
155
+ end
@@ -1,4 +1,14 @@
1
1
  describe Appsignal::Rack::AbstractMiddleware do
2
+ class HashLike < Hash
3
+ def initialize(value)
4
+ @value = value
5
+ end
6
+
7
+ def to_h
8
+ @value
9
+ end
10
+ end
11
+
2
12
  let(:app) { DummyApp.new }
3
13
  let(:request_path) { "/some/path" }
4
14
  let(:env) do
@@ -261,6 +271,13 @@ describe Appsignal::Rack::AbstractMiddleware do
261
271
 
262
272
  expect(last_transaction).to include_session_data("session" => "data", "user_id" => 123)
263
273
  end
274
+
275
+ it "sets session data if the session is a Hash-like type" do
276
+ env["rack.session"] = HashLike.new("hash-like" => "value", "user_id" => 123)
277
+ make_request
278
+
279
+ expect(last_transaction).to include_session_data("hash-like" => "value", "user_id" => 123)
280
+ end
264
281
  end
265
282
 
266
283
  context "with queue start header" do
@@ -3,9 +3,17 @@ describe Appsignal::SampleData do
3
3
 
4
4
  describe "#add" do
5
5
  it "sets the given value" do
6
- data.add(:key1 => "value 1")
6
+ logs =
7
+ capture_logs do
8
+ data.add(:key1 => "value 1")
9
+ end
7
10
 
8
11
  expect(data.value).to eq(:key1 => "value 1")
12
+
13
+ expect(logs).to_not contains_log(
14
+ :error,
15
+ "Sample data 'data_key': Unsupported data type 'NilClass' received: nil"
16
+ )
9
17
  end
10
18
 
11
19
  it "adds the given value with the block being leading" do
@@ -14,6 +22,23 @@ describe Appsignal::SampleData do
14
22
  expect(data.value).to eq(:key2 => "value 2")
15
23
  end
16
24
 
25
+ it "doesn't add nil to the data" do
26
+ logs =
27
+ capture_logs do
28
+ data.add([1])
29
+ data.add(nil)
30
+ data.add { nil }
31
+ data.add([2, 3])
32
+ end
33
+
34
+ expect(data.value).to eq([1, 2, 3])
35
+ expect(logs).to contains_log(
36
+ :error,
37
+ "Sample data 'data_key': Unsupported data type 'NilClass' received: nil"
38
+ )
39
+ expect(logs).to_not contains_log(:warn, "The sample data 'data_key' changed type")
40
+ end
41
+
17
42
  it "merges multiple values" do
18
43
  data.add(:key1 => "value 1")
19
44
  data.add(:key2 => "value 2")
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: appsignal
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
  - Robert Beekman
@@ -372,6 +372,7 @@ files:
372
372
  - spec/lib/appsignal/integrations/object_spec.rb
373
373
  - spec/lib/appsignal/integrations/que_spec.rb
374
374
  - spec/lib/appsignal/integrations/railtie_spec.rb
375
+ - spec/lib/appsignal/integrations/resque_spec.rb
375
376
  - spec/lib/appsignal/integrations/shoryuken_spec.rb
376
377
  - spec/lib/appsignal/integrations/sidekiq_spec.rb
377
378
  - spec/lib/appsignal/integrations/webmachine_spec.rb