appsignal 2.10.10 → 2.11.0.beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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