appsignal 4.0.4-java → 4.0.5-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,34 +1,45 @@
1
1
  describe Appsignal::Hooks::PumaHook do
2
2
  context "with puma" do
3
- before(:context) do
4
- class Puma
5
- def self.stats
6
- end
3
+ let(:puma_version) { "6.0.0" }
4
+ before do
5
+ stub_const("Puma", PumaMock)
6
+ stub_const("Puma::Const::VERSION", puma_version)
7
+ end
8
+
9
+ describe "#dependencies_present?" do
10
+ subject { described_class.new.dependencies_present? }
11
+
12
+ context "when Puma present" do
13
+ context "when Puma is newer than version 3.0.0" do
14
+ let(:puma_version) { "3.0.0" }
7
15
 
8
- def self.cli_config
9
- @cli_config ||= CliConfig.new
16
+ it { is_expected.to be_truthy }
10
17
  end
11
- end
12
18
 
13
- class CliConfig
14
- attr_accessor :options
19
+ context "when Puma is older than version 3.0.0" do
20
+ let(:puma_version) { "2.9.9" }
15
21
 
16
- def initialize
17
- @options = {}
22
+ it { is_expected.to be_falsey }
18
23
  end
19
24
  end
20
- end
21
- after(:context) { Object.send(:remove_const, :Puma) }
22
25
 
23
- describe "#dependencies_present?" do
24
- subject { described_class.new.dependencies_present? }
26
+ context "when Puma is not present" do
27
+ before do
28
+ hide_const("Puma")
29
+ end
25
30
 
26
- it { is_expected.to be_truthy }
31
+ it { is_expected.to be_falsey }
32
+ end
27
33
  end
28
34
 
29
35
  describe "installation" do
30
36
  before { Appsignal::Probes.probes.clear }
31
37
 
38
+ it "adds the Puma::Server patch" do
39
+ Appsignal::Hooks::PumaHook.new.install
40
+ expect(::Puma::Server.included_modules).to include(Appsignal::Integrations::PumaServer)
41
+ end
42
+
32
43
  context "when not clustered mode" do
33
44
  it "does not add AppSignal stop behavior Puma::Cluster" do
34
45
  expect(defined?(::Puma::Cluster)).to be_falsy
@@ -39,15 +50,12 @@ describe Appsignal::Hooks::PumaHook do
39
50
 
40
51
  context "when in clustered mode" do
41
52
  before do
42
- class Puma
43
- class Cluster
44
- def stop_workers
45
- @called = true
46
- end
53
+ stub_const("Puma::Cluster", Class.new do
54
+ def stop_workers
55
+ @called = true
47
56
  end
48
- end
57
+ end)
49
58
  end
50
- after { Puma.send(:remove_const, :Cluster) }
51
59
 
52
60
  it "adds behavior to Puma::Cluster.stop_workers" do
53
61
  Appsignal::Hooks::PumaHook.new.install
