appsignal 2.10.10 → 2.11.0.beta.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/.semaphore/semaphore.yml +75 -61
  4. data/CHANGELOG.md +18 -3
  5. data/build_matrix.yml +13 -7
  6. data/ext/agent.yml +19 -19
  7. data/ext/appsignal_extension.c +10 -1
  8. data/gemfiles/padrino.gemfile +2 -2
  9. data/gemfiles/rails-4.2.gemfile +9 -2
  10. data/gemfiles/rails-5.0.gemfile +1 -0
  11. data/gemfiles/rails-5.1.gemfile +1 -0
  12. data/gemfiles/rails-5.2.gemfile +1 -0
  13. data/gemfiles/rails-6.0.gemfile +1 -0
  14. data/gemfiles/resque-1.gemfile +7 -0
  15. data/gemfiles/{resque.gemfile → resque-2.gemfile} +1 -1
  16. data/lib/appsignal.rb +21 -1
  17. data/lib/appsignal/capistrano.rb +2 -0
  18. data/lib/appsignal/config.rb +6 -2
  19. data/lib/appsignal/environment.rb +126 -0
  20. data/lib/appsignal/extension/jruby.rb +10 -0
  21. data/lib/appsignal/hooks.rb +2 -0
  22. data/lib/appsignal/hooks/active_job.rb +108 -0
  23. data/lib/appsignal/hooks/net_http.rb +2 -0
  24. data/lib/appsignal/hooks/puma.rb +2 -58
  25. data/lib/appsignal/hooks/redis.rb +2 -0
  26. data/lib/appsignal/hooks/resque.rb +60 -0
  27. data/lib/appsignal/hooks/sequel.rb +2 -0
  28. data/lib/appsignal/hooks/sidekiq.rb +18 -191
  29. data/lib/appsignal/integrations/object.rb +4 -0
  30. data/lib/appsignal/integrations/que.rb +1 -1
  31. data/lib/appsignal/integrations/resque.rb +9 -12
  32. data/lib/appsignal/integrations/resque_active_job.rb +9 -30
  33. data/lib/appsignal/probes/puma.rb +61 -0
  34. data/lib/appsignal/probes/sidekiq.rb +102 -0
  35. data/lib/appsignal/transaction.rb +32 -7
  36. data/lib/appsignal/utils/deprecation_message.rb +5 -1
  37. data/lib/appsignal/version.rb +1 -1
  38. data/lib/puma/plugin/appsignal.rb +2 -1
  39. data/spec/lib/appsignal/config_spec.rb +6 -1
  40. data/spec/lib/appsignal/environment_spec.rb +167 -0
  41. data/spec/lib/appsignal/hooks/activejob_spec.rb +521 -0
  42. data/spec/lib/appsignal/hooks/puma_spec.rb +2 -181
  43. data/spec/lib/appsignal/hooks/resque_spec.rb +185 -0
  44. data/spec/lib/appsignal/hooks/sidekiq_spec.rb +292 -546
  45. data/spec/lib/appsignal/integrations/padrino_spec.rb +1 -1
  46. data/spec/lib/appsignal/integrations/que_spec.rb +25 -6
  47. data/spec/lib/appsignal/integrations/resque_active_job_spec.rb +20 -179
  48. data/spec/lib/appsignal/integrations/resque_spec.rb +20 -85
  49. data/spec/lib/appsignal/probes/puma_spec.rb +180 -0
  50. data/spec/lib/appsignal/probes/sidekiq_spec.rb +204 -0
  51. data/spec/lib/appsignal/transaction_spec.rb +35 -20
  52. data/spec/lib/appsignal_spec.rb +22 -0
  53. data/spec/lib/puma/appsignal_spec.rb +1 -1
  54. data/spec/support/helpers/action_mailer_helpers.rb +25 -0
  55. data/spec/support/helpers/dependency_helper.rb +9 -2
  56. data/spec/support/helpers/env_helpers.rb +1 -1
  57. data/spec/support/helpers/environment_metdata_helper.rb +16 -0
  58. data/spec/support/helpers/transaction_helpers.rb +6 -0
  59. data/spec/support/stubs/sidekiq/api.rb +2 -2
  60. metadata +24 -4
