appsignal 3.4.4 → 3.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +1 -1
  4. data/.semaphore/semaphore.yml +683 -52
  5. data/CHANGELOG.md +353 -4
  6. data/README.md +3 -0
  7. data/Rakefile +4 -2
  8. data/appsignal.gemspec +1 -1
  9. data/build_matrix.yml +27 -13
  10. data/ext/Rakefile +8 -1
  11. data/ext/agent.rb +27 -27
  12. data/ext/appsignal_extension.c +0 -24
  13. data/ext/base.rb +5 -2
  14. data/gemfiles/dry-monitor.gemfile +5 -0
  15. data/gemfiles/rails-7.1.gemfile +7 -0
  16. data/gemfiles/redis-4.gemfile +5 -0
  17. data/gemfiles/redis-5.gemfile +6 -0
  18. data/lib/appsignal/auth_check.rb +1 -1
  19. data/lib/appsignal/cli/diagnose/paths.rb +33 -10
  20. data/lib/appsignal/cli/diagnose.rb +15 -1
  21. data/lib/appsignal/config.rb +72 -7
  22. data/lib/appsignal/demo.rb +1 -1
  23. data/lib/appsignal/environment.rb +24 -13
  24. data/lib/appsignal/event_formatter/action_view/render_formatter.rb +1 -1
  25. data/lib/appsignal/event_formatter/rom/sql_formatter.rb +18 -0
  26. data/lib/appsignal/event_formatter/sequel/sql_formatter.rb +1 -1
  27. data/lib/appsignal/event_formatter.rb +2 -2
  28. data/lib/appsignal/extension/jruby.rb +4 -17
  29. data/lib/appsignal/extension.rb +1 -1
  30. data/lib/appsignal/heartbeat.rb +71 -0
  31. data/lib/appsignal/helpers/instrumentation.rb +10 -10
  32. data/lib/appsignal/helpers/metrics.rb +15 -13
  33. data/lib/appsignal/hooks/active_job.rb +9 -1
  34. data/lib/appsignal/hooks/active_support_notifications.rb +18 -9
  35. data/lib/appsignal/hooks/dry_monitor.rb +20 -0
  36. data/lib/appsignal/hooks/redis.rb +1 -0
  37. data/lib/appsignal/hooks/redis_client.rb +28 -0
  38. data/lib/appsignal/hooks.rb +4 -2
  39. data/lib/appsignal/integrations/active_support_notifications.rb +26 -0
  40. data/lib/appsignal/integrations/dry_monitor.rb +22 -0
  41. data/lib/appsignal/integrations/hanami.rb +1 -1
  42. data/lib/appsignal/integrations/padrino.rb +1 -1
  43. data/lib/appsignal/integrations/railtie.rb +28 -6
  44. data/lib/appsignal/integrations/redis_client.rb +20 -0
  45. data/lib/appsignal/integrations/sidekiq.rb +2 -2
  46. data/lib/appsignal/integrations/sinatra.rb +1 -1
  47. data/lib/appsignal/logger.rb +7 -5
  48. data/lib/appsignal/minutely.rb +4 -4
  49. data/lib/appsignal/probes/gvl.rb +1 -1
  50. data/lib/appsignal/probes/helpers.rb +1 -1
  51. data/lib/appsignal/probes/mri.rb +1 -1
  52. data/lib/appsignal/probes/sidekiq.rb +10 -8
  53. data/lib/appsignal/rack/generic_instrumentation.rb +1 -1
  54. data/lib/appsignal/rack/rails_instrumentation.rb +2 -2
  55. data/lib/appsignal/rack/sinatra_instrumentation.rb +5 -4
  56. data/lib/appsignal/rack/streaming_listener.rb +1 -1
  57. data/lib/appsignal/span.rb +2 -2
  58. data/lib/appsignal/transaction.rb +69 -14
  59. data/lib/appsignal/utils/deprecation_message.rb +2 -2
  60. data/lib/appsignal/utils/hash_sanitizer.rb +21 -9
  61. data/lib/appsignal/version.rb +1 -1
  62. data/lib/appsignal.rb +38 -31
  63. data/lib/puma/plugin/appsignal.rb +1 -1
  64. data/resources/cacert.pem +321 -159
  65. data/spec/lib/appsignal/capistrano2_spec.rb +2 -2
  66. data/spec/lib/appsignal/capistrano3_spec.rb +2 -2
  67. data/spec/lib/appsignal/cli/diagnose/utils_spec.rb +11 -0
  68. data/spec/lib/appsignal/cli/diagnose_spec.rb +70 -13
  69. data/spec/lib/appsignal/config_spec.rb +75 -18
  70. data/spec/lib/appsignal/environment_spec.rb +3 -3
  71. data/spec/lib/appsignal/event_formatter/mongo_ruby_driver/query_formatter_spec.rb +1 -1
  72. data/spec/lib/appsignal/event_formatter/rom/sql_formatter_spec.rb +22 -0
  73. data/spec/lib/appsignal/heartbeat_spec.rb +89 -0
  74. data/spec/lib/appsignal/hooks/active_support_notifications_spec.rb +6 -0
  75. data/spec/lib/appsignal/hooks/activejob_spec.rb +26 -1
  76. data/spec/lib/appsignal/hooks/dry_monitor_spec.rb +104 -0
  77. data/spec/lib/appsignal/hooks/redis_client_spec.rb +238 -0
  78. data/spec/lib/appsignal/hooks/redis_spec.rb +98 -76
  79. data/spec/lib/appsignal/hooks/resque_spec.rb +1 -1
  80. data/spec/lib/appsignal/hooks_spec.rb +5 -5
  81. data/spec/lib/appsignal/integrations/railtie_spec.rb +128 -59
  82. data/spec/lib/appsignal/integrations/sidekiq_spec.rb +20 -15
  83. data/spec/lib/appsignal/integrations/sinatra_spec.rb +2 -2
  84. data/spec/lib/appsignal/minutely_spec.rb +2 -2
  85. data/spec/lib/appsignal/probes/sidekiq_spec.rb +29 -6
  86. data/spec/lib/appsignal/rack/rails_instrumentation_spec.rb +1 -1
  87. data/spec/lib/appsignal/rack/sinatra_instrumentation_spec.rb +163 -71
  88. data/spec/lib/appsignal/rack/streaming_listener_spec.rb +1 -0
  89. data/spec/lib/appsignal/transaction_spec.rb +139 -10
  90. data/spec/lib/appsignal/utils/hash_sanitizer_spec.rb +42 -4
  91. data/spec/lib/appsignal_spec.rb +63 -61
  92. data/spec/lib/puma/appsignal_spec.rb +1 -1
  93. data/spec/spec_helper.rb +7 -7
  94. data/spec/support/fixtures/projects/valid/config/appsignal.yml +3 -3
  95. data/spec/support/helpers/config_helpers.rb +6 -2
  96. data/spec/support/helpers/dependency_helper.rb +13 -1
  97. data/spec/support/helpers/log_helpers.rb +2 -2
  98. data/spec/support/helpers/rails_helper.rb +28 -0
  99. data/spec/support/matchers/have_colorized_text.rb +1 -1
  100. metadata +19 -5
  101. data/ext/._appsignal-agent +0 -0
