appsignal 2.11.0.alpha.2-java → 2.11.0.beta.5-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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/.semaphore/semaphore.yml +94 -10
  4. data/CHANGELOG.md +31 -1
  5. data/README.md +4 -4
  6. data/Rakefile +16 -4
  7. data/appsignal.gemspec +1 -1
  8. data/build_matrix.yml +7 -3
  9. data/ext/Rakefile +2 -0
  10. data/ext/agent.yml +19 -19
  11. data/ext/base.rb +7 -0
  12. data/ext/extconf.rb +2 -0
  13. data/gemfiles/rails-4.2.gemfile +9 -2
  14. data/gemfiles/rails-5.0.gemfile +1 -0
  15. data/gemfiles/rails-5.1.gemfile +1 -0
  16. data/gemfiles/rails-5.2.gemfile +1 -0
  17. data/gemfiles/rails-6.0.gemfile +1 -0
  18. data/gemfiles/resque-1.gemfile +7 -0
  19. data/gemfiles/{resque.gemfile → resque-2.gemfile} +1 -1
  20. data/lib/appsignal.rb +1 -0
  21. data/lib/appsignal/auth_check.rb +4 -2
  22. data/lib/appsignal/cli/diagnose.rb +1 -1
  23. data/lib/appsignal/config.rb +35 -2
  24. data/lib/appsignal/extension.rb +6 -5
  25. data/lib/appsignal/extension/jruby.rb +6 -5
  26. data/lib/appsignal/hooks.rb +25 -0
  27. data/lib/appsignal/hooks/active_job.rb +137 -0
  28. data/lib/appsignal/hooks/puma.rb +0 -1
  29. data/lib/appsignal/hooks/resque.rb +60 -0
  30. data/lib/appsignal/hooks/sidekiq.rb +17 -94
  31. data/lib/appsignal/integrations/delayed_job_plugin.rb +1 -1
  32. data/lib/appsignal/integrations/que.rb +1 -1
  33. data/lib/appsignal/integrations/resque.rb +9 -12
  34. data/lib/appsignal/integrations/resque_active_job.rb +9 -32
  35. data/lib/appsignal/probes.rb +7 -0
  36. data/lib/appsignal/probes/puma.rb +1 -1
  37. data/lib/appsignal/probes/sidekiq.rb +3 -1
  38. data/lib/appsignal/transaction.rb +10 -0
  39. data/lib/appsignal/utils/deprecation_message.rb +6 -2
  40. data/lib/appsignal/version.rb +1 -1
  41. data/spec/lib/appsignal/auth_check_spec.rb +23 -0
  42. data/spec/lib/appsignal/capistrano2_spec.rb +1 -1
  43. data/spec/lib/appsignal/capistrano3_spec.rb +1 -1
  44. data/spec/lib/appsignal/cli/diagnose_spec.rb +42 -0
  45. data/spec/lib/appsignal/config_spec.rb +21 -0
  46. data/spec/lib/appsignal/extension/jruby_spec.rb +31 -28
  47. data/spec/lib/appsignal/extension_install_failure_spec.rb +23 -0
  48. data/spec/lib/appsignal/hooks/activejob_spec.rb +591 -0
  49. data/spec/lib/appsignal/hooks/delayed_job_spec.rb +3 -14
  50. data/spec/lib/appsignal/hooks/resque_spec.rb +185 -0
  51. data/spec/lib/appsignal/hooks/sidekiq_spec.rb +222 -268
  52. data/spec/lib/appsignal/hooks_spec.rb +57 -0
  53. data/spec/lib/appsignal/integrations/que_spec.rb +25 -6
  54. data/spec/lib/appsignal/integrations/resque_active_job_spec.rb +20 -179
  55. data/spec/lib/appsignal/integrations/resque_spec.rb +20 -85
  56. data/spec/lib/appsignal/marker_spec.rb +1 -1
  57. data/spec/lib/appsignal/probes/sidekiq_spec.rb +10 -7
  58. data/spec/lib/appsignal/transaction_spec.rb +5 -7
  59. data/spec/spec_helper.rb +5 -0
  60. data/spec/support/helpers/action_mailer_helpers.rb +25 -0
  61. data/spec/support/helpers/config_helpers.rb +3 -2
  62. data/spec/support/helpers/dependency_helper.rb +9 -2
  63. data/spec/support/helpers/transaction_helpers.rb +6 -0
  64. data/spec/support/stubs/sidekiq/api.rb +1 -1
  65. data/spec/support/testing.rb +19 -19
  66. metadata +16 -4
