appsignal 2.10.8-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 (64) 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 -0
  5. data/build_matrix.yml +13 -7
  6. data/ext/agent.yml +19 -19
  7. data/ext/appsignal_extension.c +10 -1
  8. data/ext/base.rb +11 -2
  9. data/gemfiles/padrino.gemfile +2 -2
  10. data/gemfiles/rails-4.2.gemfile +9 -2
  11. data/gemfiles/rails-5.0.gemfile +1 -0
  12. data/gemfiles/rails-5.1.gemfile +1 -0
  13. data/gemfiles/rails-5.2.gemfile +1 -0
  14. data/gemfiles/rails-6.0.gemfile +1 -0
  15. data/gemfiles/resque-1.gemfile +7 -0
  16. data/gemfiles/{resque.gemfile → resque-2.gemfile} +1 -1
  17. data/lib/appsignal.rb +21 -1
  18. data/lib/appsignal/capistrano.rb +2 -0
  19. data/lib/appsignal/config.rb +6 -2
  20. data/lib/appsignal/environment.rb +126 -0
  21. data/lib/appsignal/extension/jruby.rb +10 -0
  22. data/lib/appsignal/hooks.rb +2 -0
  23. data/lib/appsignal/hooks/active_job.rb +89 -0
  24. data/lib/appsignal/hooks/net_http.rb +2 -0
  25. data/lib/appsignal/hooks/puma.rb +2 -58
  26. data/lib/appsignal/hooks/redis.rb +2 -0
  27. data/lib/appsignal/hooks/resque.rb +60 -0
  28. data/lib/appsignal/hooks/sequel.rb +2 -0
  29. data/lib/appsignal/hooks/sidekiq.rb +18 -191
  30. data/lib/appsignal/integrations/object.rb +4 -0
  31. data/lib/appsignal/integrations/que.rb +1 -1
  32. data/lib/appsignal/integrations/resque.rb +9 -12
  33. data/lib/appsignal/integrations/resque_active_job.rb +9 -24
  34. data/lib/appsignal/probes/puma.rb +61 -0
  35. data/lib/appsignal/probes/sidekiq.rb +102 -0
  36. data/lib/appsignal/rack/js_exception_catcher.rb +5 -2
  37. data/lib/appsignal/transaction.rb +32 -7
  38. data/lib/appsignal/utils/deprecation_message.rb +5 -1
  39. data/lib/appsignal/version.rb +1 -1
  40. data/lib/puma/plugin/appsignal.rb +2 -1
  41. data/spec/lib/appsignal/cli/diagnose_spec.rb +2 -1
  42. data/spec/lib/appsignal/config_spec.rb +6 -1
  43. data/spec/lib/appsignal/environment_spec.rb +167 -0
  44. data/spec/lib/appsignal/hooks/activejob_spec.rb +458 -0
  45. data/spec/lib/appsignal/hooks/puma_spec.rb +2 -181
  46. data/spec/lib/appsignal/hooks/resque_spec.rb +185 -0
  47. data/spec/lib/appsignal/hooks/sidekiq_spec.rb +292 -546
  48. data/spec/lib/appsignal/integrations/padrino_spec.rb +1 -1
  49. data/spec/lib/appsignal/integrations/que_spec.rb +25 -6
  50. data/spec/lib/appsignal/integrations/resque_active_job_spec.rb +20 -137
  51. data/spec/lib/appsignal/integrations/resque_spec.rb +20 -85
  52. data/spec/lib/appsignal/probes/puma_spec.rb +180 -0
  53. data/spec/lib/appsignal/probes/sidekiq_spec.rb +204 -0
  54. data/spec/lib/appsignal/rack/js_exception_catcher_spec.rb +9 -4
  55. data/spec/lib/appsignal/transaction_spec.rb +35 -20
  56. data/spec/lib/appsignal_spec.rb +22 -0
  57. data/spec/lib/puma/appsignal_spec.rb +1 -1
  58. data/spec/support/helpers/action_mailer_helpers.rb +25 -0
  59. data/spec/support/helpers/dependency_helper.rb +12 -0
  60. data/spec/support/helpers/env_helpers.rb +1 -1
  61. data/spec/support/helpers/environment_metdata_helper.rb +16 -0
  62. data/spec/support/helpers/transaction_helpers.rb +6 -0
  63. data/spec/support/stubs/sidekiq/api.rb +2 -2
  64. metadata +25 -5
@@ -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,145 +1,28 @@
1
- if DependencyHelper.resque_present? && DependencyHelper.active_job_present?
2
- require "active_job"
1
+ require "appsignal/integrations/resque_active_job"
3
2
 
