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

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 (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