@@ -0,0 +1,150 @@
1
+ require "appsignal/integrations/puma"
2
+
3
+ describe Appsignal::Integrations::PumaServer do
4
+ describe "#lowlevel_error" do
5
+ before do
6
+ stub_const("Puma", PumaMock)
7
+ stub_const("Puma::Server", puma_server)
8
+ start_agent
9
+ end
10
+ let(:queue_start_time) { fixed_time * 1_000 }
11
+ let(:env) do
12
+ Rack::MockRequest.env_for(
13
+ "/some/path",
14
+ "REQUEST_METHOD" => "GET",
15
+ :params => { "page" => 2, "query" => "lorem" },
16
+ "rack.session" => { "session" => "data", "user_id" => 123 },
17
+ "HTTP_X_REQUEST_START" => "t=#{queue_start_time.to_i}" # in milliseconds
18
+ )
19
+ end
20
+ let(:server) { Puma::Server.new }
21
+ let(:error) { ExampleException.new("error message") }
22
+ around { |example| keep_transactions { example.run } }
23
+ before { Appsignal::Hooks::PumaHook.new.install }
24
+
25
+ def lowlevel_error(error, env, status = nil)
26
+ result =
27
+ if status
28
+ server.lowlevel_error(error, env, status)
29
+ else
30
+ server.lowlevel_error(error, env)
31
+ end
32
+ # Transaction is normally closed by the EventHandler's RACK_AFTER_REPLY hook
33
+ last_transaction&.complete
34
+ result
35
+ end
36
+
37
+ describe "error reporting" do
38
+ let(:puma_server) { default_puma_server_mock }
39
+
40
+ context "with active transaction" do
41
+ before { create_transaction }
42
+
43
+ it "reports the error to the transaction" do
44
+ expect do
45
+ lowlevel_error(error, env)
46
+ end.to_not(change { created_transactions.count })
47
+
48
+ expect(last_transaction).to have_error("ExampleException", "error message")
49
+ expect(last_transaction).to include_tags("reported_by" => "puma_lowlevel_error")
50
+ end
51
+ end
52
+
53
+ # This shouldn't happen if the EventHandler is set up correctly, but if
54
+ # it's not it will create a new transaction.
55
+ context "without active transaction" do
56
+ it "creates a new transaction with the error" do
57
+ expect do
58
+ lowlevel_error(error, env)
59
+ end.to change { created_transactions.count }.by(1)
60
+
61
+ expect(last_transaction).to have_error("ExampleException", "error message")
62
+ expect(last_transaction).to include_tags("reported_by" => "puma_lowlevel_error")
63
+ end
64
+ end
65
+
66
+ it "doesn't report internal Puma errors" do
67
+ expect do
68
+ lowlevel_error(Puma::MiniSSL::SSLError.new("error message"), env)
69
+ lowlevel_error(Puma::HttpParserError.new("error message"), env)
70
+ lowlevel_error(Puma::HttpParserError501.new("error message"), env)
71
+ end.to_not(change { created_transactions.count })
72
+ end
73
+
74
+ describe "request metadata" do
75
+ it "sets request metadata" do
76
+ lowlevel_error(error, env)
77
+
78
+ expect(last_transaction).to include_metadata(
79
+ "request_method" => "GET",
80
+ "method" => "GET",
81
+ "request_path" => "/some/path",
82
+ "path" => "/some/path"
83
+ )
84
+ expect(last_transaction).to include_environment(
85
+ "REQUEST_METHOD" => "GET",
86
+ "PATH_INFO" => "/some/path"
87
+ # and more, but we don't need to test Rack mock defaults
88
+ )
89
+ end
90
+
91
+ it "sets request parameters" do
92
+ lowlevel_error(error, env)
93
+
94
+ expect(last_transaction).to include_params(
95
+ "page" => "2",
96
+ "query" => "lorem"
97
+ )
98
+ end
99
+
100
+ it "sets session data" do
101
+ lowlevel_error(error, env)
102
+
103
+ expect(last_transaction).to include_session_data("session" => "data", "user_id" => 123)
104
+ end
105
+
106
+ it "sets the queue start" do
107
+ lowlevel_error(error, env)
108
+
109
+ expect(last_transaction).to have_queue_start(queue_start_time)
110
+ end
111
+ end
112
+ end
113
+
114
+ context "with Puma::Server#lowlevel_error accepting 3 arguments" do
115
+ let(:puma_server) { default_puma_server_mock }
116
+
117
+ it "calls the super class with 3 arguments" do
118
+ result = lowlevel_error(error, env, 501)
119
+ expect(result).to eq([501, {}, ""])
120
+
121
+ expect(last_transaction).to include_tags("response_status" => 501)
122
+ end
123
+ end
124
+
125
+ context "with Puma::Server#lowlevel_error accepting 2 arguments" do
126
+ let(:puma_server) do
127
+ Class.new do
128
+ def lowlevel_error(_error, _env)
129
+ [500, {}, ""]
130
+ end
131
+ end
132
+ end
133
+
134
+ it "calls the super class with 3 arguments" do
135
+ result = lowlevel_error(error, env)
136
+ expect(result).to eq([500, {}, ""])
137
+
138
+ expect(last_transaction).to include_tags("response_status" => 500)
139
+ end
140
+ end
141
+ end
142
+
143
+ def default_puma_server_mock
144
+ Class.new do
145
+ def lowlevel_error(_error, _env, status = 500)
146
+ [status, {}, ""]
147
+ end
148
+ end
149
+ end
150
+ end
@@ -244,12 +244,7 @@ describe Appsignal::Probes do
244
244
  end