4
- describe Appsignal::Integrations::ResqueActiveJobPlugin do
5
- let(:file) { File.expand_path("lib/appsignal/integrations/resque_active_job.rb") }
6
- let(:args) { "argument" }
7
- let(:job) { TestActiveJob.new(args) }
8
- before do
9
- load file
10
- start_agent
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) }
11
8
 
12
- class TestActiveJob < ActiveJob::Base
13
- include Appsignal::Integrations::ResqueActiveJobPlugin
14
-
15
- def perform(_)
16
- end
17
- end
18
- end
19
-
20
- def perform
21
- keep_transactions do
22
- job.perform_now
23
- end
24
- end
25
-
26
- context "without error" do
27
- it "creates a new transaction" do
28
- expect { perform }.to change { created_transactions.length }.by(1)
29
-
30
- expect(last_transaction.to_h).to include(
31
- "namespace" => Appsignal::Transaction::BACKGROUND_JOB,
32
- "action" => "TestActiveJob#perform",
33
- "error" => nil,
34
- "events" => [
35
- hash_including(
36
- "name" => "perform_job.resque",
37
- "title" => "",
38
- "body" => "",
39
- "body_format" => Appsignal::EventFormatter::DEFAULT,
40
- "count" => 1,
41
- "duration" => kind_of(Float)
42
- )
43
- ],
44
- "sample_data" => hash_including(
45
- "params" => ["argument"],
46
- "metadata" => {
47
- "id" => kind_of(String),
48
- "queue" => "default"
49
- }
50
- )
51
- )
52
- end
53
- end
54
-
55
- context "with error" do
56
- let(:job) do
57
- class BrokenTestActiveJob < ActiveJob::Base
58
- include Appsignal::Integrations::ResqueActiveJobPlugin
9
+ it "logs and prints a deprecation message on extend" do
10
+ Appsignal.logger = test_logger(log_stream)
59
11
 
60
- def perform(_)
61
- raise ExampleException, "my error message"
62
- end
63
- end
64
-
65
- BrokenTestActiveJob.new(args)
66
- end
67
-
68
- it "creates a new transaction with an error" do
69
- expect do
70
- expect { perform }.to raise_error(ExampleException, "my error message")
71
- end.to change { created_transactions.length }.by(1)
72
-
73
- expect(last_transaction.to_h).to include(
74
- "namespace" => Appsignal::Transaction::BACKGROUND_JOB,
75
- "action" => "BrokenTestActiveJob#perform",
76
- "error" => {
77
- "name" => "ExampleException",
78
- "message" => "my error message",
79
- "backtrace" => kind_of(String)
80
- },
81
- "sample_data" => hash_including(
82
- "params" => ["argument"],
83
- "metadata" => {
84
- "id" => kind_of(String),
85
- "queue" => "default"
86
- }
87
- )
88
- )
12
+ capture_std_streams(std_stream, err_stream) do
13
+ Class.new do
14
+ include Appsignal::Integrations::ResqueActiveJobPlugin
89
15
  end
90
16
  end
91
17
 
92
- context "with complex arguments" do
93
- context "with too long values" do
94
- let(:args) do
95
- {
96
- :foo => "Foo",
97
- :bar => "a" * 2001
98
- }
99
- end
100
-
101
- it "truncates large argument values" do
102
- perform
103
- expect(last_transaction.to_h).to include(
104
- "namespace" => Appsignal::Transaction::BACKGROUND_JOB,
105
- "action" => "TestActiveJob#perform",
106
- "error" => nil,
107
- "sample_data" => hash_including(
108
- "params" => ["foo" => "Foo", "bar" => "#{"a" * 2000}..."],
109
- "metadata" => {
110
- "id" => kind_of(String),
111
- "queue" => "default"
112
- }
113
- )
114
- )
115
- end
116
- end
117
-
118
- context "with parameter filtering" do
119
- let(:args) do
120
- {
121
- :foo => "Foo",
122
- :bar => "Bar"
123
- }
124
- end
125
- before { Appsignal.config[:filter_parameters] = ["foo"] }
126
-
127
- it "filters selected arguments" do
128
- perform
129
- expect(last_transaction.to_h).to include(
130
- "namespace" => Appsignal::Transaction::BACKGROUND_JOB,
131
- "action" => "TestActiveJob#perform",
132
- "error" => nil,
133
- "sample_data" => hash_including(
134
- "params" => ["foo" => "[FILTERED]", "bar" => "Bar"],
135
- "metadata" => {
136
- "id" => kind_of(String),
137
- "queue" => "default"
138
- }
139
- )
140
- )
141
- end
142
- end
143
- 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
144
27
  end
145
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