@@ -1,39 +1,49 @@
1
1
  if DependencyHelper.sinatra_present?
2
2
  require "appsignal/integrations/sinatra"
3
3
 
4
+ module SinatraRequestHelpers
5
+ def make_request(env)
6
+ middleware.call(env)
7
+ end
8
+
9
+ def make_request_with_error(env, error)
10
+ expect { middleware.call(env) }.to raise_error(error)
11
+ end
12
+ end
13
+
4
14
  describe Appsignal::Rack::SinatraInstrumentation do
15
+ include SinatraRequestHelpers
16
+
5
17
  let(:settings) { double(:raise_errors => false) }
6
18
  let(:app) { double(:call => true, :settings => settings) }
7
19
  let(:env) { { "sinatra.route" => "GET /", :path => "/", :method => "GET" } }
8
20
  let(:middleware) { Appsignal::Rack::SinatraInstrumentation.new(app) }
9
21
 
22
+ before(:context) { start_agent }
23
+ around do |example|
24
+ keep_transactions { example.run }
25
+ end
26
+
10
27
  describe "#call" do
11
- before do
12
- start_agent
13
- allow(middleware).to receive(:raw_payload).and_return({})
14
- allow(Appsignal).to receive(:active?).and_return(true)
15
- end
28
+ before { allow(middleware).to receive(:raw_payload).and_return({}) }
16
29
 