@@ -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
@@ -0,0 +1,7 @@
1
+ module Appsignal
2
+ module Probes
3
+ end
4
+ end
5
+
6
+ require "appsignal/probes/puma"
7
+ require "appsignal/probes/sidekiq"
@@ -1,11 +1,11 @@
1
1
  module Appsignal
2
2
  module Probes
3
- # @api private
4
3
  class PumaProbe
5
4
  def initialize
6
5
  @hostname = Appsignal.config[:hostname] || Socket.gethostname
7
6
  end
8
7
 
8
+ # @api private
9
9
  def call
10
10
  puma_stats = fetch_puma_stats
11
11
  return unless puma_stats
@@ -1,9 +1,10 @@
1
1
  module Appsignal
2
2
  module Probes
3
- # @api private
4
3
  class SidekiqProbe
4
+ # @api private
5
5
  attr_reader :config
6
6
 
7
+ # @api private
7
8
  def self.dependencies_present?
8
9
  Gem::Version.new(::Redis::VERSION) >= Gem::Version.new("3.3.5")
9
10
  end
@@ -16,6 +17,7 @@ module Appsignal
16
17
  require "sidekiq/api"
17
18
  end
18
19
 
20
+ # @api private
19
21
  def call
20
22
  track_redis_info
21
23
  track_stats
@@ -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)
5
- $stderr.puts "appsignal WARNING: #{message}"
4
+ def self.message(message, logger = Appsignal.logger)
5
+ Kernel.warn "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.5".freeze
5
5
  end
@@ -28,6 +28,29 @@ describe Appsignal::AuthCheck do
28
28
  end.join("&")
29
29
  end
30
30
 
31
+ describe ".new" do
32
+ describe "with logger argument" do
33
+ let(:err_stream) { std_stream }
34
+ let(:stderr) { err_stream.read }
35
+ let(:log_stream) { std_stream }
36
+ let(:log) { log_contents(log_stream) }
37
+
38
+ it "logs and prints a deprecation message" do
39
+ Appsignal.logger = test_logger(log_stream)
40
+
41
+ capture_std_streams(std_stream, err_stream) do
42
+ Appsignal::AuthCheck.new(config, Appsignal.logger)
43
+ end
44
+
45
+ deprecation_message =
46
+ "`Appsignal::AuthCheck.new`'s `logger` argument " \
47
+ "will be removed in the next major version."
48
+ expect(stderr).to include "appsignal WARNING: #{deprecation_message}"
49
+ expect(log).to contains_log :warn, deprecation_message
50
+ end
51
+ end
52
+ end
53
+
31
54
  describe "#perform" do
32
55
  subject { auth_check.perform }
33
56
 
@@ -10,7 +10,7 @@ if DependencyHelper.capistrano2_present?
10
10
  let(:capistrano_config) do
11
11
  Capistrano::Configuration.new.tap do |c|
12
12
  c.set(:rails_env, "production")
13
- c.set(:repository, "master")
13
+ c.set(:repository, "main")
14
14
  c.set(:deploy_to, "/home/username/app")
15
15
  c.set(:current_release, "")
16
16
  c.set(:current_revision, "503ce0923ed177a3ce000005")
@@ -15,7 +15,7 @@ if DependencyHelper.capistrano3_present?
15
15
  c.set(:log_level, :error)
16
16
  c.set(:logger, logger)
17
17
  c.set(:rails_env, "production")
18
- c.set(:repository, "master")
18
+ c.set(:repository, "main")
19
19
  c.set(:deploy_to, "/home/username/app")
20
20
  c.set(:current_release, "")
21
21
  c.set(:current_revision, "503ce0923ed177a3ce000005")
@@ -290,6 +290,8 @@ describe Appsignal::CLI::Diagnose, :api_stub => true, :send_report => :yes_cli_i
290
290
  jruby = Appsignal::System.jruby?