245
245
 
246
246
  describe ".unregister" do
247
- let(:log_stream) { StringIO.new }
248
- let(:log) { log_contents(log_stream) }
249
- before do
250
- Appsignal.internal_logger = test_logger(log_stream)
251
- speed_up_tests!
252
- end
247
+ before { speed_up_tests! }
253
248
 
254
249
  it "does not call the initialized probe after unregistering" do
255
250
  probe1_calls = 0
@@ -1,19 +1,8 @@
1
1
  describe Appsignal::Rack::AbstractMiddleware do
2
- class HashLike < Hash
3
- def initialize(value)
4
- @value = value
5
- end
6
-
7
- def to_h
8
- @value
9
- end
10
- end
11
-
12
2
  let(:app) { DummyApp.new }
13
- let(:request_path) { "/some/path" }
14
3
  let(:env) do
15
4
  Rack::MockRequest.env_for(
16
- request_path,
5
+ "/some/path",
17
6
  "REQUEST_METHOD" => "GET",
18
7
  :params => { "page" => 2, "query" => "lorem" },
19
8
  "rack.session" => { "session" => "data", "user_id" => 123 }
@@ -174,13 +163,17 @@ describe Appsignal::Rack::AbstractMiddleware do
174
163
  end
175
164
  end
176
165
 
166
+ # Partial duplicate tests from Appsignal::Rack::ApplyRackRequest that
167
+ # ensure the request metadata is set on via the AbstractMiddleware.
177
168
  describe "request metadata" do
178
169
  it "sets request metadata" do
179
170
  env.merge!("PATH_INFO" => "/some/path", "REQUEST_METHOD" => "GET")
180
171
  make_request
181
172
 
182
173
  expect(last_transaction).to include_metadata(
174
+ "request_method" => "GET",
183
175
  "method" => "GET",
176
+ "request_path" => "/some/path",
184
177
  "path" => "/some/path"
185
178
  )
186
179
  expect(last_transaction).to include_environment(
@@ -190,36 +183,6 @@ describe Appsignal::Rack::AbstractMiddleware do
190
183
  )
191
184
  end
192
185
 
193
- context "with an invalid HTTP request method" do
194
- it "stores the invalid HTTP request method" do
195
- env["REQUEST_METHOD"] = "FOO"
196
- make_request
197
-
198
- expect(last_transaction).to include_metadata("method" => "FOO")
199
- end
200
- end
201
-
202
- context "when fetching the request method raises an error" do
203
- class BrokenRequestMethodRequest < Rack::Request
204
- def request_method
205
- raise "uh oh!"
206
- end
207
- end
208
-
209
- let(:options) { { :request_class => BrokenRequestMethodRequest } }
210
-
211
- it "does not store the invalid HTTP request method" do
212
- env["REQUEST_METHOD"] = "FOO"
213
- logs = capture_logs { make_request }
214
-
215
- expect(last_transaction).to_not include_metadata("method" => anything)
216
- expect(logs).to contains_log(
217
- :error,
218
- "Exception while fetching the HTTP request method: RuntimeError: uh oh"
219
- )
220
- end
221
- end
222
-
223
186
  it "sets request parameters" do
224
187
  make_request
225
188
 
@@ -229,107 +192,63 @@ describe Appsignal::Rack::AbstractMiddleware do
229
192
  )