17
- it "should call without monitoring" do
18
- expect(Appsignal::Transaction).to_not receive(:create)
30
+ it "doesn't instrument requests" do
31
+ make_request(env)
32
+ expect(created_transactions.count).to eq(0)
19
33
  end
20
-
21
- after { middleware.call(env) }
22
34
  end
23
35
 
24
36
  describe ".settings" do
25
37
  subject { middleware.settings }
26
38
 
27
- it "should return the app's settings" do
39
+ it "returns the app's settings" do
28
40
  expect(subject).to eq(app.settings)
29
41
  end
30
42
  end
31
43
  end
32
44
 
33
45
  describe Appsignal::Rack::SinatraBaseInstrumentation do
34
- before :context do
35
- start_agent
36
- end
46
+ include SinatraRequestHelpers
37
47
 
38
48
  let(:settings) { double(:raise_errors => false) }
39
49
  let(:app) { double(:call => true, :settings => settings) }
@@ -41,11 +51,16 @@ if DependencyHelper.sinatra_present?
41
51
  let(:options) { {} }
42
52
  let(:middleware) { Appsignal::Rack::SinatraBaseInstrumentation.new(app, options) }
43
53
 
54
+ before(:context) { start_agent }
55
+ around do |example|
56
+ keep_transactions { example.run }
57
+ end
58
+
44
59
  describe "#initialize" do
45
60
  context "with no settings method in the Sinatra app" do
46
61
  let(:app) { double(:call => true) }
47
62
 
48
- it "should not raise errors" do
63
+ it "does not raise errors" do
49
64
  expect(middleware.raise_errors_on).to be(false)
50
65
  end
51
66
  end
@@ -53,7 +68,7 @@ if DependencyHelper.sinatra_present?
53
68
  context "with no raise_errors setting in the Sinatra app" do
54
69
  let(:app) { double(:call => true, :settings => double) }
55
70
 
56
- it "should not raise errors" do
71
+ it "does not raise errors" do
57
72
  expect(middleware.raise_errors_on).to be(false)
58
73
  end
59
74
  end
@@ -61,7 +76,7 @@ if DependencyHelper.sinatra_present?
61
76
  context "with raise_errors turned off in the Sinatra app" do
62
77
  let(:app) { double(:call => true, :settings => double(:raise_errors => false)) }
63
78
 
64
- it "should raise errors" do
79
+ it "raises errors" do
65
80
  expect(middleware.raise_errors_on).to be(false)
66
81
  end
67
82
  end
@@ -69,21 +84,17 @@ if DependencyHelper.sinatra_present?
69
84
  context "with raise_errors turned on in the Sinatra app" do
70
85
  let(:app) { double(:call => true, :settings => double(:raise_errors => true)) }
71
86
 
72
- it "should raise errors" do
87
+ it "raises errors" do
73
88
  expect(middleware.raise_errors_on).to be(true)
74
89
  end
75
90
  end
76
91
  end
77
92
 
78
93
  describe "#call" do
79
- before do
80
- allow(middleware).to receive(:raw_payload).and_return({})
81
- end
94
+ before { allow(middleware).to receive(:raw_payload).and_return({}) }
82
95
 
83
96
  context "when appsignal is active" do