291
291
  expect(output).to include(
292
292
  "Extension installation report",
293
+ "Installation result",
294
+ " Status: success",
293
295
  "Language details",
294
296
  " Implementation: #{jruby ? "jruby" : "ruby"}",
295
297
  " Ruby version: #{"#{rbconfig["ruby_version"]}-p#{rbconfig["PATCHLEVEL"]}"}",
@@ -310,6 +312,46 @@ describe Appsignal::CLI::Diagnose, :api_stub => true, :send_report => :yes_cli_i
310
312
  )
311
313
  end
312
314
 
315
+ context "with error in install report" do
316
+ let(:error) { RuntimeError.new("some error") }
317
+ before do
318
+ allow(File).to receive(:read).and_call_original
319
+ expect(File).to receive(:read)
320
+ .with(File.expand_path("../../../../../ext/install.report", __FILE__))
321
+ .and_return(
322
+ YAML.dump(
323
+ "result" => {
324
+ "status" => "error",
325
+ "error" => "RuntimeError: some error",
326
+ "backtrace" => error.backtrace
327
+ }
328
+ )
329
+ )
330
+ end
331
+
332
+ it "sends an error" do
333
+ run
334
+ expect(received_report["installation"]).to match(
335
+ "result" => {
336
+ "status" => "error",
337
+ "error" => "RuntimeError: some error",
338
+ "backtrace" => error.backtrace
339
+ }
340
+ )
341
+ end
342
+
343
+ it "prints the error" do
344
+ run
345
+
346
+ expect(output).to include(
347
+ "Extension installation report",
348
+ "Installation result",
349
+ "Status: error\n Error: RuntimeError: some error"
350
+ )
351
+ expect(output).to_not include("Raw report:")
352
+ end
353
+ end
354
+
313
355
  context "without install report" do
314
356
  let(:error) { RuntimeError.new("foo") }
315
357
  before do
@@ -244,6 +244,27 @@ describe Appsignal::Config do
244
244
  end
245
245
  end
246
246
 
247
+ context "with an overriden config file" do
248
+ let(:config) do
249
+ project_fixture_config("production", {}, Appsignal.logger, File.join(project_fixture_path, "config", "appsignal.yml"))
250
+ end
251
+
252
+ it "is valid and active" do
253
+ expect(config.valid?).to be_truthy
254
+ expect(config.active?).to be_truthy
255
+ end
256
+
257
+ context "with an invalid overriden config file" do
258
+ let(:config) do
259
+ project_fixture_config("production", {}, Appsignal.logger, File.join(project_fixture_path, "config", "missing.yml"))
260
+ end
261
+
262
+ it "is not valid" do
263
+ expect(config.valid?).to be_falsy
264
+ end
265
+ end
266
+ end
267
+
247
268
  context "with the config file causing an error" do
248
269
  let(:config_path) do
