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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/.semaphore/semaphore.yml +75 -61
  4. data/CHANGELOG.md +21 -1
  5. data/build_matrix.yml +13 -7
  6. data/ext/agent.yml +19 -19
  7. data/gemfiles/padrino.gemfile +2 -2
  8. data/gemfiles/rails-4.2.gemfile +9 -2
  9. data/gemfiles/rails-5.0.gemfile +1 -0
  10. data/gemfiles/rails-5.1.gemfile +1 -0
  11. data/gemfiles/rails-5.2.gemfile +1 -0
  12. data/gemfiles/rails-6.0.gemfile +1 -0
  13. data/gemfiles/resque-1.gemfile +7 -0
  14. data/gemfiles/{resque.gemfile → resque-2.gemfile} +1 -1
  15. data/lib/appsignal/hooks.rb +2 -0
  16. data/lib/appsignal/hooks/active_job.rb +114 -0
  17. data/lib/appsignal/hooks/puma.rb +2 -58
  18. data/lib/appsignal/hooks/resque.rb +60 -0
  19. data/lib/appsignal/hooks/sidekiq.rb +19 -192
  20. data/lib/appsignal/integrations/delayed_job_plugin.rb +1 -1
  21. data/lib/appsignal/integrations/que.rb +1 -1
  22. data/lib/appsignal/integrations/resque.rb +9 -12
  23. data/lib/appsignal/integrations/resque_active_job.rb +9 -32
  24. data/lib/appsignal/probes/puma.rb +61 -0
  25. data/lib/appsignal/probes/sidekiq.rb +102 -0
  26. data/lib/appsignal/rack/js_exception_catcher.rb +5 -2
  27. data/lib/appsignal/transaction.rb +10 -0
  28. data/lib/appsignal/utils/deprecation_message.rb +5 -1
  29. data/lib/appsignal/version.rb +1 -1
  30. data/lib/puma/plugin/appsignal.rb +2 -1
  31. data/spec/lib/appsignal/hooks/activejob_spec.rb +548 -0
  32. data/spec/lib/appsignal/hooks/delayed_job_spec.rb +3 -14
  33. data/spec/lib/appsignal/hooks/puma_spec.rb +2 -181
  34. data/spec/lib/appsignal/hooks/resque_spec.rb +185 -0
  35. data/spec/lib/appsignal/hooks/sidekiq_spec.rb +297 -549
  36. data/spec/lib/appsignal/integrations/padrino_spec.rb +1 -1
  37. data/spec/lib/appsignal/integrations/que_spec.rb +25 -6
  38. data/spec/lib/appsignal/integrations/resque_active_job_spec.rb +20 -179
  39. data/spec/lib/appsignal/integrations/resque_spec.rb +20 -85
  40. data/spec/lib/appsignal/probes/puma_spec.rb +180 -0
  41. data/spec/lib/appsignal/probes/sidekiq_spec.rb +204 -0
  42. data/spec/lib/appsignal/rack/js_exception_catcher_spec.rb +9 -4
  43. data/spec/lib/appsignal/transaction_spec.rb +5 -7
  44. data/spec/lib/puma/appsignal_spec.rb +1 -1
  45. data/spec/support/helpers/action_mailer_helpers.rb +25 -0
  46. data/spec/support/helpers/dependency_helper.rb +9 -2
  47. data/spec/support/helpers/transaction_helpers.rb +6 -0
  48. data/spec/support/stubs/sidekiq/api.rb +2 -2
  49. metadata +18 -3
@@ -142,7 +142,7 @@ if DependencyHelper.padrino_present?
142
142
  expect_a_transaction_to_be_created
143
143
  # Uses path for action name
144
144
  expect(transaction).to receive(:set_action_if_nil).with("PadrinoTestApp#unknown")
145
- expect(response).to match_response(404, "<h1>Not Found</h1>")
145
+ expect(response).to match_response(404, "GET /404")
146
146
  end
147
147
  end
148
148
 
@@ -14,7 +14,6 @@ if DependencyHelper.que_present?
14
14
  :error_count => 0
15
15
  }