84
- before { allow(Appsignal).to receive(:active?).and_return(true) }
85
-
86
- it "should call with monitoring" do
97
+ it "instruments requests" do
87
98
  expect(middleware).to receive(:call_with_appsignal_monitoring).with(env)
88
99
  end
89
100
  end
@@ -91,34 +102,36 @@ if DependencyHelper.sinatra_present?
91
102
  context "when appsignal is not active" do
92
103
  before { allow(Appsignal).to receive(:active?).and_return(false) }
93
104
 
94
- it "should not call with monitoring" do
95
- expect(middleware).to_not receive(:call_with_appsignal_monitoring)
105
+ it "does not instrument requests" do
106
+ expect(created_transactions.count).to eq(0)
96
107
  end
97
108
 
98
- it "should call the stack" do
109
+ it "calls the next middleware in the stack" do
99
110
  expect(app).to receive(:call).with(env)
100
111
  end
101
112
  end
102
113
 
103
- after { middleware.call(env) }
114
+ after { make_request(env) }
104
115
  end
105
116
 
106
- describe "#call_with_appsignal_monitoring", :error => false do
107
- it "should create a transaction" do
108
- expect(Appsignal::Transaction).to receive(:create).with(
109
- kind_of(String),
110
- Appsignal::Transaction::HTTP_REQUEST,
111
- kind_of(Sinatra::Request),
112
- kind_of(Hash)
113
- ).and_return(double(:set_action_if_nil => nil, :set_http_or_background_queue_start => nil,
114
- :set_metadata => nil))
115
- end
117
+ describe "#call_with_appsignal_monitoring" do
118
+ context "without an error" do
119
+ it "creates a transaction" do
120
+ expect(app).to receive(:call).with(env)
121
+
122
+ make_request(env)
116
123
 
117
- it "should call the app" do
118
- expect(app).to receive(:call).with(env)
124
+ expect(created_transactions.count).to eq(1)
125
+ expect(last_transaction.to_h).to include(
126
+ "namespace" => Appsignal::Transaction::HTTP_REQUEST,
127
+ "action" => "GET /",
128
+ "error" => nil,
129
+ "metadata" => { "path" => "" }
130
+ )
131
+ end
119
132
  end
120
133
 
121
- context "with an error", :error => true do
134
+ context "with an error" do
122
135
  let(:error) { ExampleException }
123
136
  let(:app) do
124
137
  double.tap do |d|
@@ -128,23 +141,48 @@ if DependencyHelper.sinatra_present?
128
141
  end
129
142
 
130
143
  it "records the exception" do
131
- expect_any_instance_of(Appsignal::Transaction).to receive(:set_error).with(error)
144
+ make_request_with_error(env, error)
145
+
146
+ expect(created_transactions.count).to eq(1)
147
+ expect(last_transaction.to_h).to include(
148
+ "namespace" => Appsignal::Transaction::HTTP_REQUEST,
149
+ "action" => "GET /",
150
+ "error" => hash_including(
151
+ "name" => "ExampleException",
152
+ "message" => "ExampleException"
153
+ )
154
+ )
132
155
  end
133
156
  end
134
157
 
135
158
  context "with an error in sinatra.error" do
136
- let(:error) { ExampleException }
159
+ let(:error) { ExampleException.new }
137
160
  let(:env) { { "sinatra.error" => error } }
138
161
 
139
- it "records the exception" do
140
- expect_any_instance_of(Appsignal::Transaction).to receive(:set_error).with(error)
162
+ context "when raise_errors is off" do
163
+ let(:settings) { double(:raise_errors => false) }
164
+
165
+ it "record the error" do
166
+ make_request(env)
167
+
168
+ expect(created_transactions.count).to eq(1)
169
+ expect(last_transaction.to_h).to include(
170
+ "error" => hash_including(
171
+ "name" => "ExampleException",
172
+ "message" => "ExampleException"
173
+ )
174
+ )
175
+ end
141
176
  end
