appsignal 2.11.0.alpha.2-java → 2.11.0.beta.1-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/.semaphore/semaphore.yml +37 -9
  4. data/CHANGELOG.md +10 -1
  5. data/build_matrix.yml +5 -1
  6. data/gemfiles/rails-4.2.gemfile +9 -2
  7. data/gemfiles/rails-5.0.gemfile +1 -0
  8. data/gemfiles/rails-5.1.gemfile +1 -0
  9. data/gemfiles/rails-5.2.gemfile +1 -0
  10. data/gemfiles/rails-6.0.gemfile +1 -0
  11. data/gemfiles/resque-1.gemfile +7 -0
  12. data/gemfiles/{resque.gemfile → resque-2.gemfile} +1 -1
  13. data/lib/appsignal/hooks.rb +2 -0
  14. data/lib/appsignal/hooks/active_job.rb +89 -0
  15. data/lib/appsignal/hooks/resque.rb +60 -0
  16. data/lib/appsignal/hooks/sidekiq.rb +16 -92
  17. data/lib/appsignal/integrations/que.rb +1 -1
  18. data/lib/appsignal/integrations/resque.rb +9 -12
  19. data/lib/appsignal/integrations/resque_active_job.rb +9 -32
  20. data/lib/appsignal/transaction.rb +10 -0
  21. data/lib/appsignal/utils/deprecation_message.rb +5 -1
  22. data/lib/appsignal/version.rb +1 -1
  23. data/spec/lib/appsignal/hooks/activejob_spec.rb +458 -0
  24. data/spec/lib/appsignal/hooks/resque_spec.rb +185 -0
  25. data/spec/lib/appsignal/hooks/sidekiq_spec.rb +215 -263
  26. data/spec/lib/appsignal/integrations/que_spec.rb +25 -6
  27. data/spec/lib/appsignal/integrations/resque_active_job_spec.rb +20 -179
  28. data/spec/lib/appsignal/integrations/resque_spec.rb +20 -85
  29. data/spec/lib/appsignal/probes/sidekiq_spec.rb +10 -7
  30. data/spec/lib/appsignal/transaction_spec.rb +5 -7
  31. data/spec/support/helpers/action_mailer_helpers.rb +25 -0
  32. data/spec/support/helpers/dependency_helper.rb +9 -2
  33. data/spec/support/helpers/transaction_helpers.rb +6 -0
  34. data/spec/support/stubs/sidekiq/api.rb +1 -1
  35. metadata +12 -3
@@ -32,7 +32,7 @@ module Appsignal
32
32
  transaction.set_error(error)
33
33
  raise error
34
34
  ensure
35
- transaction.set_action "#{local_attrs[:job_class]}#run"
35
+ transaction.set_action_if_nil "#{local_attrs[:job_class]}#run"
36
36
  Appsignal::Transaction.complete_current!
37
37
  end
38
38
  end
@@ -4,18 +4,15 @@ module Appsignal
4
4
  module Integrations
5
5
  # @api private
6
6
  module ResquePlugin
7
- # Do not use this file as a template for your own background processor
8
- # Resque is an exception to the rule and the code below causes the
9
- # extension to shut itself down after a single job.
10
- # see http://docs.appsignal.com/background-monitoring/custom.html
11
- def around_perform_resque_plugin(*_args)
12
- Appsignal.monitor_single_transaction(
13
- "perform_job.resque",
14
- :class => to_s,
15
- :method => "perform"
16
- ) do
17
- yield
18
- end
7
+ def self.extended(_)
8
+ callers = caller
9
+ Appsignal::Utils::DeprecationMessage.message \
10
+ "The AppSignal ResquePlugin is deprecated and does " \
11
+ "nothing on extend. In this version of the AppSignal Ruby gem " \
12
+ "the integration with Resque is automatic on all Resque workers. " \
13
+ "Please remove the following line from this file to remove this " \
14
+ "message: extend Appsignal::Integrations::ResquePlugin\n" \
15
+ "#{callers.first}"
19
16
  end
20
17
  end
21
18
  end
@@ -4,38 +4,15 @@ module Appsignal
4
4
  module Integrations
5
5
  # @api private
6
6
  module ResqueActiveJobPlugin
7
- include Appsignal::Hooks::Helpers
8
-
9
- def self.included(base)
10
- base.class_eval do
11
- around_perform do |job, block|
12
- params = Appsignal::Utils::HashSanitizer.sanitize(
13
- job.arguments,
14
- Appsignal.config[:filter_parameters]
15
- )
16
-
17
- queue_start =
18
- if job.respond_to?(:enqueued_at) && job.enqueued_at
19
- Time.parse(job.enqueued_at).utc
20
- end
21
-
22
- Appsignal.monitor_single_transaction(
23
- "perform_job.resque",
24
- :class => job.class.to_s,
25
- :method => "perform",
26
- :params => params,
27
- :queue_start => queue_start,
28
- :metadata => {
29
- :id => job.job_id,
30
- :queue => job.queue_name
31
- }
32
- ) do
33
- block.call
34
- end
35
- end
36
- end
37
-
38
- Appsignal::Environment.report("ruby_active_job_resque_enabled") { true }
7
+ def self.included(_)
8
+ callers = caller
9
+ Appsignal::Utils::DeprecationMessage.message \
10
+ "The AppSignal ResqueActiveJobPlugin is deprecated and does " \
11
+ "nothing on extend. In this version of the AppSignal Ruby gem " \
12
+ "the integration with Resque is automatic on all Resque workers. " \
13
+ "Please remove the following line from this file to remove this " \
14
+ "message: include Appsignal::Integrations::ResqueActiveJobPlugin\n" \
15
+ "#{callers.first}"
39
16
  end
40
17
  end
41
18
  end
@@ -221,6 +221,16 @@ module Appsignal
221
221
  set_action_if_nil(group_and_action.compact.join("#"))
222
222
  end
223
223
 
224
+ # Set queue start time for transaction.
225
+ #
226
+ # Most commononly called by {set_http_or_background_queue_start}.
227
+ #
228
+ # @param start [Integer] Queue start time in milliseconds.
229
+ # @raise [RangeError] When the queue start time value is too big, this
230
+ # method raises a RangeError.
231
+ # @raise [TypeError] Raises a TypeError when the given `start` argument is
232
+ # not an Integer.
233
+ # @return [void]
224
234
  def set_queue_start(start)
225
235
  return unless start
226
236
  @ext.set_queue_start(start)
@@ -1,10 +1,14 @@
1
1
  module Appsignal
2
2
  module Utils
3
3
  module DeprecationMessage
4
- def deprecation_message(message, logger = Appsignal.logger)
4
+ def self.message(message, logger = Appsignal.logger)
5
5
  $stderr.puts "appsignal WARNING: #{message}"
6
6
  logger.warn message
7
7
  end
8
+
9
+ def deprecation_message(message, logger = Appsignal.logger)
10
+ Appsignal::Utils::DeprecationMessage.message(message, logger)
11
+ end
8
12
  end
9
13
  end
10
14
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Appsignal
4
- VERSION = "2.11.0.alpha.2".freeze
4
+ VERSION = "2.11.0.beta.1".freeze
5
5
  end
@@ -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