appsignal 3.10.0 → 3.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/CHANGELOG.md +88 -0
  4. data/Gemfile +1 -0
  5. data/benchmark.rake +99 -42
  6. data/lib/appsignal/cli/demo.rb +0 -1
  7. data/lib/appsignal/config.rb +54 -98
  8. data/lib/appsignal/demo.rb +15 -20
  9. data/lib/appsignal/event_formatter/rom/sql_formatter.rb +1 -0
  10. data/lib/appsignal/event_formatter.rb +3 -2
  11. data/lib/appsignal/helpers/instrumentation.rb +331 -19
  12. data/lib/appsignal/hooks/action_cable.rb +21 -16
  13. data/lib/appsignal/hooks/active_job.rb +14 -8
  14. data/lib/appsignal/hooks/delayed_job.rb +1 -1
  15. data/lib/appsignal/hooks/shoryuken.rb +3 -63
  16. data/lib/appsignal/integrations/action_cable.rb +5 -7
  17. data/lib/appsignal/integrations/active_support_notifications.rb +1 -0
  18. data/lib/appsignal/integrations/capistrano/capistrano_2_tasks.rb +36 -35
  19. data/lib/appsignal/integrations/data_mapper.rb +1 -0
  20. data/lib/appsignal/integrations/delayed_job_plugin.rb +27 -33
  21. data/lib/appsignal/integrations/dry_monitor.rb +1 -0
  22. data/lib/appsignal/integrations/excon.rb +1 -0
  23. data/lib/appsignal/integrations/http.rb +1 -0
  24. data/lib/appsignal/integrations/net_http.rb +1 -0
  25. data/lib/appsignal/integrations/object.rb +6 -0
  26. data/lib/appsignal/integrations/que.rb +13 -20
  27. data/lib/appsignal/integrations/railtie.rb +1 -1
  28. data/lib/appsignal/integrations/rake.rb +1 -5
  29. data/lib/appsignal/integrations/redis.rb +1 -0
  30. data/lib/appsignal/integrations/redis_client.rb +1 -0
  31. data/lib/appsignal/integrations/resque.rb +2 -5
  32. data/lib/appsignal/integrations/shoryuken.rb +75 -0
  33. data/lib/appsignal/integrations/sidekiq.rb +7 -15
  34. data/lib/appsignal/integrations/unicorn.rb +1 -0
  35. data/lib/appsignal/integrations/webmachine.rb +2 -5
  36. data/lib/appsignal/logger.rb +7 -3
  37. data/lib/appsignal/probes/helpers.rb +1 -0
  38. data/lib/appsignal/probes/mri.rb +1 -0
  39. data/lib/appsignal/probes/sidekiq.rb +1 -0
  40. data/lib/appsignal/probes.rb +3 -0
  41. data/lib/appsignal/rack/abstract_middleware.rb +18 -12
  42. data/lib/appsignal/rack/event_handler.rb +39 -8
  43. data/lib/appsignal/rack/generic_instrumentation.rb +1 -0
  44. data/lib/appsignal/rack/grape_middleware.rb +2 -1
  45. data/lib/appsignal/rack/streaming_listener.rb +1 -0
  46. data/lib/appsignal/rack.rb +29 -0
  47. data/lib/appsignal/span.rb +1 -0
  48. data/lib/appsignal/transaction.rb +308 -101
  49. data/lib/appsignal/utils/data.rb +0 -1
  50. data/lib/appsignal/utils/hash_sanitizer.rb +0 -1
  51. data/lib/appsignal/utils/integration_logger.rb +0 -13
  52. data/lib/appsignal/utils/integration_memory_logger.rb +0 -13
  53. data/lib/appsignal/utils/json.rb +0 -1
  54. data/lib/appsignal/utils/query_params_sanitizer.rb +0 -1
  55. data/lib/appsignal/utils/stdout_and_logger_message.rb +0 -1
  56. data/lib/appsignal/utils.rb +6 -0
  57. data/lib/appsignal/version.rb +1 -1
  58. data/lib/appsignal.rb +6 -5
  59. data/spec/lib/appsignal/capistrano2_spec.rb +1 -1
  60. data/spec/lib/appsignal/config_spec.rb +138 -43
  61. data/spec/lib/appsignal/hooks/action_cable_spec.rb +43 -74
  62. data/spec/lib/appsignal/hooks/activejob_spec.rb +9 -0
  63. data/spec/lib/appsignal/hooks/delayed_job_spec.rb +2 -443
  64. data/spec/lib/appsignal/hooks/shoryuken_spec.rb +0 -171
  65. data/spec/lib/appsignal/integrations/delayed_job_plugin_spec.rb +459 -0
  66. data/spec/lib/appsignal/integrations/que_spec.rb +3 -4
  67. data/spec/lib/appsignal/integrations/shoryuken_spec.rb +167 -0
  68. data/spec/lib/appsignal/integrations/sidekiq_spec.rb +4 -4
  69. data/spec/lib/appsignal/integrations/webmachine_spec.rb +13 -1
  70. data/spec/lib/appsignal/rack/abstract_middleware_spec.rb +48 -3
  71. data/spec/lib/appsignal/rack/event_handler_spec.rb +81 -10
  72. data/spec/lib/appsignal/rack/rails_instrumentation_spec.rb +4 -2
  73. data/spec/lib/appsignal/rack_spec.rb +63 -0
  74. data/spec/lib/appsignal/transaction_spec.rb +1634 -1071
  75. data/spec/lib/appsignal/utils/integration_logger_spec.rb +12 -16
  76. data/spec/lib/appsignal/utils/integration_memory_logger_spec.rb +0 -10
  77. data/spec/lib/appsignal_spec.rb +323 -10
  78. data/spec/support/helpers/transaction_helpers.rb +44 -20
  79. data/spec/support/matchers/transaction.rb +15 -1
  80. data/spec/support/testing.rb +1 -1
  81. metadata +6 -2