249
270
  File.expand_path(
@@ -1,42 +1,45 @@
1
- if Appsignal::System.jruby?
2
- describe Appsignal::Extension::Jruby do
3
- let(:extension) { Appsignal::Extension }
1
+ describe "JRuby extension", :jruby do
2
+ let(:extension) { Appsignal::Extension }
3
+ let(:jruby_module) { Appsignal::Extension::Jruby }
4
4
 
5
- describe "string conversions" do
6
- it "keeps the same value during string type conversions" do
7
- # UTF-8 string with NULL
8
- # Tests if the conversions between the conversions without breaking on
9
- # NULL terminated strings in C.
10
- string = "Merry Christmas! \u0000 🎄"
5
+ it "creates a JRuby extension module" do
6
+ expect(Appsignal::Extension::Jruby).to be_kind_of(Module)
7
+ end
11
8
 
12
- appsignal_string = extension.make_appsignal_string(string)
13
- ruby_string = extension.make_ruby_string(appsignal_string)
9
+ describe "string conversions" do
10
+ it "keeps the same value during string type conversions" do
11
+ # UTF-8 string with NULL
12
+ # Tests if the conversions between the conversions without breaking on
13
+ # NULL terminated strings in C.
14
+ string = "Merry Christmas! \u0000 🎄"
14
15
 
15
- expect(ruby_string).to eq("Merry Christmas! \u0000 🎄")
16
- end
17
- end
16
+ appsignal_string = extension.make_appsignal_string(string)
17
+ ruby_string = extension.make_ruby_string(appsignal_string)
18
18
 
19
- it "loads libappsignal with FFI" do
20
- expect(described_class.ffi_libraries.map(&:name).first).to include "libappsignal"
19
+ expect(ruby_string).to eq("Merry Christmas! \u0000 🎄")
21
20
  end
21
+ end
22
22
 
23
- describe ".lib_extension" do
24
- subject { described_class.lib_extension }
23
+ it "loads libappsignal with FFI" do
24
+ expect(jruby_module.ffi_libraries.map(&:name).first).to include "libappsignal"
25
+ end
26
+
27
+ describe ".lib_extension" do
28
+ subject { jruby_module.lib_extension }
25
29
 
26
- context "when on a darwin system" do
27
- before { expect(Appsignal::System).to receive(:agent_platform).and_return("darwin") }
30
+ context "when on a darwin system" do
31
+ before { expect(Appsignal::System).to receive(:agent_platform).and_return("darwin") }
28
32
 
29
- it "returns the extension for darwin" do
30
- is_expected.to eq "dylib"
31
- end
33
+ it "returns the extension for darwin" do
34
+ is_expected.to eq "dylib"
32
35
  end
36
+ end
33
37
 
34
- context "when on a linux system" do
35
- before { expect(Appsignal::System).to receive(:agent_platform).and_return("linux") }
38
+ context "when on a linux system" do
39
+ before { expect(Appsignal::System).to receive(:agent_platform).and_return("linux") }
36
40
 
37
- it "returns the lib extension for linux" do
38
- is_expected.to eq "so"
39
- end
41
+ it "returns the lib extension for linux" do
42
+ is_expected.to eq "so"
40
43
  end
41
44
  end
42
45
  end
@@ -0,0 +1,23 @@
1
+ describe Appsignal::Extension, :extension_installation_failure do
2
+ context "when the extension library cannot be loaded" do
3
+ # This test breaks the installation on purpose and is not run by default.
4
+ # See `rake test:failure`. If this test was run, run `rake
5
+ # extension:install` again to fix the extension installation.
6
+ it "prints and logs an error" do
7
+ # ENV var to make sure installation fails on purpurse
8
+ ENV["_TEST_APPSIGNAL_EXTENSION_FAILURE"] = "true"
9
+ `rake extension:install` # Run installation
10
+
11
+ require "open3"
12
+ _stdout, stderr, _status = Open3.capture3("bin/appsignal --version")
13
+ expect(stderr).to include("ERROR: AppSignal failed to load extension")
14
+ error_message =
15
+ if DependencyHelper.running_jruby?
16
+ "cannot open shared object file"
17
+ else
18
+ "LoadError: cannot load such file"
19
+ end
20
+ expect(stderr).to include(error_message)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,591 @@
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" => {
114
+ "active_job_id" => kind_of(String),
115
+ "queue" => queue
116
+ }
117
+ )
118
+ )
119
+ events = transaction_hash["events"]
120
+ .sort_by { |e| e["start"] }
121
+ .map { |event| event["name"] }
122
+ expect(events).to eq(["perform_start.active_job", "perform.active_job"])
123
+ end
124
+
125
+ context "with custom queue" do
126
+ it "reports the custom queue as tag on the transaction" do
127
+ tags = { :queue => "custom_queue" }
128
+ expect(Appsignal).to receive(:increment_counter)
129
+ .with("active_job_queue_job_count", 1, tags.merge(:status => :processed))
130
+ perform_job(ActiveJobCustomQueueTestJob)
131
+
132
+ transaction = last_transaction
133
+ transaction_hash = transaction.to_h
134
+ expect(transaction_hash).to include(
135
+ "sample_data" => hash_including(
136
+ "tags" => hash_including("queue" => "custom_queue")
137
+ )
138
+ )
139
+ end
140
+ end
141
+
142
+ if DependencyHelper.rails_version >= Gem::Version.new("5.0.0")
143
+ context "with priority" do
144
+ before do
145
+ class ActiveJobPriorityTestJob < ActiveJob::Base
146
+ queue_with_priority 10
147
+
148
+ def perform(*_args)
149
+ end
150
+ end
151
+ end
152
+ after do
153
+ Object.send(:remove_const, :ActiveJobPriorityTestJob)
154
+ end
155
+
156
+ it "reports the priority as tag on the transaction" do
157
+ tags = { :queue => queue }
158
+ expect(Appsignal).to receive(:increment_counter)
159
+ .with("active_job_queue_job_count", 1, tags.merge(:status => :processed))
160
+ expect(Appsignal).to receive(:increment_counter)
161
+ .with("active_job_queue_priority_job_count", 1, tags.merge(:priority => 10, :status => :processed))
162
+
163
+ perform_job(ActiveJobPriorityTestJob)
164
+
165
+ transaction = last_transaction
166
+ transaction_hash = transaction.to_h
167
+ expect(transaction_hash).to include(
168
+ "sample_data" => hash_including(
169
+ "tags" => hash_including("queue" => queue, "priority" => 10)
170
+ )
171
+ )
172
+ end
173
+ end
174
+ end
175
+
176
+ context "with error" do
177
+ it "reports the error on the transaction from the ActiveRecord integration" do
178
+ allow(Appsignal).to receive(:increment_counter) # Other calls we're testing in another test
179
+ tags = { :queue => queue }
180
+ expect(Appsignal).to receive(:increment_counter)
181
+ .with("active_job_queue_job_count", 1, tags.merge(:status => :failed))
182
+ expect(Appsignal).to receive(:increment_counter)
183
+ .with("active_job_queue_job_count", 1, tags.merge(:status => :processed))
184
+
185
+ expect do
186
+ perform_job(ActiveJobErrorTestJob)
187
+ end.to raise_error(RuntimeError, "uh oh")
188
+
189
+ transaction = last_transaction
190
+ transaction_hash = transaction.to_h
191
+ expect(transaction_hash).to include(
192
+ "action" => "ActiveJobErrorTestJob#perform",
193
+ "error" => {
194
+ "name" => "RuntimeError",
195
+ "message" => "uh oh",
196
+ "backtrace" => kind_of(String)
197
+ },
198
+ "namespace" => namespace,
199
+ "metadata" => {},
200
+ "sample_data" => hash_including(
201
+ "params" => [],
202
+ "tags" => {
203
+ "active_job_id" => kind_of(String),
204
+ "queue" => queue
205
+ }
206
+ )
207
+ )
208
+ events = transaction_hash["events"]
209
+ .sort_by { |e| e["start"] }
210
+ .map { |event| event["name"] }
211
+ expect(events).to eq(["perform_start.active_job", "perform.active_job"])
212
+ end
213
+
214
+ if DependencyHelper.rails_version >= Gem::Version.new("5.0.0")
215
+ context "with priority" do
216
+ before do
217
+ class ActiveJobErrorPriorityTestJob < ActiveJob::Base
218
+ queue_with_priority 10
219
+
220
+ def perform(*_args)
221
+ raise "uh oh"
222
+ end
223
+ end
224
+ end
225
+ after do
226
+ Object.send(:remove_const, :ActiveJobErrorPriorityTestJob)
227
+ end
228
+
229
+ it "reports the priority as tag on the transaction" do
230
+ tags = { :queue => queue }
231
+ expect(Appsignal).to receive(:increment_counter)
232
+ .with("active_job_queue_job_count", 1, tags.merge(:status => :processed))
233
+ expect(Appsignal).to receive(:increment_counter)
234
+ .with("active_job_queue_job_count", 1, tags.merge(:status => :failed))
235
+ expect(Appsignal).to receive(:increment_counter)
236
+ .with("active_job_queue_priority_job_count", 1, tags.merge(:priority => 10, :status => :processed))
237
+ expect(Appsignal).to receive(:increment_counter)
238
+ .with("active_job_queue_priority_job_count", 1, tags.merge(:priority => 10, :status => :failed))
239
+
240
+ expect do
241
+ perform_job(ActiveJobErrorPriorityTestJob)
242
+ end.to raise_error(RuntimeError, "uh oh")
243
+
244
+ transaction = last_transaction
245
+ transaction_hash = transaction.to_h
246
+ expect(transaction_hash).to include(
247
+ "sample_data" => hash_including(
248
+ "tags" => hash_including("queue" => queue, "priority" => 10)
249
+ )
250
+ )
251
+ end
252
+ end
253
+ end
254
+ end
255
+
256
+ context "when wrapped in another transaction" do
257
+ it "does not create a new transaction or close the currently open one" do
258
+ current_transaction = background_job_transaction
259
+ allow(current_transaction).to receive(:complete).and_call_original
260
+ set_current_transaction current_transaction
261
+
262
+ perform_job(ActiveJobTestJob)
263
+
264
+ expect(created_transactions.count).to eql(1)
265
+ expect(current_transaction).to_not have_received(:complete)
266
+ current_transaction.complete
267
+
268
+ transaction = current_transaction
269
+ transaction_hash = transaction.to_h
270
+ # It does set data on the transaction
271
+ expect(transaction_hash).to include(
272
+ "id" => current_transaction.transaction_id,
273
+ "action" => "ActiveJobTestJob#perform",
274
+ "error" => nil,
275
+ "namespace" => namespace,
276
+ "metadata" => {},
277
+ "sample_data" => hash_including(
278
+ "params" => [],
279
+ "tags" => {
280
+ "active_job_id" => kind_of(String),
281
+ "queue" => queue
282
+ }
283
+ )
284
+ )
285
+ events = transaction_hash["events"]
286
+ .reject { |e| e["name"] == "enqueue.active_job" }
287
+ .sort_by { |e| e["start"] }
288
+ .map { |event| event["name"] }
289
+ expect(events).to eq(["perform_start.active_job", "perform.active_job"])
290
+ end
291
+ end
292
+
293
+ context "with params" do
294
+ it "filters the configured params" do
295
+ Appsignal.config = project_fixture_config("production")
296
+ Appsignal.config[:filter_parameters] = ["foo"]
297
+ perform_job(ActiveJobTestJob, method_given_args)
298
+
299
+ transaction = last_transaction
300
+ transaction_hash = transaction.to_h
301
+ expect(transaction_hash["sample_data"]["params"]).to include(
302
+ [
303
+ "foo",
304
+ {
305
+ "_aj_symbol_keys" => ["foo"],
306
+ "foo" => "[FILTERED]",
307
+ "bar" => "Bar",
308
+ "baz" => { "_aj_symbol_keys" => [], "1" => "foo" }
309
+ }
310
+ ]
311
+ )
312
+ end
313
+ end
314
+
315
+ context "with provider_job_id", :skip => DependencyHelper.rails_version < Gem::Version.new("5.0.0") do
316
+ before do
317
+ module ActiveJob
318
+ module QueueAdapters
319
+ # Adapter used in our test suite to add provider data to the job
320
+ # data, as is done by Rails provided ActiveJob adapters.
321
+ #
322
+ # This implementation is based on the
323
+ # `ActiveJob::QueueAdapters::InlineAdapter`.
324
+ class AppsignalTestAdapter < InlineAdapter
325
+ def enqueue(job)
326
+ Base.execute(job.serialize.merge("provider_job_id" => "my_provider_job_id"))
327
+ end
328
+ end
329
+ end
330
+ end
331
+
332
+ class ProviderWrappedActiveJobTestJob < ActiveJob::Base
333
+ self.queue_adapter = :appsignal_test
334
+
335
+ def perform(*_args)
336
+ end
337
+ end
338
+ end
339
+ after do
340
+ ActiveJob::QueueAdapters.send(:remove_const, :AppsignalTestAdapter)
341
+ Object.send(:remove_const, :ProviderWrappedActiveJobTestJob)
342
+ end
343
+
344
+ it "sets provider_job_id as tag" do
345
+ perform_job(ProviderWrappedActiveJobTestJob)
346
+
347
+ transaction = last_transaction
348
+ transaction_hash = transaction.to_h
349
+ expect(transaction_hash["sample_data"]["tags"]).to include(
350
+ "provider_job_id" => "my_provider_job_id"
351
+ )
352
+ end
353
+ end
354
+
355
+ context "with enqueued_at", :skip => DependencyHelper.rails_version < Gem::Version.new("6.0.0") do
356
+ before do
357
+ module ActiveJob
358
+ module QueueAdapters
359
+ # Adapter used in our test suite to add provider data to the job
360
+ # data, as is done by Rails provided ActiveJob adapters.
361
+ #
362
+ # This implementation is based on the
363
+ # `ActiveJob::QueueAdapters::InlineAdapter`.
364
+ class AppsignalTestAdapter < InlineAdapter
365
+ def enqueue(job)
366
+ Base.execute(job.serialize.merge("enqueued_at" => "2020-10-10T10:10:10Z"))
367
+ end
368
+ end
369
+ end
370
+ end
371
+
372
+ class ProviderWrappedActiveJobTestJob < ActiveJob::Base
373
+ self.queue_adapter = :appsignal_test
374
+
375
+ def perform(*_args)
376
+ end
377
+ end
378
+ end
379
+ after do
380
+ ActiveJob::QueueAdapters.send(:remove_const, :AppsignalTestAdapter)
381
+ Object.send(:remove_const, :ProviderWrappedActiveJobTestJob)
382
+ end
383
+
384
+ it "sets queue time on transaction" do
385
+ allow_any_instance_of(Appsignal::Transaction).to receive(:set_queue_start).and_call_original
386
+ perform_job(ProviderWrappedActiveJobTestJob)
387
+
388
+ transaction = last_transaction
389
+ queue_time = Time.parse("2020-10-10T10:10:10Z")
390
+ expect(transaction).to have_received(:set_queue_start)
391
+ .with((queue_time.to_f * 1_000).to_i)
392
+ end
393
+ end
394
+
395
+ context "with ActionMailer job" do
396
+ include ActionMailerHelpers
397
+
398
+ before do
399
+ class ActionMailerTestJob < ActionMailer::Base
400
+ def welcome(_first_arg = nil, _second_arg = nil)
401
+ end
402
+ end
403
+ end
404
+ after do
405
+ Object.send(:remove_const, :ActionMailerTestJob)
406
+ end
407
+
408
+ context "without params" do
409
+ it "sets the Action mailer data on the transaction" do
410
+ perform_mailer(ActionMailerTestJob, :welcome)
411
+
412
+ transaction = last_transaction
413
+ transaction_hash = transaction.to_h
414
+ expect(transaction_hash).to include(
415
+ "action" => "ActionMailerTestJob#welcome",
416
+ "sample_data" => hash_including(
417
+ "params" => ["ActionMailerTestJob", "welcome", "deliver_now"],
418
+ "tags" => {
419
+ "active_job_id" => kind_of(String),
420
+ "queue" => "mailers"
421
+ }
422
+ )
423
+ )
424
+ end
425
+ end
426
+
427
+ context "with multiple arguments" do
428
+ it "sets the arguments on the transaction" do
429
+ perform_mailer(ActionMailerTestJob, :welcome, method_given_args)
430
+
431
+ transaction = last_transaction
432
+ transaction_hash = transaction.to_h
433
+ expect(transaction_hash).to include(
434
+ "action" => "ActionMailerTestJob#welcome",
435
+ "sample_data" => hash_including(
436
+ "params" => ["ActionMailerTestJob", "welcome", "deliver_now"] + method_expected_args,
437
+ "tags" => {
438
+ "active_job_id" => kind_of(String),
439
+ "queue" => "mailers"
440
+ }
441
+ )
442
+ )
443
+ end
444
+ end
445
+
446
+ if DependencyHelper.rails_version >= Gem::Version.new("5.2.0")
447
+ context "with parameterized arguments" do
448
+ it "sets the arguments on the transaction" do
449
+ perform_mailer(ActionMailerTestJob, :welcome, parameterized_given_args)
450
+
451
+ transaction = last_transaction
452
+ transaction_hash = transaction.to_h
453
+ expect(transaction_hash).to include(
454
+ "action" => "ActionMailerTestJob#welcome",
455
+ "sample_data" => hash_including(
456
+ "params" => ["ActionMailerTestJob", "welcome", "deliver_now", parameterized_expected_args],
457
+ "tags" => {
458
+ "active_job_id" => kind_of(String),
459
+ "queue" => "mailers"
460
+ }
461
+ )
462
+ )
463
+ end
464
+ end
465
+ end
466
+ end
467
+
468
+ if DependencyHelper.rails_version >= Gem::Version.new("6.0.0")
469
+ context "with ActionMailer MailDeliveryJob job" do
470
+ include ActionMailerHelpers
471
+
472
+ before do
473
+ class ActionMailerTestMailDeliveryJob < ActionMailer::Base
474
+ self.delivery_job = ActionMailer::MailDeliveryJob
475
+
476
+ def welcome(*_args)
477
+ end
478
+ end
479
+ end
480
+ after do
481
+ Object.send(:remove_const, :ActionMailerTestMailDeliveryJob)
482
+ end
483
+
484
+ it "sets the Action mailer data on the transaction" do
485
+ perform_mailer(ActionMailerTestMailDeliveryJob, :welcome)
486
+
487
+ transaction = last_transaction
488
+ transaction_hash = transaction.to_h
489
+ expect(transaction_hash).to include(
490
+ "action" => "ActionMailerTestMailDeliveryJob#welcome",
491
+ "sample_data" => hash_including(
492
+ "params" => [
493
+ "ActionMailerTestMailDeliveryJob",
494
+ "welcome",
495
+ "deliver_now",
496
+ { active_job_internal_key => ["args"], "args" => [] }
497
+ ],
498
+ "tags" => {
499
+ "active_job_id" => kind_of(String),
500
+ "queue" => "mailers"
501
+ }
502
+ )
503
+ )
504
+ end
505
+
506
+ context "with method arguments" do
507
+ it "sets the Action mailer data on the transaction" do
508
+ perform_mailer(ActionMailerTestMailDeliveryJob, :welcome, method_given_args)
509
+
510
+ transaction = last_transaction
511
+ transaction_hash = transaction.to_h
512
+ expect(transaction_hash).to include(
513
+ "action" => "ActionMailerTestMailDeliveryJob#welcome",
514
+ "sample_data" => hash_including(
515
+ "params" => [
516
+ "ActionMailerTestMailDeliveryJob",
517
+ "welcome",
518
+ "deliver_now",
519
+ {
520
+ active_job_internal_key => ["args"],
521
+ "args" => method_expected_args
522
+ }
523
+ ],
524
+ "tags" => {
525
+ "active_job_id" => kind_of(String),
526
+ "queue" => "mailers"
527
+ }
528
+ )
529
+ )
530
+ end
531
+ end
532
+
533
+ context "with parameterized arguments" do
534
+ it "sets the Action mailer data on the transaction" do
535
+ perform_mailer(ActionMailerTestMailDeliveryJob, :welcome, parameterized_given_args)
536
+
537
+ transaction = last_transaction
538
+ transaction_hash = transaction.to_h
539
+ expect(transaction_hash).to include(
540
+ "action" => "ActionMailerTestMailDeliveryJob#welcome",
541
+ "sample_data" => hash_including(
542
+ "params" => [
543
+ "ActionMailerTestMailDeliveryJob",
544
+ "welcome",
545
+ "deliver_now",
546
+ {
547
+ active_job_internal_key => ["params", "args"],
548
+ "args" => [],
549
+ "params" => parameterized_expected_args
550
+ }
551
+ ],
552
+ "tags" => {
553
+ "active_job_id" => kind_of(String),
554
+ "queue" => "mailers"
555
+ }
556
+ )
557
+ )
558
+ end
559
+ end
560
+ end
561
+ end
562
+
563
+ def perform_active_job
564
+ Timecop.freeze(time) do
565
+ yield
566
+ end
567
+ end
568
+
569
+ def perform_job(job_class, args = nil)
570
+ perform_active_job do
571
+ if args
572
+ job_class.perform_later(args)
573
+ else
574
+ job_class.perform_later
575
+ end
576
+ end
577
+ end
578
+
579
+ def perform_mailer(mailer, method, args = nil)
580
+ perform_active_job { perform_action_mailer(mailer, method, args) }
581
+ end
582
+
583
+ def active_job_internal_key
584
+ if DependencyHelper.ruby_version >= Gem::Version.new("2.7.0")
585
+ "_aj_ruby2_keywords"
586
+ else
587
+ "_aj_symbol_keys"
588
+ end
589
+ end
590
+ end
591
+ end