appsignal 2.11.0.alpha.1-java → 2.11.0.beta.4-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.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/.semaphore/semaphore.yml +75 -61
- data/CHANGELOG.md +21 -1
- data/build_matrix.yml +13 -7
- data/ext/agent.yml +19 -19
- data/gemfiles/padrino.gemfile +2 -2
- data/gemfiles/rails-4.2.gemfile +9 -2
- data/gemfiles/rails-5.0.gemfile +1 -0
- data/gemfiles/rails-5.1.gemfile +1 -0
- data/gemfiles/rails-5.2.gemfile +1 -0
- data/gemfiles/rails-6.0.gemfile +1 -0
- data/gemfiles/resque-1.gemfile +7 -0
- data/gemfiles/{resque.gemfile → resque-2.gemfile} +1 -1
- data/lib/appsignal/hooks.rb +2 -0
- data/lib/appsignal/hooks/active_job.rb +114 -0
- data/lib/appsignal/hooks/puma.rb +2 -58
- data/lib/appsignal/hooks/resque.rb +60 -0
- data/lib/appsignal/hooks/sidekiq.rb +19 -192
- data/lib/appsignal/integrations/delayed_job_plugin.rb +1 -1
- data/lib/appsignal/integrations/que.rb +1 -1
- data/lib/appsignal/integrations/resque.rb +9 -12
- data/lib/appsignal/integrations/resque_active_job.rb +9 -32
- data/lib/appsignal/probes/puma.rb +61 -0
- data/lib/appsignal/probes/sidekiq.rb +102 -0
- data/lib/appsignal/rack/js_exception_catcher.rb +5 -2
- data/lib/appsignal/transaction.rb +10 -0
- data/lib/appsignal/utils/deprecation_message.rb +5 -1
- data/lib/appsignal/version.rb +1 -1
- data/lib/puma/plugin/appsignal.rb +2 -1
- data/spec/lib/appsignal/hooks/activejob_spec.rb +548 -0
- data/spec/lib/appsignal/hooks/delayed_job_spec.rb +3 -14
- data/spec/lib/appsignal/hooks/puma_spec.rb +2 -181
- data/spec/lib/appsignal/hooks/resque_spec.rb +185 -0
- data/spec/lib/appsignal/hooks/sidekiq_spec.rb +297 -549
- data/spec/lib/appsignal/integrations/padrino_spec.rb +1 -1
- data/spec/lib/appsignal/integrations/que_spec.rb +25 -6
- data/spec/lib/appsignal/integrations/resque_active_job_spec.rb +20 -179
- data/spec/lib/appsignal/integrations/resque_spec.rb +20 -85
- data/spec/lib/appsignal/probes/puma_spec.rb +180 -0
- data/spec/lib/appsignal/probes/sidekiq_spec.rb +204 -0
- data/spec/lib/appsignal/rack/js_exception_catcher_spec.rb +9 -4
- data/spec/lib/appsignal/transaction_spec.rb +5 -7
- data/spec/lib/puma/appsignal_spec.rb +1 -1
- data/spec/support/helpers/action_mailer_helpers.rb +25 -0
- data/spec/support/helpers/dependency_helper.rb +9 -2
- data/spec/support/helpers/transaction_helpers.rb +6 -0
- data/spec/support/stubs/sidekiq/api.rb +2 -2
- metadata +18 -3
@@ -4,18 +4,15 @@ module Appsignal
|
|
4
4
|
module Integrations
|
5
5
|
# @api private
|
6
6
|
module ResquePlugin
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
"
|
14
|
-
:
|
15
|
-
|
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
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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,61 @@
|
|
1
|
+
module Appsignal
|
2
|
+
module Probes
|
3
|
+
# @api private
|
4
|
+
class PumaProbe
|
5
|
+
def initialize
|
6
|
+
@hostname = Appsignal.config[:hostname] || Socket.gethostname
|
7
|
+
end
|
8
|
+
|
9
|
+
def call
|
10
|
+
puma_stats = fetch_puma_stats
|
11
|
+
return unless puma_stats
|
12
|
+
|
13
|
+
stats = JSON.parse puma_stats, :symbolize_names => true
|
14
|
+
counts = {}
|
15
|
+
count_keys = [:backlog, :running, :pool_capacity, :max_threads]
|
16
|
+
|
17
|
+
if stats[:worker_status] # Multiple workers
|
18
|
+
stats[:worker_status].each do |worker|
|
19
|
+
stat = worker[:last_status]
|
20
|
+
count_keys.each do |key|
|
21
|
+
count_if_present counts, key, stat
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
gauge(:workers, stats[:workers], :type => :count)
|
26
|
+
gauge(:workers, stats[:booted_workers], :type => :booted)
|
27
|
+
gauge(:workers, stats[:old_workers], :type => :old)
|
28
|
+
else # Single worker
|
29
|
+
count_keys.each do |key|
|
30
|
+
count_if_present counts, key, stats
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
gauge(:connection_backlog, counts[:backlog]) if counts[:backlog]
|
35
|
+
gauge(:pool_capacity, counts[:pool_capacity]) if counts[:pool_capacity]
|
36
|
+
gauge(:threads, counts[:running], :type => :running) if counts[:running]
|
37
|
+
gauge(:threads, counts[:max_threads], :type => :max) if counts[:max_threads]
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
attr_reader :hostname
|
43
|
+
|
44
|
+
def gauge(field, count, tags = {})
|
45
|
+
Appsignal.set_gauge("puma_#{field}", count, tags.merge(:hostname => hostname))
|
46
|
+
end
|
47
|
+
|
48
|
+
def count_if_present(counts, key, stats)
|
49
|
+
stat_value = stats[key]
|
50
|
+
return unless stat_value
|
51
|
+
counts[key] ||= 0
|
52
|
+
counts[key] += stat_value
|
53
|
+
end
|
54
|
+
|
55
|
+
def fetch_puma_stats
|
56
|
+
::Puma.stats
|
57
|
+
rescue NoMethodError # rubocop:disable Lint/HandleExceptions
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module Appsignal
|
2
|
+
module Probes
|
3
|
+
# @api private
|
4
|
+
class SidekiqProbe
|
5
|
+
attr_reader :config
|
6
|
+
|
7
|
+
def self.dependencies_present?
|
8
|
+
Gem::Version.new(::Redis::VERSION) >= Gem::Version.new("3.3.5")
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(config = {})
|
12
|
+
@config = config
|
13
|
+
@cache = {}
|
14
|
+
config_string = " with config: #{config}" unless config.empty?
|
15
|
+
Appsignal.logger.debug("Initializing Sidekiq probe#{config_string}")
|
16
|
+
require "sidekiq/api"
|
17
|
+
end
|
18
|
+
|
19
|
+
def call
|
20
|
+
track_redis_info
|
21
|
+
track_stats
|
22
|
+
track_queues
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
attr_reader :cache
|
28
|
+
|
29
|
+
def track_redis_info
|
30
|
+
return unless ::Sidekiq.respond_to?(:redis_info)
|
31
|
+
redis_info = ::Sidekiq.redis_info
|
32
|
+
|
33
|
+
gauge "connection_count", redis_info.fetch("connected_clients")
|
34
|
+
gauge "memory_usage", redis_info.fetch("used_memory")
|
35
|
+
gauge "memory_usage_rss", redis_info.fetch("used_memory_rss")
|
36
|
+
end
|
37
|
+
|
38
|
+
def track_stats
|
39
|
+
stats = ::Sidekiq::Stats.new
|
40
|
+
|
41
|
+
gauge "worker_count", stats.workers_size
|
42
|
+
gauge "process_count", stats.processes_size
|
43
|
+
gauge_delta :jobs_processed, "job_count", stats.processed,
|
44
|
+
:status => :processed
|
45
|
+
gauge_delta :jobs_failed, "job_count", stats.failed, :status => :failed
|
46
|
+
gauge "job_count", stats.retry_size, :status => :retry_queue
|
47
|
+
gauge_delta :jobs_dead, "job_count", stats.dead_size, :status => :died
|
48
|
+
gauge "job_count", stats.scheduled_size, :status => :scheduled
|
49
|
+
gauge "job_count", stats.enqueued, :status => :enqueued
|
50
|
+
end
|
51
|
+
|
52
|
+
def track_queues
|
53
|
+
::Sidekiq::Queue.all.each do |queue|
|
54
|
+
gauge "queue_length", queue.size, :queue => queue.name
|
55
|
+
# Convert latency from seconds to milliseconds
|
56
|
+
gauge "queue_latency", queue.latency * 1_000.0, :queue => queue.name
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Track a gauge metric with the `sidekiq_` prefix
|
61
|
+
def gauge(key, value, tags = {})
|
62
|
+
tags[:hostname] = hostname if hostname
|
63
|
+
Appsignal.set_gauge "sidekiq_#{key}", value, tags
|
64
|
+
end
|
65
|
+
|
66
|
+
# Track the delta of two values for a gauge metric
|
67
|
+
#
|
68
|
+
# First call will store the data for the metric and the second call will
|
69
|
+
# set a gauge metric with the difference. This is used for absolute
|
70
|
+
# counter values which we want to track as gauges.
|
71
|
+
#
|
72
|
+
# @example
|
73
|
+
# gauge_delta :my_cache_key, "my_gauge", 10
|
74
|
+
# gauge_delta :my_cache_key, "my_gauge", 15
|
75
|
+
# # Creates a gauge with the value `5`
|
76
|
+
# @see #gauge
|
77
|
+
def gauge_delta(cache_key, key, value, tags = {})
|
78
|
+
previous_value = cache[cache_key]
|
79
|
+
cache[cache_key] = value
|
80
|
+
return unless previous_value
|
81
|
+
new_value = value - previous_value
|
82
|
+
gauge key, new_value, tags
|
83
|
+
end
|
84
|
+
|
85
|
+
def hostname
|
86
|
+
return @hostname if defined?(@hostname)
|
87
|
+
if config.key?(:hostname)
|
88
|
+
@hostname = config[:hostname]
|
89
|
+
Appsignal.logger.debug "Sidekiq probe: Using hostname config " \
|
90
|
+
"option #{@hostname.inspect} as hostname"
|
91
|
+
return @hostname
|
92
|
+
end
|
93
|
+
|
94
|
+
host = nil
|
95
|
+
::Sidekiq.redis { |c| host = c.connection[:host] }
|
96
|
+
Appsignal.logger.debug "Sidekiq probe: Using Redis server hostname " \
|
97
|
+
"#{host.inspect} as hostname"
|
98
|
+
@hostname = host
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -29,8 +29,11 @@ module Appsignal
|
|
29
29
|
Appsignal.logger.debug \
|
30
30
|
"Initializing Appsignal::Rack::JSExceptionCatcher"
|
31
31
|
deprecation_message "The Appsignal::Rack::JSExceptionCatcher is " \
|
32
|
-
"deprecated
|
33
|
-
"integration
|
32
|
+
"deprecated and will be removed in a future version. Please use " \
|
33
|
+
"the official AppSignal JavaScript integration by disabling " \
|
34
|
+
"`enable_frontend_error_catching` in your configuration and " \
|
35
|
+
"installing AppSignal for JavaScript instead. " \
|
36
|
+
"(https://docs.appsignal.com/front-end/)"
|
34
37
|
@app = app
|
35
38
|
end
|
36
39
|
|
@@ -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
|
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
|
data/lib/appsignal/version.rb
CHANGED
@@ -17,7 +17,8 @@ Puma::Plugin.create do
|
|
17
17
|
launcher.events.on_booted do
|
18
18
|
require "appsignal"
|
19
19
|
if ::Puma.respond_to?(:stats)
|
20
|
-
|
20
|
+
require "appsignal/probes/puma"
|
21
|
+
Appsignal::Minutely.probes.register :puma, Appsignal::Probes::PumaProbe
|
21
22
|
end
|
22
23
|
Appsignal.start
|
23
24
|
Appsignal.start_logger
|
@@ -0,0 +1,548 @@
|
|
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 = { :priority => 10, :queue => queue }
|
158
|
+
expect(Appsignal).to receive(:increment_counter)
|
159
|
+
.with("active_job_queue_job_count", 1, tags.merge(:status => :processed))
|
160
|
+
|
161
|
+
perform_job(ActiveJobPriorityTestJob)
|
162
|
+
|
163
|
+
transaction = last_transaction
|
164
|
+
transaction_hash = transaction.to_h
|
165
|
+
expect(transaction_hash).to include(
|
166
|
+
"sample_data" => hash_including(
|
167
|
+
"tags" => hash_including("queue" => queue, "priority" => 10)
|
168
|
+
)
|
169
|
+
)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
context "with error" do
|
175
|
+
it "reports the error on the transaction from the ActiveRecord integration" do
|
176
|
+
allow(Appsignal).to receive(:increment_counter) # Other calls we're testing in another test
|
177
|
+
tags = { :queue => queue }
|
178
|
+
expect(Appsignal).to receive(:increment_counter)
|
179
|
+
.with("active_job_queue_job_count", 1, tags.merge(:status => :failed))
|
180
|
+
expect(Appsignal).to receive(:increment_counter)
|
181
|
+
.with("active_job_queue_job_count", 1, tags.merge(:status => :processed))
|
182
|
+
|
183
|
+
expect do
|
184
|
+
perform_job(ActiveJobErrorTestJob)
|
185
|
+
end.to raise_error(RuntimeError, "uh oh")
|
186
|
+
|
187
|
+
transaction = last_transaction
|
188
|
+
transaction_hash = transaction.to_h
|
189
|
+
expect(transaction_hash).to include(
|
190
|
+
"action" => "ActiveJobErrorTestJob#perform",
|
191
|
+
"error" => {
|
192
|
+
"name" => "RuntimeError",
|
193
|
+
"message" => "uh oh",
|
194
|
+
"backtrace" => kind_of(String)
|
195
|
+
},
|
196
|
+
"namespace" => namespace,
|
197
|
+
"metadata" => {},
|
198
|
+
"sample_data" => hash_including(
|
199
|
+
"params" => [],
|
200
|
+
"tags" => {
|
201
|
+
"active_job_id" => kind_of(String),
|
202
|
+
"queue" => queue
|
203
|
+
}
|
204
|
+
)
|
205
|
+
)
|
206
|
+
events = transaction_hash["events"]
|
207
|
+
.sort_by { |e| e["start"] }
|
208
|
+
.map { |event| event["name"] }
|
209
|
+
expect(events).to eq(["perform_start.active_job", "perform.active_job"])
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
context "when wrapped in another transaction" do
|
214
|
+
it "does not create a new transaction or close the currently open one" do
|
215
|
+
current_transaction = background_job_transaction
|
216
|
+
allow(current_transaction).to receive(:complete).and_call_original
|
217
|
+
set_current_transaction current_transaction
|
218
|
+
|
219
|
+
perform_job(ActiveJobTestJob)
|
220
|
+
|
221
|
+
expect(created_transactions.count).to eql(1)
|
222
|
+
expect(current_transaction).to_not have_received(:complete)
|
223
|
+
current_transaction.complete
|
224
|
+
|
225
|
+
transaction = current_transaction
|
226
|
+
transaction_hash = transaction.to_h
|
227
|
+
# It does set data on the transaction
|
228
|
+
expect(transaction_hash).to include(
|
229
|
+
"id" => current_transaction.transaction_id,
|
230
|
+
"action" => "ActiveJobTestJob#perform",
|
231
|
+
"error" => nil,
|
232
|
+
"namespace" => namespace,
|
233
|
+
"metadata" => {},
|
234
|
+
"sample_data" => hash_including(
|
235
|
+
"params" => [],
|
236
|
+
"tags" => {
|
237
|
+
"active_job_id" => kind_of(String),
|
238
|
+
"queue" => queue
|
239
|
+
}
|
240
|
+
)
|
241
|
+
)
|
242
|
+
events = transaction_hash["events"]
|
243
|
+
.reject { |e| e["name"] == "enqueue.active_job" }
|
244
|
+
.sort_by { |e| e["start"] }
|
245
|
+
.map { |event| event["name"] }
|
246
|
+
expect(events).to eq(["perform_start.active_job", "perform.active_job"])
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
context "with params" do
|
251
|
+
it "filters the configured params" do
|
252
|
+
Appsignal.config = project_fixture_config("production")
|
253
|
+
Appsignal.config[:filter_parameters] = ["foo"]
|
254
|
+
perform_job(ActiveJobTestJob, method_given_args)
|
255
|
+
|
256
|
+
transaction = last_transaction
|
257
|
+
transaction_hash = transaction.to_h
|
258
|
+
expect(transaction_hash["sample_data"]["params"]).to include(
|
259
|
+
[
|
260
|
+
"foo",
|
261
|
+
{
|
262
|
+
"_aj_symbol_keys" => ["foo"],
|
263
|
+
"foo" => "[FILTERED]",
|
264
|
+
"bar" => "Bar",
|
265
|
+
"baz" => { "_aj_symbol_keys" => [], "1" => "foo" }
|
266
|
+
}
|
267
|
+
]
|
268
|
+
)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
context "with provider_job_id", :skip => DependencyHelper.rails_version < Gem::Version.new("5.0.0") do
|
273
|
+
before do
|
274
|
+
module ActiveJob
|
275
|
+
module QueueAdapters
|
276
|
+
# Adapter used in our test suite to add provider data to the job
|
277
|
+
# data, as is done by Rails provided ActiveJob adapters.
|
278
|
+
#
|
279
|
+
# This implementation is based on the
|
280
|
+
# `ActiveJob::QueueAdapters::InlineAdapter`.
|
281
|
+
class AppsignalTestAdapter < InlineAdapter
|
282
|
+
def enqueue(job)
|
283
|
+
Base.execute(job.serialize.merge("provider_job_id" => "my_provider_job_id"))
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
class ProviderWrappedActiveJobTestJob < ActiveJob::Base
|
290
|
+
self.queue_adapter = :appsignal_test
|
291
|
+
|
292
|
+
def perform(*_args)
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
after do
|
297
|
+
ActiveJob::QueueAdapters.send(:remove_const, :AppsignalTestAdapter)
|
298
|
+
Object.send(:remove_const, :ProviderWrappedActiveJobTestJob)
|
299
|
+
end
|
300
|
+
|
301
|
+
it "sets provider_job_id as tag" do
|
302
|
+
perform_job(ProviderWrappedActiveJobTestJob)
|
303
|
+
|
304
|
+
transaction = last_transaction
|
305
|
+
transaction_hash = transaction.to_h
|
306
|
+
expect(transaction_hash["sample_data"]["tags"]).to include(
|
307
|
+
"provider_job_id" => "my_provider_job_id"
|
308
|
+
)
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
context "with enqueued_at", :skip => DependencyHelper.rails_version < Gem::Version.new("6.0.0") do
|
313
|
+
before do
|
314
|
+
module ActiveJob
|
315
|
+
module QueueAdapters
|
316
|
+
# Adapter used in our test suite to add provider data to the job
|
317
|
+
# data, as is done by Rails provided ActiveJob adapters.
|
318
|
+
#
|
319
|
+
# This implementation is based on the
|
320
|
+
# `ActiveJob::QueueAdapters::InlineAdapter`.
|
321
|
+
class AppsignalTestAdapter < InlineAdapter
|
322
|
+
def enqueue(job)
|
323
|
+
Base.execute(job.serialize.merge("enqueued_at" => "2020-10-10T10:10:10Z"))
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
class ProviderWrappedActiveJobTestJob < ActiveJob::Base
|
330
|
+
self.queue_adapter = :appsignal_test
|
331
|
+
|
332
|
+
def perform(*_args)
|
333
|
+
end
|
334
|
+
end
|
335
|
+
end
|
336
|
+
after do
|
337
|
+
ActiveJob::QueueAdapters.send(:remove_const, :AppsignalTestAdapter)
|
338
|
+
Object.send(:remove_const, :ProviderWrappedActiveJobTestJob)
|
339
|
+
end
|
340
|
+
|
341
|
+
it "sets queue time on transaction" do
|
342
|
+
allow_any_instance_of(Appsignal::Transaction).to receive(:set_queue_start).and_call_original
|
343
|
+
perform_job(ProviderWrappedActiveJobTestJob)
|
344
|
+
|
345
|
+
transaction = last_transaction
|
346
|
+
queue_time = Time.parse("2020-10-10T10:10:10Z")
|
347
|
+
expect(transaction).to have_received(:set_queue_start)
|
348
|
+
.with((queue_time.to_f * 1_000).to_i)
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
context "with ActionMailer job" do
|
353
|
+
include ActionMailerHelpers
|
354
|
+
|
355
|
+
before do
|
356
|
+
class ActionMailerTestJob < ActionMailer::Base
|
357
|
+
def welcome(_first_arg = nil, _second_arg = nil)
|
358
|
+
end
|
359
|
+
end
|
360
|
+
end
|
361
|
+
after do
|
362
|
+
Object.send(:remove_const, :ActionMailerTestJob)
|
363
|
+
end
|
364
|
+
|
365
|
+
context "without params" do
|
366
|
+
it "sets the Action mailer data on the transaction" do
|
367
|
+
perform_mailer(ActionMailerTestJob, :welcome)
|
368
|
+
|
369
|
+
transaction = last_transaction
|
370
|
+
transaction_hash = transaction.to_h
|
371
|
+
expect(transaction_hash).to include(
|
372
|
+
"action" => "ActionMailerTestJob#welcome",
|
373
|
+
"sample_data" => hash_including(
|
374
|
+
"params" => ["ActionMailerTestJob", "welcome", "deliver_now"],
|
375
|
+
"tags" => {
|
376
|
+
"active_job_id" => kind_of(String),
|
377
|
+
"queue" => "mailers"
|
378
|
+
}
|
379
|
+
)
|
380
|
+
)
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
context "with multiple arguments" do
|
385
|
+
it "sets the arguments on the transaction" do
|
386
|
+
perform_mailer(ActionMailerTestJob, :welcome, method_given_args)
|
387
|
+
|
388
|
+
transaction = last_transaction
|
389
|
+
transaction_hash = transaction.to_h
|
390
|
+
expect(transaction_hash).to include(
|
391
|
+
"action" => "ActionMailerTestJob#welcome",
|
392
|
+
"sample_data" => hash_including(
|
393
|
+
"params" => ["ActionMailerTestJob", "welcome", "deliver_now"] + method_expected_args,
|
394
|
+
"tags" => {
|
395
|
+
"active_job_id" => kind_of(String),
|
396
|
+
"queue" => "mailers"
|
397
|
+
}
|
398
|
+
)
|
399
|
+
)
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
if DependencyHelper.rails_version >= Gem::Version.new("5.2.0")
|
404
|
+
context "with parameterized arguments" do
|
405
|
+
it "sets the arguments on the transaction" do
|
406
|
+
perform_mailer(ActionMailerTestJob, :welcome, parameterized_given_args)
|
407
|
+
|
408
|
+
transaction = last_transaction
|
409
|
+
transaction_hash = transaction.to_h
|
410
|
+
expect(transaction_hash).to include(
|
411
|
+
"action" => "ActionMailerTestJob#welcome",
|
412
|
+
"sample_data" => hash_including(
|
413
|
+
"params" => ["ActionMailerTestJob", "welcome", "deliver_now", parameterized_expected_args],
|
414
|
+
"tags" => {
|
415
|
+
"active_job_id" => kind_of(String),
|
416
|
+
"queue" => "mailers"
|
417
|
+
}
|
418
|
+
)
|
419
|
+
)
|
420
|
+
end
|
421
|
+
end
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
if DependencyHelper.rails_version >= Gem::Version.new("6.0.0")
|
426
|
+
context "with ActionMailer MailDeliveryJob job" do
|
427
|
+
include ActionMailerHelpers
|
428
|
+
|
429
|
+
before do
|
430
|
+
class ActionMailerTestMailDeliveryJob < ActionMailer::Base
|
431
|
+
self.delivery_job = ActionMailer::MailDeliveryJob
|
432
|
+
|
433
|
+
def welcome(*_args)
|
434
|
+
end
|
435
|
+
end
|
436
|
+
end
|
437
|
+
after do
|
438
|
+
Object.send(:remove_const, :ActionMailerTestMailDeliveryJob)
|
439
|
+
end
|
440
|
+
|
441
|
+
it "sets the Action mailer data on the transaction" do
|
442
|
+
perform_mailer(ActionMailerTestMailDeliveryJob, :welcome)
|
443
|
+
|
444
|
+
transaction = last_transaction
|
445
|
+
transaction_hash = transaction.to_h
|
446
|
+
expect(transaction_hash).to include(
|
447
|
+
"action" => "ActionMailerTestMailDeliveryJob#welcome",
|
448
|
+
"sample_data" => hash_including(
|
449
|
+
"params" => [
|
450
|
+
"ActionMailerTestMailDeliveryJob",
|
451
|
+
"welcome",
|
452
|
+
"deliver_now",
|
453
|
+
{ active_job_internal_key => ["args"], "args" => [] }
|
454
|
+
],
|
455
|
+
"tags" => {
|
456
|
+
"active_job_id" => kind_of(String),
|
457
|
+
"queue" => "mailers"
|
458
|
+
}
|
459
|
+
)
|
460
|
+
)
|
461
|
+
end
|
462
|
+
|
463
|
+
context "with method arguments" do
|
464
|
+
it "sets the Action mailer data on the transaction" do
|
465
|
+
perform_mailer(ActionMailerTestMailDeliveryJob, :welcome, method_given_args)
|
466
|
+
|
467
|
+
transaction = last_transaction
|
468
|
+
transaction_hash = transaction.to_h
|
469
|
+
expect(transaction_hash).to include(
|
470
|
+
"action" => "ActionMailerTestMailDeliveryJob#welcome",
|
471
|
+
"sample_data" => hash_including(
|
472
|
+
"params" => [
|
473
|
+
"ActionMailerTestMailDeliveryJob",
|
474
|
+
"welcome",
|
475
|
+
"deliver_now",
|
476
|
+
{
|
477
|
+
active_job_internal_key => ["args"],
|
478
|
+
"args" => method_expected_args
|
479
|
+
}
|
480
|
+
],
|
481
|
+
"tags" => {
|
482
|
+
"active_job_id" => kind_of(String),
|
483
|
+
"queue" => "mailers"
|
484
|
+
}
|
485
|
+
)
|
486
|
+
)
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
490
|
+
context "with parameterized arguments" do
|
491
|
+
it "sets the Action mailer data on the transaction" do
|
492
|
+
perform_mailer(ActionMailerTestMailDeliveryJob, :welcome, parameterized_given_args)
|
493
|
+
|
494
|
+
transaction = last_transaction
|
495
|
+
transaction_hash = transaction.to_h
|
496
|
+
expect(transaction_hash).to include(
|
497
|
+
"action" => "ActionMailerTestMailDeliveryJob#welcome",
|
498
|
+
"sample_data" => hash_including(
|
499
|
+
"params" => [
|
500
|
+
"ActionMailerTestMailDeliveryJob",
|
501
|
+
"welcome",
|
502
|
+
"deliver_now",
|
503
|
+
{
|
504
|
+
active_job_internal_key => ["params", "args"],
|
505
|
+
"args" => [],
|
506
|
+
"params" => parameterized_expected_args
|
507
|
+
}
|
508
|
+
],
|
509
|
+
"tags" => {
|
510
|
+
"active_job_id" => kind_of(String),
|
511
|
+
"queue" => "mailers"
|
512
|
+
}
|
513
|
+
)
|
514
|
+
)
|
515
|
+
end
|
516
|
+
end
|
517
|
+
end
|
518
|
+
end
|
519
|
+
|
520
|
+
def perform_active_job
|
521
|
+
Timecop.freeze(time) do
|
522
|
+
yield
|
523
|
+
end
|
524
|
+
end
|
525
|
+
|
526
|
+
def perform_job(job_class, args = nil)
|
527
|
+
perform_active_job do
|
528
|
+
if args
|
529
|
+
job_class.perform_later(args)
|
530
|
+
else
|
531
|
+
job_class.perform_later
|
532
|
+
end
|
533
|
+
end
|
534
|
+
end
|
535
|
+
|
536
|
+
def perform_mailer(mailer, method, args = nil)
|
537
|
+
perform_active_job { perform_action_mailer(mailer, method, args) }
|
538
|
+
end
|
539
|
+
|
540
|
+
def active_job_internal_key
|
541
|
+
if DependencyHelper.ruby_version >= Gem::Version.new("2.7.0")
|
542
|
+
"_aj_ruby2_keywords"
|
543
|
+
else
|
544
|
+
"_aj_symbol_keys"
|
545
|
+
end
|
546
|
+
end
|
547
|
+
end
|
548
|
+
end
|