16
16
  end
17
-
18
17
  let(:env) do
19
18
  {
20
19
  :class => "MyQueJob",
@@ -29,7 +28,6 @@ if DependencyHelper.que_present?
29
28
  :params => %w[1 birds]
30
29
  }
31
30
  end
32
-
33
31
  let(:job) do
34
32
  Class.new(::Que::Job) do
35
33
  def run(*args)
@@ -37,7 +35,6 @@ if DependencyHelper.que_present?
37
35
  end
38
36
  end
39
37
  let(:instance) { job.new(job_attrs) }
40
-
41
38
  before do
42
39
  allow(Que).to receive(:execute)
43
40
 
@@ -46,10 +43,14 @@ if DependencyHelper.que_present?
46
43
  end
47
44
  around { |example| keep_transactions { example.run } }
48
45
 
46
+ def perform_job(job)
47
+ job._run
48
+ end
49
+
49
50
  context "success" do
50
51
  it "creates a transaction for a job" do
51
52
  expect do
52
- instance._run
53
+ perform_job(instance)
53
54
  end.to change { created_transactions.length }.by(1)
54
55
 
55
56
  expect(last_transaction).to be_completed
@@ -95,7 +96,7 @@ if DependencyHelper.que_present?
95
96
 
96
97
  expect do
97
98
  expect do
98
- instance._run
99
+ perform_job(instance)
99
100
  end.to raise_error(ExampleException)
100
101
  end.to change { created_transactions.length }.by(1)
101
102
 
@@ -130,7 +131,7 @@ if DependencyHelper.que_present?
130
131
  it "reports errors and not re-raise them" do
131
132
  allow(instance).to receive(:run).and_raise(error)
132
133
 
133
- expect { instance._run }.to change { created_transactions.length }.by(1)
134
+ expect { perform_job(instance) }.to change { created_transactions.length }.by(1)
134
135
 
135
136
  expect(last_transaction).to be_completed
136
137
  transaction_hash = last_transaction.to_h
@@ -156,6 +157,24 @@ if DependencyHelper.que_present?
156
157
  )
157
158
  end
158
159
  end
160
+
161
+ context "when action set in job" do
162
+ let(:job) do
163
+ Class.new(::Que::Job) do
164
+ def run(*_args)
165
+ Appsignal.set_action("MyCustomJob#perform")
166
+ end
167
+ end
168
+ end
169
+
170
+ it "uses the custom action" do
171
+ perform_job(instance)
172
+
173
+ expect(last_transaction).to be_completed
174
+ transaction_hash = last_transaction.to_h
175
+ expect(transaction_hash).to include("action" => "MyCustomJob#perform")
176
+ end
177
+ end
159
178
  end
160
179
  end
161
180
  end
@@ -1,187 +1,28 @@
1
- if DependencyHelper.active_job_present?
2
- require "active_job"
3
- require File.expand_path("lib/appsignal/integrations/resque_active_job.rb")
1
+ require "appsignal/integrations/resque_active_job"
4
2
 
5
- class TestActiveJob < ActiveJob::Base
6
- include Appsignal::Integrations::ResqueActiveJobPlugin
3
+ describe "Legacy Resque ActiveJob integration" do
4
+ let(:err_stream) { std_stream }
5
+ let(:stderr) { err_stream.read }
6
+ let(:log_stream) { std_stream }
7
+ let(:log) { log_contents(log_stream) }
7
8
 