@@ -0,0 +1,459 @@
1
+ describe "Appsignal::Integrations::DelayedJobHook" do
2
+ before(:context) do
3
+ module Delayed
4
+ class Plugin
5
+ def self.callbacks
6
+ end
7
+ end
8
+
9
+ class Worker
10
+ def self.plugins
11
+ @plugins ||= []
12
+ end
13
+ end
14
+ end
15
+ require "appsignal/integrations/delayed_job_plugin"
16
+ end
17
+ after(:context) { Object.send(:remove_const, :Delayed) }
18
+ before { start_agent }
19
+
20
+ # We haven't found a way to test the hooks, we'll have to do that manually
21
+
22
+ describe ".invoke_with_instrumentation" do
23
+ let(:plugin) { Appsignal::Integrations::DelayedJobPlugin }
24
+ let(:time) { Time.parse("01-01-2001 10:01:00UTC") }
25
+ let(:created_at) { time - 3600 }
26
+ let(:run_at) { time - 3600 }
27
+ let(:payload_object) { double(:args => args) }
28
+ let(:job_data) do
29
+ {
30
+ :id => 123,
31
+ :name => "TestClass#perform",
32
+ :priority => 1,
33
+ :attempts => 1,
34
+ :queue => "default",
35
+ :created_at => created_at,
36
+ :run_at => run_at,
37
+ :payload_object => payload_object
38
+ }
39
+ end
40
+ let(:args) { ["argument"] }
41
+ let(:job) { double(job_data) }
42
+ let(:invoked_block) { proc {} }
43
+
44
+ def perform
45
+ Timecop.freeze(time) do
46
+ keep_transactions do
47
+ plugin.invoke_with_instrumentation(job, invoked_block)
48
+ end
49
+ end
50
+ end
51
+
52
+ context "with a normal call" do
53
+ it "wraps it in a transaction" do
54
+ perform
55
+
56
+ transaction = last_transaction
57
+ expect(transaction).to have_namespace("background_job")
58
+ expect(transaction).to have_action("TestClass#perform")
59
+ expect(transaction).to_not have_error
60
+ expect(transaction).to include_event(:name => "perform_job.delayed_job")
61
+ expect(transaction).to include_tags(
62
+ "priority" => 1,
63
+ "attempts" => 1,
64
+ "queue" => "default",
65
+ "id" => "123"
66
+ )
67
+ expect(transaction).to include_params(["argument"])
68
+ end
69
+
70
+ context "with more complex params" do
71
+ let(:args) do
72
+ {
73
+ :foo => "Foo",
74
+ :bar => "Bar"
75
+ }
76
+ end
77
+
78
+ it "adds the more complex arguments" do
79
+ perform
80
+
81
+ expect(last_transaction).to include_params("foo" => "Foo", "bar" => "Bar")
82
+ end
83
+
84
+ context "with parameter filtering" do
85
+ before do
86
+ Appsignal.config = project_fixture_config("production")
87
+ Appsignal.config[:filter_parameters] = ["foo"]
88
+ end
89
+
90
+ it "filters selected arguments" do
91
+ perform
92
+
93
+ expect(last_transaction).to include_params("foo" => "[FILTERED]", "bar" => "Bar")
94
+ end
95
+ end
96
+ end
97
+
98
+ context "with run_at in the future" do
99
+ let(:run_at) { Time.parse("2017-01-01 10:01:00UTC") }
100
+
101
+ it "reports queue_start with run_at time" do
102
+ perform
103
+
104
+ expect(last_transaction).to have_queue_start(run_at.to_i * 1000)
105
+ end
106
+ end
107
+
108
+ context "with class method job" do
109
+ let(:job_data) do
110
+ { :name => "CustomClassMethod.perform", :payload_object => payload_object }
111
+ end
112
+
113
+ it "wraps it in a transaction using the class method job name" do
114
+ perform
115
+ expect(last_transaction).to have_action("CustomClassMethod.perform")
116
+ end
117
+ end
118
+
119
+ context "with custom name call" do
120
+ before { perform }
121
+
122
+ context "with appsignal_name defined" do
123
+ context "with payload_object being an object" do
124
+ context "with value" do
125
+ let(:payload_object) { double(:appsignal_name => "CustomClass#perform") }
126
+
127
+ it "wraps it in a transaction using the custom name" do
128
+ expect(last_transaction).to have_action("CustomClass#perform")
129
+ end
130
+ end
131
+
132
+ context "with non-String value" do
133
+ let(:payload_object) { double(:appsignal_name => Object.new) }
134
+
135
+ it "wraps it in a transaction using the original job name" do
136
+ expect(last_transaction).to have_action("TestClass#perform")
137
+ end
138
+ end
139
+
140
+ context "with class method name as job" do
141
+ let(:payload_object) { double(:appsignal_name => "CustomClassMethod.perform") }
142
+
143
+ it "wraps it in a transaction using the custom name" do
144
+ perform
145
+ expect(last_transaction).to have_action("CustomClassMethod.perform")
146
+ end
147
+ end
148
+ end
149
+
150
+ context "with payload_object being a Hash" do
151
+ context "with value" do
152
+ let(:payload_object) { double(:appsignal_name => "CustomClassHash#perform") }
153
+
154
+ it "wraps it in a transaction using the custom name" do
155
+ expect(last_transaction).to have_action("CustomClassHash#perform")
156
+ end
157
+ end
158
+
159
+ context "with non-String value" do
160
+ let(:payload_object) { double(:appsignal_name => Object.new) }
161
+
162
+ it "wraps it in a transaction using the original job name" do
163
+ expect(last_transaction).to have_action("TestClass#perform")
164
+ end
165
+ end
166
+
167
+ context "with class method name as job" do
168
+ let(:payload_object) { { :appsignal_name => "CustomClassMethod.perform" } }
169
+
170
+ it "wraps it in a transaction using the custom name" do
171
+ perform
172
+ expect(last_transaction).to have_action("CustomClassMethod.perform")
173
+ end
174
+ end
175
+ end
176
+
177
+ context "with payload_object acting like a Hash and returning a non-String value" do
178
+ class ClassActingAsHash
179
+ def self.[](_key)
180
+ Object.new
181
+ end
182
+
183
+ def self.appsignal_name
184
+ "ClassActingAsHash#perform"
185
+ end
186
+ end
187
+ let(:payload_object) { ClassActingAsHash }
188
+
189
+ # We check for hash values before object values
190
+ # this means ClassActingAsHash returns `Object.new` instead
191
+ # of `self.appsignal_name`. Since this isn't a valid `String`
192
+ # we return the default job name as action name.
193
+ it "wraps it in a transaction using the original job name" do
194
+ expect(last_transaction).to have_action("TestClass#perform")
195
+ end
196
+ end
197
+ end
198
+ end
199
+
200
+ context "with only job class name" do
201
+ let(:job_data) do
202
+ { :name => "Banana", :payload_object => payload_object }
203
+ end
204
+
205
+ it "appends #perform to the class name" do
206
+ perform
207
+ expect(last_transaction).to have_action("Banana#perform")
208
+ end
209
+ end
210
+
211
+ if active_job_present?
212
+ require "active_job"
213
+
214
+ context "when wrapped by ActiveJob" do
215
+ let(:payload_object) do
216
+ ActiveJob::QueueAdapters::DelayedJobAdapter::JobWrapper.new(
217
+ "arguments" => args,
218
+ "job_class" => "TestClass",
219
+ "job_id" => 123,
220
+ "locale" => :en,
221
+ "queue_name" => "default"
222
+ )
223
+ end
224
+ let(:job) do
225
+ double(
226
+ :id => 123,
227
+ :name => "ActiveJob::QueueAdapters::DelayedJobAdapter::JobWrapper",
228
+ :priority => 1,
229
+ :attempts => 1,
230
+ :queue => "default",
231
+ :created_at => created_at,
232
+ :run_at => run_at,
233
+ :payload_object => payload_object
234
+ )
235
+ end
236
+ let(:args) { ["activejob_argument"] }
237
+
238
+ it "wraps it in a transaction with the correct params" do
239
+ perform
240
+
241
+ transaction = last_transaction
242
+ expect(transaction).to have_namespace("background_job")
243
+ expect(transaction).to have_action("TestClass#perform")
244
+ expect(transaction).to_not have_error
245
+ expect(transaction).to include_event("name" => "perform_job.delayed_job")
246
+ expect(transaction).to include_tags(
247
+ "priority" => 1,
248
+ "attempts" => 1,
249
+ "queue" => "default",
250
+ "id" => "123"
251
+ )
252
+ expect(transaction).to include_params(["activejob_argument"])
253
+ end
254
+
255
+ context "with more complex params" do
256
+ let(:args) do
257
+ {
258
+ :foo => "Foo",
259
+ :bar => "Bar"
260
+ }
261
+ end
262
+
263
+ it "adds the more complex arguments" do
264
+ perform
265
+ transaction = last_transaction
266
+ expect(transaction).to have_action("TestClass#perform")
267
+ expect(transaction).to include_params(
268
+ "foo" => "Foo",
269
+ "bar" => "Bar"
270
+ )
271
+ end
272
+
273
+ context "with parameter filtering" do
274
+ before do
275
+ Appsignal.config = project_fixture_config("production")
276
+ Appsignal.config[:filter_parameters] = ["foo"]
277
+ end
278
+
279
+ it "filters selected arguments" do
280
+ perform
281
+ transaction = last_transaction
282
+ expect(transaction).to have_action("TestClass#perform")
283
+ expect(transaction).to include_params(
284
+ "foo" => "[FILTERED]",
285
+ "bar" => "Bar"
286
+ )
287
+ end
288
+ end
289
+ end
290
+
291
+ context "with run_at in the future" do
292
+ let(:run_at) { Time.parse("2017-01-01 10:01:00UTC") }
293
+
294
+ it "reports queue_start with run_at time" do
295
+ perform
296
+
297
+ expect(last_transaction).to have_queue_start(run_at.to_i * 1000)
298
+ end
299
+ end
300
+ end
301
+ end
302
+ end
303
+
304
+ context "with an erroring call" do
305
+ let(:error) { ExampleException.new("uh oh") }
306
+ before do
307
+ expect(invoked_block).to receive(:call).and_raise(error)
308
+ end
309
+
310
+ it "adds the error to the transaction" do
311
+ expect do
312
+ perform
313
+ end.to raise_error(error)
314
+
315
+ transaction = last_transaction
316
+ expect(transaction).to have_namespace("background_job")
317
+ expect(transaction).to have_action("TestClass#perform")
318
+ expect(transaction).to have_error("ExampleException", "uh oh")
319
+ end
320
+ end
321
+ end
322
+
323
+ describe ".extract_value" do
324
+ let(:plugin) { Appsignal::Integrations::DelayedJobPlugin }
325
+
326
+ context "for a hash" do
327
+ let(:hash) { { :key => "value", :bool_false => false } }
328
+
329
+ context "when the key exists" do
330
+ subject { plugin.extract_value(hash, :key) }
331
+
332
+ it { is_expected.to eq "value" }
333
+
334
+ context "when the value is false" do
335
+ subject { plugin.extract_value(hash, :bool_false) }
336
+
337
+ it { is_expected.to be false }
338
+ end
339
+ end
340
+
341
+ context "when the key does not exist" do
342
+ subject { plugin.extract_value(hash, :nonexistent_key) }
343
+
344
+ it { is_expected.to be_nil }
345
+
346
+ context "with a default value" do
347
+ subject { plugin.extract_value(hash, :nonexistent_key, 1) }
348
+
349
+ it { is_expected.to eq 1 }
350
+ end
351
+ end
352
+ end
353
+
354
+ context "for a struct" do
355
+ before :context do
356
+ TestStruct = Struct.new(:key)
357
+ end
358
+ let(:struct) { TestStruct.new("value") }
359
+
360
+ context "when the key exists" do
361
+ subject { plugin.extract_value(struct, :key) }
362
+
363
+ it { is_expected.to eq "value" }
364
+ end
365
+
366
+ context "when the key does not exist" do
367
+ subject { plugin.extract_value(struct, :nonexistent_key) }
368
+
369
+ it { is_expected.to be_nil }
370
+
371
+ context "with a default value" do
372
+ subject { plugin.extract_value(struct, :nonexistent_key, 1) }
373
+
374
+ it { is_expected.to eq 1 }
375
+ end
376
+ end
377
+ end
378
+
379
+ context "for a struct with a method" do
380
+ before :context do
381
+ class TestStructClass < Struct.new(:id) # rubocop:disable Style/StructInheritance
382
+ def appsignal_name
383
+ "TestStruct#perform"
384
+ end
385
+
386
+ def bool_false
387
+ false
388
+ end
389
+ end
390
+ end
391
+ let(:struct) { TestStructClass.new("id") }
392
+
393
+ context "when the Struct responds to a method" do
394
+ subject { plugin.extract_value(struct, :appsignal_name) }
395
+
396
+ it "returns the method value" do
397
+ is_expected.to eq "TestStruct#perform"
398
+ end
399
+
400
+ context "when the value is false" do
401
+ subject { plugin.extract_value(struct, :bool_false) }
402
+
403
+ it "returns the method value" do
404
+ is_expected.to be false
405
+ end
406
+ end
407
+ end
408
+
409
+ context "when the key does not exist" do
410
+ subject { plugin.extract_value(struct, :nonexistent_key) }
411
+
412
+ context "without a method with the same name" do
413
+ it "returns nil" do
414
+ is_expected.to be_nil
415
+ end
416
+ end
417
+
418
+ context "with a default value" do
419
+ let(:default_value) { :my_default_value }
420
+ subject { plugin.extract_value(struct, :nonexistent_key, default_value) }
421
+
422
+ it "returns the default value" do
423
+ is_expected.to eq default_value
424
+ end
425
+ end
426
+ end
427
+ end
428
+
429
+ context "for an object" do
430
+ let(:object) { double(:existing_method => "value") }
431
+
432
+ context "when the method exists" do
433
+ subject { plugin.extract_value(object, :existing_method) }
434
+
435
+ it { is_expected.to eq "value" }
436
+ end
437
+
438
+ context "when the method does not exist" do
439
+ subject { plugin.extract_value(object, :nonexistent_method) }
440
+
441
+ it { is_expected.to be_nil }
442
+
443
+ context "and there is a default value" do
444
+ subject { plugin.extract_value(object, :nonexistent_method, 1) }
445
+
446
+ it { is_expected.to eq 1 }
447
+ end
448
+ end
449
+ end
450
+
451
+ context "when we need to call to_s on the value" do
452
+ let(:object) { double(:existing_method => 1) }
453
+
454
+ subject { plugin.extract_value(object, :existing_method, nil, true) }
455
+
456
+ it { is_expected.to eq "1" }
457
+ end
458
+ end
459
+ end
@@ -39,7 +39,6 @@ if DependencyHelper.que_present?
39
39
  allow(Que).to receive(:execute)