230
193
  end
231
194
 
232
- context "when setting custom params" do
233
- let(:app) do
234
- DummyApp.new do |_env|
235
- Appsignal::Transaction.current.set_params("custom" => "param")
236
- end
237
- end
238
-
239
- it "allow custom request parameters to be set" do
240
- make_request
241
-
242
- expect(last_transaction).to include_params("custom" => "param")
243
- end
244
- end
245
-
246
- context "when fetching the request method raises an error" do
247
- class BrokenRequestParamsRequest < Rack::Request
248
- def params
249
- raise "uh oh!"
250
- end
251
- end
252
-
253
- let(:options) do
254
- { :request_class => BrokenRequestParamsRequest, :params_method => :params }
255
- end
256
-
257
- it "does not store the invalid HTTP request method" do
258
- logs = capture_logs { make_request }
259
-
260
- expect(last_transaction).to_not include_params
261
- expect(logs).to contains_log(
262
- :error,
263
- "Exception while fetching params " \
264
- "from 'BrokenRequestParamsRequest#params': RuntimeError uh oh!"
265
- )
266
- end
267
- end
268
-
269
195
  it "sets session data" do
270
196
  make_request
271
197
 
272
198
  expect(last_transaction).to include_session_data("session" => "data", "user_id" => 123)
273
199
  end
274
200
 
275
- it "sets session data if the session is a Hash-like type" do
276
- env["rack.session"] = HashLike.new("hash-like" => "value", "user_id" => 123)
277
- make_request
278
-
279
- expect(last_transaction).to include_session_data("hash-like" => "value", "user_id" => 123)
280
- end
281
- end
282
-
283
- context "with queue start header" do
284
- let(:queue_start_time) { fixed_time * 1_000 }
201
+ context "with queue start header" do
202
+ let(:queue_start_time) { fixed_time * 1_000 }
285
203
 
286
- it "sets the queue start" do
287
- env["HTTP_X_REQUEST_START"] = "t=#{queue_start_time.to_i}" # in milliseconds
288
- make_request
204
+ it "sets the queue start" do
205
+ env["HTTP_X_REQUEST_START"] = "t=#{queue_start_time.to_i}" # in milliseconds
206
+ make_request
289
207
 
290
- expect(last_transaction).to have_queue_start(queue_start_time)
208
+ expect(last_transaction).to have_queue_start(queue_start_time)
209
+ end
291
210
  end
292
- end
293
211
 
294
- class FilteredRequest
295
- attr_reader :env
212
+ class SomeFilteredRequest
213
+ attr_reader :env
296
214
 
297
- def initialize(env)
298
- @env = env
299
- end
215
+ def initialize(env)
216
+ @env = env
217
+ end
300
218
 
301
- def path
302
- "/static/path"
303
- end
219
+ def path
220
+ "/static/path"
221
+ end
304
222
 
305
- def request_method
306
- "GET"
307
- end
223
+ def request_method
224
+ "GET"
225
+ end
308
226
 
309
- def filtered_params
310
- { "abc" => "123" }
311
- end
227
+ def filtered_params
228
+ { "abc" => "123" }
229
+ end
312
230
 
313
- def session
314
- { "data" => "value" }
231
+ def session
232
+ { "data" => "value" }
233
+ end
315
234
  end
316
- end
317
235
 
318
- context "with overridden request class and params method" do
319
- let(:options) do
320
- { :request_class => FilteredRequest, :params_method => :filtered_params }
321
- end
236
+ context "with overridden request class and params method" do
237
+ let(:options) do
238
+ { :request_class => SomeFilteredRequest, :params_method => :filtered_params }
239
+ end
322
240
 
323
- it "uses the overridden request class and params method to fetch params" do
324
- make_request
241
+ it "uses the overridden request class and params method to fetch params" do
242
+ make_request
325
243
 
