appsignal 4.0.3-java → 4.0.5-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +51 -0
  3. data/ext/agent.rb +27 -27
  4. data/lib/appsignal/check_in/cron.rb +2 -34
  5. data/lib/appsignal/check_in/scheduler.rb +192 -0
  6. data/lib/appsignal/check_in.rb +18 -0
  7. data/lib/appsignal/cli/diagnose.rb +1 -1
  8. data/lib/appsignal/config.rb +7 -0
  9. data/lib/appsignal/hooks/at_exit.rb +3 -1
  10. data/lib/appsignal/hooks/puma.rb +5 -1
  11. data/lib/appsignal/integrations/puma.rb +45 -0
  12. data/lib/appsignal/rack/abstract_middleware.rb +3 -47
  13. data/lib/appsignal/rack/body_wrapper.rb +15 -0
  14. data/lib/appsignal/rack/event_handler.rb +2 -0
  15. data/lib/appsignal/rack/hanami_middleware.rb +5 -1
  16. data/lib/appsignal/rack.rb +68 -0
  17. data/lib/appsignal/transmitter.rb +30 -7
  18. data/lib/appsignal/utils/ndjson.rb +15 -0
  19. data/lib/appsignal/utils.rb +1 -0
  20. data/lib/appsignal/version.rb +1 -1
  21. data/lib/appsignal.rb +1 -0
  22. data/spec/lib/appsignal/check_in/cron_spec.rb +202 -0
  23. data/spec/lib/appsignal/check_in/scheduler_spec.rb +443 -0
  24. data/spec/lib/appsignal/config_spec.rb +13 -0
  25. data/spec/lib/appsignal/environment_spec.rb +1 -1
  26. data/spec/lib/appsignal/hooks/at_exit_spec.rb +22 -0
  27. data/spec/lib/appsignal/hooks/puma_spec.rb +31 -23
  28. data/spec/lib/appsignal/integrations/puma_spec.rb +150 -0
  29. data/spec/lib/appsignal/probes_spec.rb +1 -6
  30. data/spec/lib/appsignal/rack/abstract_middleware_spec.rb +41 -122
  31. data/spec/lib/appsignal/rack/body_wrapper_spec.rb +29 -21
  32. data/spec/lib/appsignal/rack_spec.rb +180 -0
  33. data/spec/lib/appsignal/transmitter_spec.rb +48 -2
  34. data/spec/lib/appsignal_spec.rb +5 -0
  35. data/spec/spec_helper.rb +0 -7
  36. data/spec/support/helpers/config_helpers.rb +2 -1
  37. data/spec/support/helpers/take_at_most_helper.rb +21 -0
  38. data/spec/support/matchers/contains_log.rb +10 -3
  39. data/spec/support/mocks/hash_like.rb +10 -0
  40. data/spec/support/mocks/puma_mock.rb +43 -0
  41. metadata +11 -3
  42. data/spec/lib/appsignal/check_in_spec.rb +0 -136
@@ -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
 
@@ -5,23 +5,29 @@ describe Appsignal::Rack::BodyWrapper do
5
5
  set_current_transaction(transaction)
6
6
  end
7
7
 
8
- describe "with a body that supports all possible features" do
9
- it "reduces the supported methods to just each()" do
10
- # which is the safest thing to do, since the body is likely broken
11
- fake_body = double(
12
- :each => nil,
13
- :call => nil,
14
- :to_ary => [],
15
- :to_path => "/tmp/foo.bin",
16
- :close => nil
17
- )
8
+ it "forwards method calls to the body if the method doesn't exist" do
9
+ fake_body = double(
10
+ :body => ["some body"],
11
+ :some_method => :some_value
12
+ )
13
+
14
+ wrapped = described_class.wrap(fake_body, transaction)
15
+ expect(wrapped).to respond_to(:body)
16
+ expect(wrapped.body).to eq(["some body"])
17
+
18
+ expect(wrapped).to respond_to(:some_method)
19
+ expect(wrapped.some_method).to eq(:some_value)
20
+ end
18
21
 
19
- wrapped = described_class.wrap(fake_body, transaction)
20
- expect(wrapped).to respond_to(:each)
21
- expect(wrapped).to_not respond_to(:to_ary)
22
- expect(wrapped).to_not respond_to(:call)
23
- expect(wrapped).to respond_to(:close)
24
- end
22
+ it "doesn't respond to methods the Rack::BodyProxy doesn't respond to" do
23
+ body = Rack::BodyProxy.new(["body"])
24
+ wrapped = described_class.wrap(body, transaction)
25
+
26
+ expect(wrapped).to_not respond_to(:to_str)
27
+ expect { wrapped.to_str }.to raise_error(NoMethodError)
28
+
29
+ expect(wrapped).to_not respond_to(:body)
30
+ expect { wrapped.body }.to raise_error(NoMethodError)
25
31
  end
26
32
 
27
33
  describe "with a body only supporting each()" do
@@ -97,15 +103,17 @@ describe Appsignal::Rack::BodyWrapper do
97
103
  end
98
104
 
99
105
  describe "with a body supporting both each() and call" do
100
- it "wraps with the wrapper that conceals call() and exposes each" do
101
- fake_body = double
102
- allow(fake_body).to receive(:each)
103
- allow(fake_body).to receive(:call)
106
+ it "wraps with the wrapper that exposes each" do
107
+ fake_body = double(
108
+ :each => true,
109
+ :call => "original call"
110
+ )
104
111
 
105
112
  wrapped = described_class.wrap(fake_body, transaction)
106
113
  expect(wrapped).to respond_to(:each)
107
114
  expect(wrapped).to_not respond_to(:to_ary)
108
- expect(wrapped).to_not respond_to(:call)
115
+ expect(wrapped).to respond_to(:call)
116
+ expect(wrapped.call).to eq("original call")
109
117
  expect(wrapped).to_not respond_to(:to_path)
110
118
  expect(wrapped).to respond_to(:close)
111
119
  end
@@ -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