40
40
 
41
41
  start_agent
42
- expect(Appsignal.active?).to be_truthy
43
42
  end
44
43
  around { |example| keep_transactions { example.run } }
45
44
 
@@ -66,7 +65,7 @@ if DependencyHelper.que_present?
66
65
  "title" => ""
67
66
  )
68
67
  expect(transaction).to include_params(%w[1 birds])
69
- expect(transaction).to include_sample_metadata(
68
+ expect(transaction).to include_tags(
70
69
  "attempts" => 0,
71
70
  "id" => 123,
72
71
  "priority" => 100,
@@ -95,7 +94,7 @@ if DependencyHelper.que_present?
95
94
  expect(transaction).to have_namespace(Appsignal::Transaction::BACKGROUND_JOB)
96
95
  expect(transaction).to have_error(error.class.name, error.message)
97
96
  expect(transaction).to include_params(%w[1 birds])
98
- expect(transaction).to include_sample_metadata(
97
+ expect(transaction).to include_tags(
99
98
  "attempts" => 0,
100
99
  "id" => 123,
101
100
  "priority" => 100,
@@ -120,7 +119,7 @@ if DependencyHelper.que_present?
120
119
  expect(transaction).to have_namespace(Appsignal::Transaction::BACKGROUND_JOB)
121
120
  expect(transaction).to have_error(error.class.name, error.message)
122
121
  expect(transaction).to include_params(%w[1 birds])
123
- expect(transaction).to include_sample_metadata(
122
+ expect(transaction).to include_tags(
124
123
  "attempts" => 0,
125
124
  "id" => 123,
126
125
  "priority" => 100,
@@ -0,0 +1,167 @@
1
+ require "appsignal/integrations/shoryuken"
2
+
3
+ describe Appsignal::Integrations::ShoryukenMiddleware do
4
+ class DemoShoryukenWorker
5
+ end
6
+
7
+ let(:time) { "2010-01-01 10:01:00UTC" }
8
+ let(:worker_instance) { DemoShoryukenWorker.new }
9
+ let(:queue) { "some-funky-queue-name" }
10
+ let(:sqs_msg) { double(:message_id => "msg1", :attributes => {}) }
11
+ let(:body) { {} }
12
+ before { start_agent }
13
+ around { |example| keep_transactions { example.run } }
14
+
15
+ def perform_shoryuken_job(&block)
16
+ block ||= lambda {}
17
+ Timecop.freeze(Time.parse(time)) do
18
+ described_class.new.call(
19
+ worker_instance,
20
+ queue,
21
+ sqs_msg,
22
+ body,
23
+ &block
24
+ )
25
+ end
26
+ end
27
+
28
+ context "with a performance call" do
29
+ let(:sent_timestamp) { Time.parse("1976-11-18 0:00:00UTC").to_i * 1000 }
30
+ let(:sqs_msg) do
31
+ double(:message_id => "msg1", :attributes => { "SentTimestamp" => sent_timestamp })
32
+ end
33
+
34
+ context "with complex argument" do
35
+ let(:body) { { :foo => "Foo", :bar => "Bar" } }
36
+
37
+ it "wraps the job in a transaction with the correct params" do
38
+ expect { perform_shoryuken_job }.to change { created_transactions.length }.by(1)
39
+
40
+ transaction = last_transaction
41
+ expect(transaction).to have_id
42
+ expect(transaction).to have_namespace(Appsignal::Transaction::BACKGROUND_JOB)
43
+ expect(transaction).to have_action("DemoShoryukenWorker#perform")
44
+ expect(transaction).to_not have_error
45
+ expect(transaction).to include_event(
46
+ "body" => "",
47
+ "body_format" => Appsignal::EventFormatter::DEFAULT,
48
+ "count" => 1,
49
+ "name" => "perform_job.shoryuken",
50
+ "title" => ""
51
+ )
52
+ expect(transaction).to include_params("foo" => "Foo", "bar" => "Bar")
53
+ expect(transaction).to include_tags(
54
+ "message_id" => "msg1",
55
+ "queue" => queue,
56
+ "SentTimestamp" => sent_timestamp
57
+ )
58
+ expect(transaction).to have_queue_start(sent_timestamp)
59
+ expect(transaction).to be_completed
60
+ end
61
+
62
+ context "with parameter filtering" do
63
+ before do
64
+ Appsignal.config = project_fixture_config("production")
65
+ Appsignal.config[:filter_parameters] = ["foo"]
66
+ end
67
+
68
+ it "filters selected arguments" do
69
+ perform_shoryuken_job
70
+
71
+ expect(last_transaction).to include_params("foo" => "[FILTERED]", "bar" => "Bar")
72
+ end
73
+ end
74
+ end
75
+
76
+ context "with a string as an argument" do
77
+ let(:body) { "foo bar" }
78
+
79
+ it "handles string arguments" do
80
+ perform_shoryuken_job
81
+
82
+ expect(last_transaction).to include_params("params" => body)
83
+ end
84
+ end
85
+
86
+ context "with primitive type as argument" do
87
+ let(:body) { 1 }
88
+
89
+ it "handles primitive types as arguments" do
90
+ perform_shoryuken_job
91
+
92
+ expect(last_transaction).to include_params("params" => body)
93
+ end
94
+ end
95
+ end
96
+
97
+ context "with exception" do
98
+ it "sets the exception on the transaction" do
99
+ expect do
100
+ expect do
101
+ perform_shoryuken_job { raise ExampleException, "error message" }
102
+ end.to raise_error(ExampleException)
103
+ end.to change { created_transactions.length }.by(1)
104
+
105
+ transaction = last_transaction
106
+ expect(transaction).to have_id
107
+ expect(transaction).to have_action("DemoShoryukenWorker#perform")
108
+ expect(transaction).to have_namespace(Appsignal::Transaction::BACKGROUND_JOB)
109
+ expect(transaction).to have_error("ExampleException", "error message")
110
+ expect(transaction).to be_completed
111
+ end
112
+ end
113
+
114
+ context "with batched jobs" do
115
+ let(:sqs_msg) do
116
+ [
117
+ double(
118
+ :message_id => "msg2",
119
+ :attributes => {
120
+ "SentTimestamp" => (Time.parse("1976-11-18 01:00:00UTC").to_i * 1000).to_s
121
+ }
122
+ ),
123
+ double(
124
+ :message_id => "msg1",
125
+ :attributes => { "SentTimestamp" => sent_timestamp.to_s }
126
+ )
127
+ ]
128
+ end
129
+ let(:body) do
130
+ [
131
+ "foo bar",
132
+ { :id => "123", :foo => "Foo", :bar => "Bar" }
133
+ ]
134
+ end
135
+ let(:sent_timestamp) { Time.parse("1976-11-18 01:00:00UTC").to_i * 1000 }
136
+
137
+ it "creates a transaction for the batch" do
138
+ expect do
139
+ perform_shoryuken_job {} # rubocop:disable Lint/EmptyBlock
140
+ end.to change { created_transactions.length }.by(1)
141
+
142
+ transaction = last_transaction
143
+ expect(transaction).to have_id
144
+ expect(transaction).to have_action("DemoShoryukenWorker#perform")
145
+ expect(transaction).to have_namespace(Appsignal::Transaction::BACKGROUND_JOB)
146
+ expect(transaction).to_not have_error
147
+ expect(transaction).to include_event(
148
+ "body" => "",
149
+ "body_format" => Appsignal::EventFormatter::DEFAULT,
150
+ "count" => 1,
151
+ "name" => "perform_job.shoryuken",
152
+ "title" => ""
153
+ )
154
+ expect(transaction).to include_params(
155
+ "msg2" => "foo bar",
156
+ "msg1" => { "id" => "123", "foo" => "Foo", "bar" => "Bar" }
157
+ )
158
+ expect(transaction).to include_tags(
159
+ "batch" => true,
160
+ "queue" => "some-funky-queue-name",
161
+ "SentTimestamp" => sent_timestamp.to_s # Earliest/oldest timestamp from messages
162
+ )
163
+ # Queue time based on earliest/oldest timestamp from messages
164
+ expect(transaction).to have_queue_start(sent_timestamp)
165
+ end
166
+ end
167
+ end
@@ -362,7 +362,7 @@ describe Appsignal::Integrations::SidekiqMiddleware, :with_yaml_parse_error => f
362
362
  perform_sidekiq_job { raise error, "uh oh" }
363
363
  end.to raise_error(error)
364
364
 
365
- expect(transaction).to have_id(jid)
365
+ expect(transaction).to have_id
366
366
  expect(transaction).to have_namespace(namespace)
367
367
  expect(transaction).to have_action("TestClass#perform")
368
368
  expect(transaction).to have_error("ExampleException", "uh oh")
@@ -373,7 +373,7 @@ describe Appsignal::Integrations::SidekiqMiddleware, :with_yaml_parse_error => f
373
373
  )
374
374
  expect(transaction).to_not include_environment
375
375
  expect(transaction).to include_params(expected_args)
376
- expect(transaction).to_not include_tags
376
+ expect(transaction).to include_tags("request_id" => jid)
377
377
  expect(transaction).to_not include_breadcrumbs
378
378
  expect_transaction_to_have_sidekiq_event(transaction)
379
379
  end
@@ -418,11 +418,11 @@ describe Appsignal::Integrations::SidekiqMiddleware, :with_yaml_parse_error => f
418
418
  .with("sidekiq_queue_job_count", 1, { :queue => "default", :status => :processed })
419
419
  perform_sidekiq_job
420
420
 
421
- expect(transaction).to have_id(jid)
421
+ expect(transaction).to have_id
422
422
  expect(transaction).to have_namespace(namespace)
423
423
  expect(transaction).to have_action("TestClass#perform")
424
424
  expect(transaction).to_not have_error
425
- expect(transaction).to_not include_tags
425
+ expect(transaction).to include_tags("request_id" => jid)
426
426
  expect(transaction).to_not include_environment
427
427
  expect(transaction).to_not include_breadcrumbs
428
428
  expect(transaction).to_not include_params(expected_args)