326
- expect(last_transaction).to include_params("abc" => "123")
327
- end
244
+ expect(last_transaction).to include_params("abc" => "123")
245
+ end
328
246
 
329
- it "uses the overridden request class to fetch session data" do
330
- make_request
247
+ it "uses the overridden request class to fetch session data" do
248
+ make_request
331
249
 
332
- expect(last_transaction).to include_session_data("data" => "value")
250
+ expect(last_transaction).to include_session_data("data" => "value")
251
+ end
333
252
  end
334
253
  end
335
254
 
@@ -61,3 +61,183 @@ describe Appsignal::Rack::Utils do
61
61
  end
62
62
  end
63
63
  end
64
+
65
+ describe Appsignal::Rack::ApplyRackRequest do
66
+ describe "#apply_to" do
67
+ let(:merged_env) do
68
+ Rack::MockRequest.env_for(
69
+ "/some/path",
70
+ {
71
+ "REQUEST_METHOD" => "GET",
72
+ :params => { "page" => 2, "query" => "lorem" },
73
+ "rack.session" => { "session" => "data", "user_id" => 123 }
74
+ }.merge(env)
75
+ )
76
+ end
77
+ let(:env) { {} }
78
+ let(:request) { ::Rack::Request.new(merged_env) }
79
+ let(:options) { {} }
80
+ let(:helper) { described_class.new(request, options) }
81
+ let(:transaction) { http_request_transaction }
82
+ before { start_agent }
83
+
84
+ def apply_to(transaction)
85
+ helper.apply_to(transaction)
86
+ transaction._sample
87
+ end
88
+
89
+ it "sets request metadata" do
90
+ apply_to(transaction)
91
+
92
+ expect(transaction).to include_metadata(
93
+ "method" => "GET",
94
+ "path" => "/some/path"
95
+ )
96
+ expect(transaction).to include_environment(
97
+ "REQUEST_METHOD" => "GET",
98
+ "PATH_INFO" => "/some/path"
99
+ # and more, but we don't need to test Rack mock defaults
100
+ )
101
+ end
102
+
103
+ context "with an invalid HTTP request method" do
104
+ let(:env) { { "REQUEST_METHOD" => "FOO" } }
105
+
106
+ it "stores the invalid HTTP request method" do
107
+ apply_to(transaction)
108
+
109
+ expect(transaction).to include_metadata("method" => "FOO")
110
+ end
111
+ end
112
+
113
+ context "when fetching the request method raises an error" do
114
+ class BrokenRequestMethodRequest < Rack::Request
115
+ def request_method
116
+ raise "uh oh!"
117
+ end
118
+ end
119
+
120
+ let(:env) { { "REQUEST_METHOD" => "FOO" } }
121
+ let(:request) { BrokenRequestMethodRequest.new(merged_env) }
122
+
123
+ it "does not store the invalid HTTP request method" do
124
+ logs = capture_logs { apply_to(transaction) }
125
+
126
+ expect(transaction).to_not include_metadata("method" => anything)
127
+ expect(logs).to contains_log(
128
+ :error,
129
+ "Exception while fetching the HTTP request method: RuntimeError: uh oh"
130
+ )
131
+ end
132
+ end
133
+
134
+ it "sets request parameters" do
135
+ apply_to(transaction)
136
+
137
+ expect(transaction).to include_params(
138
+ "page" => "2",
139
+ "query" => "lorem"
140
+ )
141
+ end
142
+
143
+ context "when params_method isn't set" do
144
+ let(:options) { { :params_method => nil } }
145
+
146
+ it "reports no params" do
147
+ apply_to(transaction)
148
+
149
+ expect(transaction).to_not include_params
150
+ end
151
+ end
152
+
153
+ context "when fetching the request method raises an error" do
154
+ class BrokenRequestParamsRequest < Rack::Request
155
+ def params
156
+ raise "uh oh!"
157
+ end
158
+ end
159
+
160
+ let(:request) { BrokenRequestParamsRequest.new(merged_env) }
161
+ let(:options) { { :params_method => :params } }
162
+
163
+ it "does not store the invalid HTTP request method" do
164
+ logs = capture_logs { apply_to(transaction) }
165
+
166
+ expect(transaction).to_not include_params
167
+ expect(logs).to contains_log(
168
+ :error,
169
+ "Exception while fetching params " \
170
+ "from 'BrokenRequestParamsRequest#params': RuntimeError uh oh!"
171
+ )
172
+ end
173
+ end
174
+
175
+ it "sets session data" do
176
+ apply_to(transaction)
177
+
178
+ expect(transaction).to include_session_data("session" => "data", "user_id" => 123)
179
+ end
180
+
181
+ context "with Hash-like session data" do
182
+ let(:env) { { "rack.session" => HashLike.new("hash-like" => "value", "user_id" => 123) } }
183
+
184
+ it "sets session data" do
185
+ apply_to(transaction)
186
+
187
+ expect(transaction).to include_session_data("hash-like" => "value", "user_id" => 123)
188
+ end
189
+ end
190
+
191
+ context "with queue start header" do
192
+ let(:queue_start_time) { fixed_time * 1_000 }
193
+ let(:env) { { "HTTP_X_REQUEST_START" => "t=#{queue_start_time.to_i}" } } # in milliseconds
194
+
195
+ it "sets the queue start" do
196
+ apply_to(transaction)
197
+
198
+ expect(transaction).to have_queue_start(queue_start_time)
199
+ end
200
+ end
201
+
202
+ class RackFilteredRequest
203
+ attr_reader :env
204
+
205
+ def initialize(env)
206
+ @env = env
207
+ end
208
+
209
+ def path
210
+ "/static/path"
211
+ end
212
+
213
+ def request_method
214
+ "GET"
215
+ end
216
+
217
+ def filtered_params
218
+ { "abc" => "123" }
219
+ end
220
+
221
+ def session
222
+ { "data" => "value" }
223
+ end
224
+ end
225
+
226
+ context "with overridden request class and params method" do
227
+ let(:request) { RackFilteredRequest.new(env) }
228
+ let(:options) { { :params_method => :filtered_params } }
229
+
230
+ it "uses the overridden request class and params method to fetch params" do
231
+ apply_to(transaction)
232
+
233
+ expect(transaction).to include_params("abc" => "123")
234
+ end
235
+
236
+ it "uses the overridden request class to fetch session data" do
237
+ apply_to(transaction)
238
+
239
+ expect(transaction).to include_session_data("data" => "value")
240
+ end
241
+ end
242
+ end
243
+ end
data/spec/spec_helper.rb CHANGED
@@ -168,13 +168,6 @@ RSpec.configure do |config|
168
168
  end
169
169
 
170
170
  def stop_minutely_probes
171
- thread =
172
- begin
173
- Appsignal::Probes.class_variable_get(:@@thread) # Fetch old thread
174
- rescue NameError
175
- nil
176
- end
177
171
  Appsignal::Probes.stop
178
- thread&.join # Wait for old thread to exit
179
172
  end
180
173
  end
@@ -46,7 +46,7 @@ module ConfigHelpers
46
46
  end
47
47
  module_function :build_config
48
48
 
49
- def start_agent(env: "production", options: {})
49
+ def start_agent(env: "production", options: {}, internal_logger: nil)
50
50
  env = "production" if env == :default
51
51
  env ||= "production"
52
52
  Appsignal.configure(env, :root_path => project_fixture_path) do |config|
@@ -55,6 +55,7 @@ module ConfigHelpers
55
55
  end
56
56
  end
57
57
  Appsignal.start
58
+ Appsignal.internal_logger = internal_logger if internal_logger
58
59
  end
59
60
 
60
61
  def clear_integration_env_vars!