142
177
 
143
178
  context "when raise_errors is on" do
144
179
  let(:settings) { double(:raise_errors => true) }
145
180
 
146
181
  it "does not record the error" do
147
- expect_any_instance_of(Appsignal::Transaction).to_not receive(:set_error)
182
+ make_request(env)
183
+
184
+ expect(created_transactions.count).to eq(1)
185
+ expect(last_transaction.to_h).to include("error" => nil)
148
186
  end
149
187
  end
150
188
 
@@ -152,22 +190,30 @@ if DependencyHelper.sinatra_present?
152
190
  let(:env) { { "sinatra.error" => error, "sinatra.skip_appsignal_error" => true } }
153
191
 
154
192
  it "does not record the error" do
155
- expect_any_instance_of(Appsignal::Transaction).to_not receive(:set_error)
193
+ make_request(env)
194
+
195
+ expect(created_transactions.count).to eq(1)
196
+ expect(last_transaction.to_h).to include("error" => nil)
156
197
  end
157
198
  end
158
199
  end
159
200
 
160
201
  describe "action name" do
161
- it "should set the action" do
162
- expect_any_instance_of(Appsignal::Transaction)
163
- .to receive(:set_action_if_nil).with("GET /")
202
+ it "sets the action" do
203
+ make_request(env)
204
+
205
+ expect(created_transactions.count).to eq(1)
206
+ expect(last_transaction.to_h).to include("action" => "GET /")
164
207
  end
165
208
 
166
209
  context "without 'sinatra.route' env" do
167
210
  let(:env) { { :path => "/", :method => "GET" } }
168
211
 
169
- it "returns nil" do
170
- expect_any_instance_of(Appsignal::Transaction).to receive(:set_action_if_nil).with(nil)
212
+ it "doesn't set an action name" do
213
+ make_request(env)
214
+
215
+ expect(created_transactions.count).to eq(1)
216
+ expect(last_transaction.to_h).to include("action" => nil)
171
217
  end
172
218
  end
173
219
 
@@ -175,44 +221,90 @@ if DependencyHelper.sinatra_present?
175
221
  before { env["SCRIPT_NAME"] = "/api" }
176
222
 
177
223
  it "should call set_action with an application prefix path" do
178
- expect_any_instance_of(Appsignal::Transaction)
179
- .to receive(:set_action_if_nil).with("GET /api/")
224
+ make_request(env)
225
+
226
+ expect(created_transactions.count).to eq(1)
227
+ expect(last_transaction.to_h).to include("action" => "GET /api/")
180
228
  end
181
229
 
182
230
  context "without 'sinatra.route' env" do
183
231
  let(:env) { { :path => "/", :method => "GET" } }
184
232
 
185
- it "returns nil" do
186
- expect_any_instance_of(Appsignal::Transaction)
187
- .to receive(:set_action_if_nil).with(nil)
233
+ it "doesn't set an action name" do
234
+ make_request(env)
235
+
236
+ expect(created_transactions.count).to eq(1)
237
+ expect(last_transaction.to_h).to include("action" => nil)
188
238
  end
189
239
  end
190
240
  end
191
241
  end
192
242
 
193
- it "should set metadata" do
194
- expect_any_instance_of(Appsignal::Transaction).to receive(:set_metadata).twice
243
+ context "metadata" do
244
+ let(:env) { { "PATH_INFO" => "/some/path", "REQUEST_METHOD" => "GET" } }
245
+
246
+ it "sets metadata from the environment" do
247
+ make_request(env)
248
+
249
+ expect(created_transactions.count).to eq(1)
250
+ expect(last_transaction.to_h).to include(
251
+ "metadata" => {
252
+ "method" => "GET",
253
+ "path" => "/some/path"
254
+ },
255
+ "sample_data" => hash_including(
256
+ "environment" => hash_including(
257
+ "REQUEST_METHOD" => "GET",
258
+ "PATH_INFO" => "/some/path"
259
+ )
260
+ )
261
+ )
262
+ end
263
+ end
264
+
265
+ context "with queue start" do
266
+ let(:queue_start_time) { fixed_time * 1_000 }
267
+ let(:env) do
268
+ { "HTTP_X_REQUEST_START" => "t=#{queue_start_time.to_i}" } # in milliseconds
269
+ end
270
+
271
+ it "sets the queue start" do
272
+ make_request(env)
273
+ expect(last_transaction.ext.queue_start).to eq(queue_start_time)
274
+ end
195
275
  end