@@ -0,0 +1,521 @@
1
+ if DependencyHelper.active_job_present?
2
+ require "active_job"
3
+ require "action_mailer"
4
+
5
+ describe Appsignal::Hooks::ActiveJobHook do
6
+ describe "#dependencies_present?" do
7
+ subject { described_class.new.dependencies_present? }
8
+
9
+ context "when ActiveJob constant is found" do
10
+ before { stub_const "ActiveJob", Class.new }
11
+
12
+ it { is_expected.to be_truthy }
13
+ end
14
+
15
+ context "when ActiveJob constant is not found" do
16
+ before { hide_const "ActiveJob" }
17
+
18
+ it { is_expected.to be_falsy }
19
+ end
20
+ end
21
+
22
+ describe "#install" do
23
+ it "extends ActiveJob::Base with the AppSignal ActiveJob plugin" do
24
+ start_agent
25
+
26
+ path, _line_number = ActiveJob::Base.method(:execute).source_location
27
+ expect(path).to end_with("/lib/appsignal/hooks/active_job.rb")
28
+ end
29
+ end
30
+ end
31
+
32
+ describe Appsignal::Hooks::ActiveJobHook::ActiveJobClassInstrumentation do
33
+ let(:time) { Time.parse("2001-01-01 10:00:00UTC") }
34
+ let(:namespace) { Appsignal::Transaction::BACKGROUND_JOB }
35
+ let(:queue) { "default" }
36
+ let(:log) { StringIO.new }
37
+ let(:parameterized_given_args) do
38
+ {
39
+ :foo => "Foo",
40
+ "bar" => "Bar",
41
+ "baz" => { "1" => "foo" }
42
+ }
43
+ end
44
+ let(:method_given_args) do
45
+ [
46
+ "foo",
47
+ parameterized_given_args
48
+ ]
49
+ end
50
+ let(:parameterized_expected_args) do
51
+ {
52
+ "_aj_symbol_keys" => ["foo"],
53
+ "foo" => "Foo",
54
+ "bar" => "Bar",
55
+ "baz" => {
56
+ "_aj_symbol_keys" => [],
57
+ "1" => "foo"
58
+ }
59
+ }
60
+ end
61
+ let(:method_expected_args) do
62
+ [
63
+ "foo",
64
+ parameterized_expected_args
65
+ ]
66
+ end
67
+ before do
68
+ ActiveJob::Base.queue_adapter = :inline
69
+
70
+ start_agent
71
+ Appsignal.logger = test_logger(log)
72
+ class ActiveJobTestJob < ActiveJob::Base
73
+ def perform(*_args)
74
+ end
75
+ end
76
+
77
+ class ActiveJobErrorTestJob < ActiveJob::Base
78
+ def perform
79
+ raise "uh oh"
80
+ end
81
+ end
82
+
83
+ class ActiveJobCustomQueueTestJob < ActiveJob::Base
84
+ queue_as :custom_queue
85
+
86
+ def perform(*_args)
87
+ end
88
+ end
89
+ end
90
+ around { |example| keep_transactions { example.run } }
91
+ after do
92
+ Object.send(:remove_const, :ActiveJobTestJob)
93
+ Object.send(:remove_const, :ActiveJobErrorTestJob)
94
+ Object.send(:remove_const, :ActiveJobCustomQueueTestJob)
95
+ end
96
+
97
+ it "reports the name from the ActiveJob integration" do
98
+ tags = { :queue => queue }
99
+ expect(Appsignal).to receive(:increment_counter)
100
+ .with("active_job_queue_job_count", 1, tags.merge(:status => :processed))
101
+
102
+ perform_job(ActiveJobTestJob)
103
+
104
+ transaction = last_transaction
105
+ transaction_hash = transaction.to_h
106
+ expect(transaction_hash).to include(
107
+ "action" => "ActiveJobTestJob#perform",
108
+ "error" => nil,
109
+ "namespace" => namespace,
110
+ "metadata" => {},
111
+ "sample_data" => hash_including(
112
+ "params" => [],
113
+ "tags" => { "queue" => queue }
114
+ )
115
+ )
116
+ events = transaction_hash["events"]
117
+ .sort_by { |e| e["start"] }
118
+ .map { |event| event["name"] }
119
+ expect(events).to eq(["perform_start.active_job", "perform.active_job"])
120
+ end
121
+
122
+ context "with custom queue" do
123
+ it "reports the custom queue as tag on the transaction" do
124
+ tags = { :queue => "custom_queue" }
125
+ expect(Appsignal).to receive(:increment_counter)
126
+ .with("active_job_queue_job_count", 1, tags.merge(:status => :processed))
127
+ perform_job(ActiveJobCustomQueueTestJob)
128
+
129
+ transaction = last_transaction
130
+ transaction_hash = transaction.to_h
131
+ expect(transaction_hash).to include(
132
+ "sample_data" => hash_including(
133
+ "tags" => { "queue" => "custom_queue" }
134
+ )
135
+ )
136
+ end
137
+ end
138
+
139
+ if DependencyHelper.rails_version >= Gem::Version.new("5.0.0")
140
+ context "with priority" do
141
+ before do
142
+ class ActiveJobPriorityTestJob < ActiveJob::Base
143
+ queue_with_priority 10
144
+
145
+ def perform(*_args)
146
+ end
147
+ end
148
+ end
149
+ after do
150
+ Object.send(:remove_const, :ActiveJobPriorityTestJob)
151
+ end
152
+
153
+ it "reports the priority as tag on the transaction" do
154
+ tags = { :priority => 10, :queue => queue }
155
+ expect(Appsignal).to receive(:increment_counter)
156
+ .with("active_job_queue_job_count", 1, tags.merge(:status => :processed))
157
+
158
+ perform_job(ActiveJobPriorityTestJob)
159
+
160
+ transaction = last_transaction
161
+ transaction_hash = transaction.to_h
162
+ expect(transaction_hash).to include(
163
+ "sample_data" => hash_including(
164
+ "tags" => { "queue" => queue, "priority" => 10 }
165
+ )
166
+ )
167
+ end
168
+ end
169
+ end
170
+
171
+ context "with error" do
172
+ it "reports the error on the transaction from the ActiveRecord integration" do
173
+ allow(Appsignal).to receive(:increment_counter) # Other calls we're testing in another test
174
+ tags = { :queue => queue }
175
+ expect(Appsignal).to receive(:increment_counter)
176
+ .with("active_job_queue_job_count", 1, tags.merge(:status => :failed))
177
+ expect(Appsignal).to receive(:increment_counter)
178
+ .with("active_job_queue_job_count", 1, tags.merge(:status => :processed))
179
+
180
+ expect do
181
+ perform_job(ActiveJobErrorTestJob)
182
+ end.to raise_error(RuntimeError, "uh oh")
183
+
184
+ transaction = last_transaction
185
+ transaction_hash = transaction.to_h
186
+ expect(transaction_hash).to include(
187
+ "action" => "ActiveJobErrorTestJob#perform",
188
+ "error" => {
189
+ "name" => "RuntimeError",
190
+ "message" => "uh oh",
191
+ "backtrace" => kind_of(String)
192
+ },
193
+ "namespace" => namespace,
194
+ "metadata" => {},
195
+ "sample_data" => hash_including(
196
+ "params" => [],
197
+ "tags" => { "queue" => queue }
198
+ )
199
+ )
200
+ events = transaction_hash["events"]
201
+ .sort_by { |e| e["start"] }
202
+ .map { |event| event["name"] }
203
+ expect(events).to eq(["perform_start.active_job", "perform.active_job"])
204
+ end
205
+ end
206
+
207
+ context "when wrapped in another transaction" do
208
+ it "does not create a new transaction or close the currently open one" do
209
+ current_transaction = background_job_transaction
210
+ allow(current_transaction).to receive(:complete).and_call_original
211
+ set_current_transaction current_transaction
212
+
213
+ perform_job(ActiveJobTestJob)
214
+
215
+ expect(created_transactions.count).to eql(1)
216
+ expect(current_transaction).to_not have_received(:complete)
217
+ current_transaction.complete
218
+
219
+ transaction = current_transaction
220
+ transaction_hash = transaction.to_h
221
+ # It does set data on the transaction
222
+ expect(transaction_hash).to include(
223
+ "id" => current_transaction.transaction_id,
224
+ "action" => "ActiveJobTestJob#perform",
225
+ "error" => nil,
226
+ "namespace" => namespace,
227
+ "metadata" => {},
228
+ "sample_data" => hash_including(
229
+ "params" => [],
230
+ "tags" => { "queue" => queue }
231
+ )
232
+ )
233
+ events = transaction_hash["events"]
234
+ .reject { |e| e["name"] == "enqueue.active_job" }
235
+ .sort_by { |e| e["start"] }
236
+ .map { |event| event["name"] }
237
+ expect(events).to eq(["perform_start.active_job", "perform.active_job"])
238
+ end
239
+ end
240
+
241
+ context "with params" do
242
+ it "filters the configured params" do
243
+ Appsignal.config = project_fixture_config("production")
244
+ Appsignal.config[:filter_parameters] = ["foo"]
245
+ perform_job(ActiveJobTestJob, method_given_args)
246
+
247
+ transaction = last_transaction
248
+ transaction_hash = transaction.to_h
249
+ expect(transaction_hash["sample_data"]["params"]).to include(
250
+ [
251
+ "foo",
252
+ {
253
+ "_aj_symbol_keys" => ["foo"],
254
+ "foo" => "[FILTERED]",
255
+ "bar" => "Bar",
256
+ "baz" => { "_aj_symbol_keys" => [], "1" => "foo" }
257
+ }
258
+ ]
259
+ )
260
+ end
261
+ end
262
+
263
+ context "with provider_job_id", :skip => DependencyHelper.rails_version < Gem::Version.new("5.0.0") do
264
+ before do
265
+ module ActiveJob
266
+ module QueueAdapters
267
+ # Adapter used in our test suite to add provider data to the job
268
+ # data, as is done by Rails provided ActiveJob adapters.
269
+ #
270
+ # This implementation is based on the
271
+ # `ActiveJob::QueueAdapters::InlineAdapter`.
272
+ class AppsignalTestAdapter < InlineAdapter
273
+ def enqueue(job)
274
+ Base.execute(job.serialize.merge("provider_job_id" => "my_provider_job_id"))
275
+ end
276
+ end
277
+ end
278
+ end
279
+
280
+ class ProviderWrappedActiveJobTestJob < ActiveJob::Base
281
+ self.queue_adapter = :appsignal_test
282
+
283
+ def perform(*_args)
284
+ end
285
+ end
286
+ end
287
+ after do
288
+ ActiveJob::QueueAdapters.send(:remove_const, :AppsignalTestAdapter)
289
+ Object.send(:remove_const, :ProviderWrappedActiveJobTestJob)
290
+ end
291
+
292
+ it "sets provider_job_id as tag" do
293
+ perform_job(ProviderWrappedActiveJobTestJob)
294
+
295
+ transaction = last_transaction
296
+ transaction_hash = transaction.to_h
297
+ expect(transaction_hash["sample_data"]["tags"]).to include(
298
+ "provider_job_id" => "my_provider_job_id"
299
+ )
300
+ end
301
+ end
302
+
303
+ context "with enqueued_at", :skip => DependencyHelper.rails_version < Gem::Version.new("6.0.0") do
304
+ before do
305
+ module ActiveJob
306
+ module QueueAdapters
307
+ # Adapter used in our test suite to add provider data to the job
308
+ # data, as is done by Rails provided ActiveJob adapters.
309
+ #
310
+ # This implementation is based on the
311
+ # `ActiveJob::QueueAdapters::InlineAdapter`.
312
+ class AppsignalTestAdapter < InlineAdapter
313
+ def enqueue(job)
314
+ Base.execute(job.serialize.merge("enqueued_at" => "2020-10-10T10:10:10Z"))
315
+ end
316
+ end
317
+ end
318
+ end
319
+
320
+ class ProviderWrappedActiveJobTestJob < ActiveJob::Base
321
+ self.queue_adapter = :appsignal_test
322
+
323
+ def perform(*_args)
324
+ end
325
+ end
326
+ end
327
+ after do
328
+ ActiveJob::QueueAdapters.send(:remove_const, :AppsignalTestAdapter)
329
+ Object.send(:remove_const, :ProviderWrappedActiveJobTestJob)
330
+ end
331
+
332
+ it "sets queue time on transaction" do
333
+ allow_any_instance_of(Appsignal::Transaction).to receive(:set_queue_start).and_call_original
334
+ perform_job(ProviderWrappedActiveJobTestJob)
335
+
336
+ transaction = last_transaction
337
+ queue_time = Time.parse("2020-10-10T10:10:10Z")
338
+ expect(transaction).to have_received(:set_queue_start)
339
+ .with((queue_time.to_f * 1_000).to_i)
340
+ end
341
+ end
342
+
343
+ context "with ActionMailer job" do
344
+ include ActionMailerHelpers
345
+
346
+ before do
347
+ class ActionMailerTestJob < ActionMailer::Base
348
+ def welcome(_first_arg = nil, _second_arg = nil)
349
+ end
350
+ end
351
+ end
352
+ after do
353
+ Object.send(:remove_const, :ActionMailerTestJob)
354
+ end
355
+
356
+ context "without params" do
357
+ it "sets the Action mailer data on the transaction" do
358
+ perform_mailer(ActionMailerTestJob, :welcome)
359
+
360
+ transaction = last_transaction
361
+ transaction_hash = transaction.to_h
362
+ expect(transaction_hash).to include(
363
+ "action" => "ActionMailerTestJob#welcome",
364
+ "sample_data" => hash_including(
365
+ "params" => ["ActionMailerTestJob", "welcome", "deliver_now"],
366
+ "tags" => { "queue" => "mailers" }
367
+ )
368
+ )
369
+ end
370
+ end
371
+
372
+ context "with multiple arguments" do
373
+ it "sets the arguments on the transaction" do
374
+ perform_mailer(ActionMailerTestJob, :welcome, method_given_args)
375
+
376
+ transaction = last_transaction
377
+ transaction_hash = transaction.to_h
378
+ expect(transaction_hash).to include(
379
+ "action" => "ActionMailerTestJob#welcome",
380
+ "sample_data" => hash_including(
381
+ "params" => ["ActionMailerTestJob", "welcome", "deliver_now"] + method_expected_args,
382
+ "tags" => { "queue" => "mailers" }
383
+ )
384
+ )
385
+ end
386
+ end
387
+
388
+ if DependencyHelper.rails_version >= Gem::Version.new("5.2.0")
389
+ context "with parameterized arguments" do
390
+ it "sets the arguments on the transaction" do
391
+ perform_mailer(ActionMailerTestJob, :welcome, parameterized_given_args)
392
+
393
+ transaction = last_transaction
394
+ transaction_hash = transaction.to_h
395
+ expect(transaction_hash).to include(
396
+ "action" => "ActionMailerTestJob#welcome",
397
+ "sample_data" => hash_including(
398
+ "params" => ["ActionMailerTestJob", "welcome", "deliver_now", parameterized_expected_args],
399
+ "tags" => { "queue" => "mailers" }
400
+ )
401
+ )
402
+ end
403
+ end
404
+ end
405
+ end
406
+
407
+ if DependencyHelper.rails_version >= Gem::Version.new("6.0.0")
408
+ context "with ActionMailer MailDeliveryJob job" do
409
+ include ActionMailerHelpers
410
+
411
+ before do
412
+ class ActionMailerTestMailDeliveryJob < ActionMailer::Base
413
+ self.delivery_job = ActionMailer::MailDeliveryJob
414
+
415
+ def welcome(*_args)
416
+ end
417
+ end
418
+ end
419
+ after do
420
+ Object.send(:remove_const, :ActionMailerTestMailDeliveryJob)
421
+ end
422
+
423
+ it "sets the Action mailer data on the transaction" do
424
+ perform_mailer(ActionMailerTestMailDeliveryJob, :welcome)
425
+
426
+ transaction = last_transaction
427
+ transaction_hash = transaction.to_h
428
+ expect(transaction_hash).to include(
429
+ "action" => "ActionMailerTestMailDeliveryJob#welcome",
430
+ "sample_data" => hash_including(
431
+ "params" => [
432
+ "ActionMailerTestMailDeliveryJob",
433
+ "welcome",
434
+ "deliver_now",
435
+ { active_job_internal_key => ["args"], "args" => [] }
436
+ ],
437
+ "tags" => { "queue" => "mailers" }
438
+ )
439
+ )
440
+ end
441
+
442
+ context "with method arguments" do
443
+ it "sets the Action mailer data on the transaction" do
444
+ perform_mailer(ActionMailerTestMailDeliveryJob, :welcome, method_given_args)
445
+
446
+ transaction = last_transaction
447
+ transaction_hash = transaction.to_h
448
+ expect(transaction_hash).to include(
449
+ "action" => "ActionMailerTestMailDeliveryJob#welcome",
450
+ "sample_data" => hash_including(
451
+ "params" => [
452
+ "ActionMailerTestMailDeliveryJob",
453
+ "welcome",
454
+ "deliver_now",
455
+ {
456
+ active_job_internal_key => ["args"],
457
+ "args" => method_expected_args
458
+ }
459
+ ],
460
+ "tags" => { "queue" => "mailers" }
461
+ )
462
+ )
463
+ end
464
+ end
465
+
466
+ context "with parameterized arguments" do
467
+ it "sets the Action mailer data on the transaction" do
468
+ perform_mailer(ActionMailerTestMailDeliveryJob, :welcome, parameterized_given_args)
469
+
470
+ transaction = last_transaction
471
+ transaction_hash = transaction.to_h
472
+ expect(transaction_hash).to include(
473
+ "action" => "ActionMailerTestMailDeliveryJob#welcome",
474
+ "sample_data" => hash_including(
475
+ "params" => [
476
+ "ActionMailerTestMailDeliveryJob",
477
+ "welcome",
478
+ "deliver_now",
479
+ {
480
+ active_job_internal_key => ["params", "args"],
481
+ "args" => [],
482
+ "params" => parameterized_expected_args
483
+ }
484
+ ],
485
+ "tags" => { "queue" => "mailers" }
486
+ )
487
+ )
488
+ end
489
+ end
490
+ end
491
+ end
492
+
493
+ def perform_active_job
494
+ Timecop.freeze(time) do
495
+ yield
496
+ end
497
+ end
498
+
499
+ def perform_job(job_class, args = nil)
500
+ perform_active_job do
501
+ if args
502
+ job_class.perform_later(args)
503
+ else
504
+ job_class.perform_later
505
+ end
506
+ end
507
+ end
508
+
509
+ def perform_mailer(mailer, method, args = nil)
510
+ perform_active_job { perform_action_mailer(mailer, method, args) }
511
+ end
512
+
513
+ def active_job_internal_key
514
+ if DependencyHelper.ruby_version >= Gem::Version.new("2.7.0")
515
+ "_aj_ruby2_keywords"
516
+ else
517
+ "_aj_symbol_keys"
518
+ end
519
+ end
520
+ end
521
+ end