appsignal 2.10.8 → 2.11.0.beta.1

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