196
276
 
197
- it "should set the queue start" do
198
- expect_any_instance_of(Appsignal::Transaction)
199
- .to receive(:set_http_or_background_queue_start)
277
+ class FilteredRequest
278
+ def initialize(_args) # rubocop:disable Style/RedundantInitialize
279
+ end
280
+
281
+ def path
282
+ "/static/path"
283
+ end
284
+
285
+ def request_method
286
+ "GET"
287
+ end
288
+
289
+ def filtered_params
290
+ { "abc" => "123" }
291
+ end
200
292
  end
201
293
 
202
294
  context "with overridden request class and params method" do
203
- let(:options) { { :request_class => ::Rack::Request, :params_method => :filtered_params } }
295
+ let(:options) { { :request_class => FilteredRequest, :params_method => :filtered_params } }
204
296
 
205
- it "should use the overridden request class and params method" do
206
- request = ::Rack::Request.new(env)
207
- expect(::Rack::Request).to receive(:new)
208
- .with(env.merge(:params_method => :filtered_params))
209
- .at_least(:once)
210
- .and_return(request)
297
+ it "uses the overridden request class and params method to fetch params" do
298
+ make_request(env)
299
+
300
+ expect(created_transactions.count).to eq(1)
301
+ expect(last_transaction.to_h).to include(
302
+ "sample_data" => hash_including(
303
+ "params" => { "abc" => "123" }
304
+ )
305
+ )
211
306
  end
212
307
  end
213
-
214
- after(:error => false) { middleware.call(env) }
215
- after(:error => true) { expect { middleware.call(env) }.to raise_error(error) }
216
308
  end
217
309
  end
218
310
  end
@@ -1,6 +1,7 @@
1
1
  require "appsignal/rack/streaming_listener"
2
2
 
3
3
  describe Appsignal::Rack::StreamingListener do
4
+ before(:context) { start_agent }
4
5
  let(:headers) { {} }
5
6
  let(:env) do