8
- def perform(_)
9
- end
10
- end
11
-
12
- describe Appsignal::Integrations::ResqueActiveJobPlugin do
13
- let(:args) { "argument" }
14
- let(:job) { TestActiveJob.new(args) }
15
- before { start_agent }
16
-
17
- def perform
18
- keep_transactions do
19
- job.perform_now
20
- end
21
- end
22
-
23
- context "without error" do
24
- it "creates a new transaction" do
25
- expect { perform }.to change { created_transactions.length }.by(1)
26
-
27
- expect(last_transaction.to_h).to include(
28
- "namespace" => Appsignal::Transaction::BACKGROUND_JOB,
29
- "action" => "TestActiveJob#perform",
30
- "error" => nil,
31
- "events" => [
32
- hash_including(
33
- "name" => "perform_job.resque",
34
- "title" => "",
35
- "body" => "",
36
- "body_format" => Appsignal::EventFormatter::DEFAULT,
37
- "count" => 1,
38
- "duration" => kind_of(Float)
39
- )
40
- ],
41
- "sample_data" => hash_including(
42
- "params" => ["argument"],
43
- "metadata" => {
44
- "id" => kind_of(String),
45
- "queue" => "default"
46
- }
47
- )
48
- )
49
- end
50
- end
51
-
52
- context "with error" do
53
- let(:job) do
54
- class BrokenTestActiveJob < ActiveJob::Base
55
- include Appsignal::Integrations::ResqueActiveJobPlugin
56
-
57
- def perform(_)
58
- raise ExampleException, "my error message"
59
- end
60
- end
61
-
62
- BrokenTestActiveJob.new(args)
63
- end
64
-
65
- it "creates a new transaction with an error" do
66
- expect do
67
- expect { perform }.to raise_error(ExampleException, "my error message")
68
- end.to change { created_transactions.length }.by(1)
9
+ it "logs and prints a deprecation message on extend" do
10
+ Appsignal.logger = test_logger(log_stream)
69
11
 
70
- expect(last_transaction.to_h).to include(
71
- "namespace" => Appsignal::Transaction::BACKGROUND_JOB,
72
- "action" => "BrokenTestActiveJob#perform",
73
- "error" => {
74
- "name" => "ExampleException",
75
- "message" => "my error message",
76
- "backtrace" => kind_of(String)
77
- },
78
- "sample_data" => hash_including(
79
- "params" => ["argument"],
80
- "metadata" => {
81
- "id" => kind_of(String),
82
- "queue" => "default"
83
- }
84
- )
85
- )
12
+ capture_std_streams(std_stream, err_stream) do
13
+ Class.new do
14
+ include Appsignal::Integrations::ResqueActiveJobPlugin
86
15
  end
87
16
  end
88
17
 
89
- context "with complex arguments" do
90
- context "with too long values" do
91
- let(:args) do
92
- {
93
- :foo => "Foo",
94
- :bar => "a" * 2001
95
- }
96
- end
97
-
98
- it "truncates large argument values" do
99
- perform
100
- expect(last_transaction.to_h).to include(
101
- "namespace" => Appsignal::Transaction::BACKGROUND_JOB,
102
- "action" => "TestActiveJob#perform",
103
- "error" => nil,
104
- "sample_data" => hash_including(
105
- "params" => ["foo" => "Foo", "bar" => "#{"a" * 2000}..."],
106
- "metadata" => {
107
- "id" => kind_of(String),
108
- "queue" => "default"
109
- }
110
- )
111
- )
112
- end
113
- end
114
-
115
- context "with parameter filtering" do
116
- let(:args) do
117
- {
118
- :foo => "Foo",
119
- :bar => "Bar"
120
- }
121
- end
122
- before { Appsignal.config[:filter_parameters] = ["foo"] }
123
-
124
- it "filters selected arguments" do
125
- perform
126
- expect(last_transaction.to_h).to include(
127
- "namespace" => Appsignal::Transaction::BACKGROUND_JOB,
128
- "action" => "TestActiveJob#perform",
129
- "error" => nil,
130
- "sample_data" => hash_including(
131
- "params" => ["foo" => "[FILTERED]", "bar" => "Bar"],
132
- "metadata" => {
133
- "id" => kind_of(String),
134
- "queue" => "default"
135
- }
136
- )
137
- )
138
- end
139
- end
140
- end
141
-
142
- context "without queue time" do
143
- it "does not add queue time to transaction" do
144
- # TODO: Not available in transaction.to_h yet.
145
- # https://github.com/appsignal/appsignal-agent/issues/293
146
- expect(Appsignal).to receive(:monitor_single_transaction).with(
147
- "perform_job.resque",
148
- a_hash_including(:queue_start => nil)
149
- ).and_call_original
150
-
151
- perform
152
- expect(last_transaction.to_h).to include(
153
- "namespace" => Appsignal::Transaction::BACKGROUND_JOB,
154
- "action" => "TestActiveJob#perform",
155
- "events" => [
156
- hash_including("name" => "perform_job.resque")
157
- ]
158
- )
159
- end
160
- end
161
-
162
- if DependencyHelper.rails6_present?
163
- context "with queue time" do
164
- it "adds queue time to transction" do
165
- queue_start = "2017-01-01 10:01:00UTC"
166
- queue_start_time = Time.parse(queue_start)
167
- # TODO: Not available in transaction.to_h yet.
168
- # https://github.com/appsignal/appsignal-agent/issues/293
169
- expect(Appsignal).to receive(:monitor_single_transaction).with(
170
- "perform_job.resque",
171
- a_hash_including(:queue_start => queue_start_time)
172
- ).and_call_original
173
- job.enqueued_at = queue_start
174
-
175
- perform
176
- expect(last_transaction.to_h).to include(
177
- "namespace" => Appsignal::Transaction::BACKGROUND_JOB,
178
- "action" => "TestActiveJob#perform",
179
- "events" => [
180
- hash_including("name" => "perform_job.resque")
181
- ]
182
- )
183
- end
184
- end
185
- end
18
+ deprecation_message =
19
+ "The AppSignal ResqueActiveJobPlugin is deprecated and does " \
20
+ "nothing on extend. In this version of the AppSignal Ruby gem " \
21
+ "the integration with Resque is automatic on all Resque workers. " \
22
+ "Please remove the following line from this file to remove this " \
23
+ "message: include Appsignal::Integrations::ResqueActiveJobPlugin\n" \
24
+ "#{__FILE__}:"
25
+ expect(stderr).to include "appsignal WARNING: #{deprecation_message}"
26
+ expect(log).to contains_log :warn, deprecation_message
186
27
  end