6
7
  {
@@ -303,7 +303,7 @@ describe Appsignal::Transaction do
303
303
  context "with overridden options" do
304
304
  let(:options) { { :params_method => :filtered_params } }
305
305
 
306
- it "sets the overriden :params_method" do
306
+ it "sets the overridden :params_method" do
307
307
  expect(subject[:params_method]).to eq :filtered_params
308
308
  end
309
309
  end
@@ -355,7 +355,7 @@ describe Appsignal::Transaction do
355
355
  end
356
356
 
357
357
  describe "#set_tags" do
358
- let(:long_string) { "a" * 2001 }
358
+ let(:long_string) { "a" * 10_001 }
359
359
  before do
360
360
  transaction.set_tags(
361
361
  :valid_key => "valid_value",
@@ -377,7 +377,7 @@ describe Appsignal::Transaction do
377
377
  "valid_string_key" => "valid_value",
378
378
  "both_symbols" => "valid_value",
379
379
  "integer_value" => 1,
380
- "too_long_value" => "#{"a" * 2000}...",
380
+ "too_long_value" => "#{"a" * 10_000}...",
381
381
  long_string => "too_long_key"
382
382
  )
383
383
  end
@@ -432,6 +432,19 @@ describe Appsignal::Transaction do
432
432
  expect(breadcrumb["metadata"]).to eq({})
433
433
  end
434
434
  end
435
+
436
+ context "with metadata argument that's not a Hash" do
437
+ it "does not add the breadcrumb and logs and error" do
438
+ transaction.add_breadcrumb("category", "action", "message", "invalid metadata")
439
+ transaction.sample_data
440
+
441
+ expect(transaction.to_h["sample_data"]["breadcrumbs"]).to be_empty
442
+ expect(log_contents(log)).to contains_log(
443
+ :error,
444
+ "add_breadcrumb: Cannot add breadcrumb. The given metadata argument is not a Hash."
445
+ )
446
+ end
447
+ end
435
448
  end
436
449
 
437
450
  describe "#set_action" do
@@ -567,7 +580,7 @@ describe Appsignal::Transaction do
567
580
  it "does not raise an error when the queue start is too big" do
568
581
  expect(transaction.ext).to receive(:set_queue_start).and_raise(RangeError)
569
582
 
570
- expect(Appsignal.logger).to receive(:warn).with("Queue start value 10 is too big")
583
+ expect(Appsignal.internal_logger).to receive(:warn).with("Queue start value 10 is too big")
571
584
 
572
585
  expect do
573
586
  transaction.set_queue_start(10)
@@ -623,6 +636,18 @@ describe Appsignal::Transaction do
623
636
  expect(transaction.to_h["metadata"]).to eq("request_method" => "GET")
624
637
  end
625
638
 
639
+ context "when filter_metadata includes metadata key" do
640
+ before { Appsignal.config[:filter_metadata] = ["filter_key"] }
641
+ after { Appsignal.config[:filter_metadata] = [] }
642
+
643
+ it "does not set the metadata on the transaction" do
644
+ transaction.set_metadata(:filter_key, "filtered value")
645
+ transaction.set_metadata("filter_key", "filtered value")
646
+
647
+ expect(transaction.to_h["metadata"].keys).to_not include("filter_key")
648
+ end
649
+ end
650
+
626
651
  context "when the key is nil" do
627
652
  it "does not update the metadata on the transaction" do
628
653
  transaction.set_metadata(nil, "GET")
@@ -663,6 +688,8 @@ describe Appsignal::Transaction do
663
688
  transaction.set_sample_data("params", "string")
664
689
 
665
690
  expect(transaction.to_h["sample_data"]).to eq({})
691
+ expect(log_contents(log)).to contains_log :error,
692
+ %(Invalid sample data for 'params'. Value is not an Array or Hash: '"string"')
666
693
  end
667
694
  end
668
695
 
@@ -724,7 +751,7 @@ describe Appsignal::Transaction do
724
751
  e
725
752
  end
726
753
 
727
- it "should also respond to add_exception for backwords compatibility" do
754
+ it "should also respond to add_exception for backwards compatibility" do
728
755
  expect(transaction).to respond_to(:add_exception)
729
756
  end
730
757
 
@@ -739,7 +766,7 @@ describe Appsignal::Transaction do
739
766
  let(:error) { Object.new }
740
767
 
741
768
  it "does not add the error" do
742
- expect(Appsignal.logger).to receive(:error).with(
769
+ expect(Appsignal.internal_logger).to receive(:error).with(
743
770
  "Appsignal::Transaction#set_error: Cannot set error. " \
744
771
  "The given value is not an exception: #{error.inspect}"
745
772
  )
@@ -761,6 +788,99 @@ describe Appsignal::Transaction do
761
788
  end
762
789
  end
763
790
 
791
+ context "when the error has no causes" do
792
+ it "should not send the causes information as sample data" do
793
+ expect(transaction.ext).to_not receive(:set_sample_data)
794
+
795
+ transaction.set_error(error)
796
+ end
797
+ end
798
+
799
+ context "when the error has multiple causes" do
800
+ let(:error) do
801
+ e = ExampleStandardError.new("test message")
802
+ e2 = RuntimeError.new("cause message")
803
+ e3 = StandardError.new("cause message 2")
804
+ allow(e).to receive(:backtrace).and_return(["line 1"])
805
+ allow(e).to receive(:cause).and_return(e2)
806
+ allow(e2).to receive(:cause).and_return(e3)
807
+ e
808
+ end
809
+
810
+ it "sends the causes information as sample data" do
811
+ expect(transaction.ext).to receive(:set_error).with(
812
+ "ExampleStandardError",
813
+ "test message",
814
+ Appsignal::Utils::Data.generate(["line 1"])
815
+ )
816
+
817
+ expect(transaction.ext).to receive(:set_sample_data).with(
818
+ "error_causes",
819
+ Appsignal::Utils::Data.generate(
820
+ [
821
+ {
822
+ :name => "RuntimeError",
823
+ :message => "cause message"
824
+ },
825
+ {
826
+ :name => "StandardError",
827
+ :message => "cause message 2"
828
+ }
829
+ ]
830
+ )
831
+ )
832
+
833
+ expect(Appsignal.internal_logger).to_not receive(:debug)
834
+
835
+ transaction.set_error(error)
836
+ end
837
+ end
838
+
839
+ context "when the error has too many causes" do
840
+ let(:error) do
841
+ e = ExampleStandardError.new("root cause error")
842
+
843
+ 11.times do |i|
844
+ next_e = ExampleStandardError.new("wrapper error #{i}")
845
+ allow(next_e).to receive(:cause).and_return(e)
846
+ e = next_e
847
+ end
848
+
849
+ allow(e).to receive(:backtrace).and_return(["line 1"])
850
+ e
851
+ end
852
+
853
+ it "sends only the first causes as sample data" do
854
+ expect(transaction.ext).to receive(:set_error).with(
855
+ "ExampleStandardError",
856
+ "wrapper error 10",
857
+ Appsignal::Utils::Data.generate(["line 1"])
858
+ )
859
+
860
+ expected_error_causes = Array.new(10) do |i|
861
+ {
862
+ :name => "ExampleStandardError",
863
+ :message => "wrapper error #{9 - i}"
864
+ }
865
+ end
866
+
867
+ expected_error_causes.last[:is_root_cause] = false
868
+
869
+ expect(transaction.ext).to receive(:set_sample_data).with(
870
+ "error_causes",
871
+ Appsignal::Utils::Data.generate(expected_error_causes)
872
+ )
873
+
874
+ expect(Appsignal.internal_logger).to receive(:debug).with(
875
+ "Appsignal::Transaction#set_error: Error has more " \
876
+ "than 10 error causes. Only the first 10 " \
877
+ "will be reported."
878
+ )
879
+
880
+ transaction.set_error(error)
881
+ end
882
+ end
883
+
764
884
  context "when error message is nil" do
765
885
  let(:error) do
766
886
  e = ExampleStandardError.new
@@ -1275,8 +1395,8 @@ describe Appsignal::Transaction do
1275
1395
  end
1276
1396
  end
1277
1397
 
1278
- describe "#metadata" do
1279
- subject { transaction.send(:metadata) }
1398
+ describe "#sanitized_metadata" do
1399
+ subject { transaction.send(:sanitized_metadata) }
1280
1400
 
1281
1401
  context "when request is nil" do
1282
1402
  let(:request) { nil }
@@ -1291,9 +1411,18 @@ describe Appsignal::Transaction do
1291
1411
  end
1292
1412
 
1293
1413
  context "when env is present" do
1294
- let(:env) { { :metadata => { :key => "value" } } }
1414
+ let(:env) { { "key" => "value" } }
1295
1415
 
1296
- it { is_expected.to eq env[:metadata] }
1416
+ it { is_expected.to eq("key" => "value") }
1417
+
1418
+ context "with filter_metadata option set" do
1419
+ before { Appsignal.config[:filter_metadata] = ["key"] }
1420
+ after { Appsignal.config[:filter_metadata] = [] }
1421
+
1422
+ it "filters out keys listed in the filter_metadata option" do
1423
+ expect(subject.keys).to_not include("key")
1424
+ end
1425
+ end
1297
1426
  end
1298
1427
  end
1299
1428