187
28
  end
@@ -1,93 +1,28 @@
1
- if DependencyHelper.resque_present?
2
- describe "Resque integration" do
3
- let(:file) { File.expand_path("lib/appsignal/integrations/resque.rb") }
1
+ require "appsignal/integrations/resque"
4
2
 
5
- context "with resque" do
6
- before do
7
- load file
8
- start_agent
3
+ describe "Legacy Resque integration" do
4
+ let(:err_stream) { std_stream }
5
+ let(:stderr) { err_stream.read }
6
+ let(:log_stream) { std_stream }
7
+ let(:log) { log_contents(log_stream) }
9
8
 
10
- class TestJob
11
- extend Appsignal::Integrations::ResquePlugin
9
+ it "logs and prints a deprecation message on extend" do
10
+ Appsignal.logger = test_logger(log_stream)
12
11
 
13
- def self.perform
14
- end
15
- end
16
-
17
- class BrokenTestJob
18
- extend Appsignal::Integrations::ResquePlugin
19
-
20
- def self.perform
21
- raise ExampleException, "my error message"
22
- end
23
- end
24
- end
25
-
26
- describe :around_perform_resque_plugin do
27
- let(:job) { ::Resque::Job.new("default", "class" => "TestJob") }
28
- before { expect(Appsignal).to receive(:stop) }
29
-
30
- context "without exception" do
31
- it "creates a new transaction" do
32
- expect do
33
- keep_transactions { job.perform }
34
- end.to change { created_transactions.length }.by(1)
35
-
36
- expect(last_transaction).to be_completed
37
- expect(last_transaction.to_h).to include(
38
- "namespace" => Appsignal::Transaction::BACKGROUND_JOB,
39
- "action" => "TestJob#perform",
40
- "error" => nil,
41
- "events" => [
42
- hash_including(
43
- "name" => "perform_job.resque",
44
- "title" => "",
45
- "body" => "",
46
- "body_format" => Appsignal::EventFormatter::DEFAULT,
47
- "count" => 1,
48
- "duration" => kind_of(Float)
49
- )
50
- ]
51
- )
52
- end
53
- end
54
-
55
- context "with exception" do
56
- let(:job) { ::Resque::Job.new("default", "class" => "BrokenTestJob") }
57
-
58
- def perform
59
- keep_transactions do
60
- expect do
61
- job.perform
62
- end.to raise_error(ExampleException, "my error message")
63
- end
64
- end
65
-
66
- it "sets the exception on the transaction" do
67
- expect do
68
- perform
69
- end.to change { created_transactions.length }.by(1)
70
-
71
- expect(last_transaction).to be_completed
72
- expect(last_transaction.to_h).to include(
73
- "namespace" => Appsignal::Transaction::BACKGROUND_JOB,
74
- "action" => "BrokenTestJob#perform",
75
- "error" => {
76
- "name" => "ExampleException",
77
- "message" => "my error message",
78
- "backtrace" => kind_of(String)
79
- }
80
- )
81
- end
82
- end
12
+ capture_std_streams(std_stream, err_stream) do
13
+ Class.new do
14
+ extend Appsignal::Integrations::ResquePlugin
83
15
  end
84
16
  end
85
17
 
86
- context "without resque" do
87
- before(:context) { Object.send(:remove_const, :Resque) }
88
-
89
- it { expect { ::Resque }.to raise_error(NameError) }
90
- it { expect { load file }.to_not raise_error }
91
- end
18
+ deprecation_message =
19
+ "The AppSignal ResquePlugin is deprecated and does " \
20
+ "nothing on extend. In this version of the AppSignal Ruby gem " \
21
+ "the integration with Resque is automatic on all Resque workers. " \
22
+ "Please remove the following line from this file to remove this " \
23
+ "message: extend Appsignal::Integrations::ResquePlugin\n" \
24
+ "#{__FILE__}:"
25
+ expect(stderr).to include "appsignal WARNING: #{deprecation_message}"
26
+ expect(log).to contains_log :warn, deprecation_message
92
27
  end
93
28
  end
@@ -0,0 +1,180 @@
1
+ require "appsignal/probes/puma"
2
+
3
+ describe Appsignal::Probes::PumaProbe do
4
+ before(:context) do
5
+ Appsignal.config = project_fixture_config
6
+ end
7
+ after(:context) do
8
+ Appsignal.config = nil
9
+ end
10
+
11
+ let(:probe) { described_class.new }
12
+
13
+ describe "hostname" do
14
+ it "returns the socket hostname" do
15
+ expect(probe.send(:hostname)).to eql(Socket.gethostname)
16
+ end
17
+
18
+ context "with overridden hostname" do
19
+ around do |sample|
20
+ Appsignal.config[:hostname] = "frontend1"
21
+ sample.run
22
+ Appsignal.config[:hostname] = nil
23
+ end
24
+ it "returns the configured host" do
25
+ expect(probe.send(:hostname)).to eql("frontend1")
26
+ end
27
+ end
28
+ end
29
+
30
+ describe "#call" do
31
+ let(:expected_default_tags) { { :hostname => Socket.gethostname } }
32
+
33
+ context "with multiple worker stats" do
34
+ before(:context) do
35
+ class Puma
36
+ def self.stats
37
+ {
38
+ "workers" => 2,
39
+ "booted_workers" => 2,
40
+ "old_workers" => 0,
41
+ "worker_status" => [
42
+ {
43
+ "last_status" => {
44
+ "backlog" => 0,
45
+ "running" => 5,
46
+ "pool_capacity" => 5,
47
+ "max_threads" => 5
48
+ }
49
+ },
50
+ {
51
+ "last_status" => {
52
+ "backlog" => 0,
53
+ "running" => 5,
54
+ "pool_capacity" => 5,
55
+ "max_threads" => 5
56
+ }
57
+ }
58
+ ]
59
+ }.to_json
60
+ end
61
+ end
62
+ end
63
+ after(:context) { Object.send(:remove_const, :Puma) }
64
+
65
+ it "calls `puma_gauge` with the (summed) worker metrics" do
66
+ expect_gauge(:workers, 2, :type => :count)
67
+ expect_gauge(:workers, 2, :type => :booted)
68
+ expect_gauge(:workers, 0, :type => :old)
69
+
70
+ expect_gauge(:connection_backlog, 0)
71
+ expect_gauge(:pool_capacity, 10)
72
+ expect_gauge(:threads, 10, :type => :running)
73
+ expect_gauge(:threads, 10, :type => :max)
74
+
75
+ probe.call
76
+ end
77
+ end
78
+
79
+ context "with single worker stats" do
80
+ before(:context) do
81
+ class Puma
82
+ def self.stats
83
+ {
84
+ "backlog" => 0,
85
+ "running" => 5,
86
+ "pool_capacity" => 5,
87
+ "max_threads" => 5
88
+ }.to_json
89
+ end
90
+ end
91
+ end
92
+ after(:context) { Object.send(:remove_const, :Puma) }
93
+
94
+ it "calls `puma_gauge` with the (summed) worker metrics" do
95
+ expect_gauge(:connection_backlog, 0)
96
+ expect_gauge(:pool_capacity, 5)
97
+ expect_gauge(:threads, 5, :type => :running)
98
+ expect_gauge(:threads, 5, :type => :max)
99
+ probe.call
100
+ end
101
+ end
102
+
103
+ context "without stats" do
104
+ before(:context) do
105
+ class Puma
106
+ def self.stats
107
+ end
108
+ end
109
+ end
110
+ after(:context) { Object.send(:remove_const, :Puma) }
111
+
112
+ context "when it returns nil" do
113
+ it "does not track metrics" do
114
+ expect(probe).to_not receive(:puma_gauge)
115
+ probe.call
116
+ end
117
+ end
118
+
119
+ # Puma.stats raises a NoMethodError on a nil object on the first call.
120
+ context "when it returns a NoMethodError on the first call" do
121
+ let(:log) { StringIO.new }
122
+
123
+ it "ignores the first call and tracks the second call" do
124
+ use_logger_with log do
125
+ expect(Puma).to receive(:stats)
126
+ .and_raise(NoMethodError.new("undefined method `stats' for nil:NilClass"))
127
+ probe.call
128
+
129
+ expect(Puma).to receive(:stats).and_return({
130
+ "backlog" => 1,
131
+ "running" => 5,
132
+ "pool_capacity" => 4,
133
+ "max_threads" => 6
134
+ }.to_json)
135
+
136
+ expect_gauge(:connection_backlog, 1)
137
+ expect_gauge(:pool_capacity, 4)
138
+ expect_gauge(:threads, 5, :type => :running)
139
+ expect_gauge(:threads, 6, :type => :max)
140
+ probe.call
141
+ end
142
+
143
+ expect(log_contents(log)).to_not contains_log(:error, "Error in minutely probe 'puma'")
144
+ end
145
+ end
146
+
147
+ context "when it does not have a complete stats payload" do
148
+ let(:log) { StringIO.new }
149
+
150
+ it "tracks whatever metrics we do have" do
151
+ use_logger_with log do
152
+ expect(Puma).to receive(:stats).and_return({
153
+ "backlog" => 1,
154
+ "running" => 5
155
+ }.to_json)
156
+
157
+ expect_gauge(:connection_backlog, 1)
158
+ expect_no_gauge(:pool_capacity)
159
+ expect_gauge(:threads, 5, :type => :running)
160
+ expect_no_gauge(:threads, :type => :max)
161
+ probe.call
162
+ end
163
+
164
+ expect(log_contents(log)).to_not contains_log(:error, "Error in minutely probe 'puma'")
165
+ end
166
+ end
167
+ end
168
+
169
+ def expect_gauge(key, value, tags = {})
170
+ expect(Appsignal).to receive(:set_gauge)
171
+ .with("puma_#{key}", value, expected_default_tags.merge(tags))
172
+ .and_call_original
173
+ end
174
+
175
+ def expect_no_gauge(key, tags = {})
176
+ expect(Appsignal).to_not receive(:set_gauge)
177
+ .with("puma_#{key}", anything, tags)
178
+ end
179
+ end
180
+ end