appsignal 3.9.3-java → 3.11.0-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/.github/workflows/ci.yml +22 -19
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +180 -0
- data/Gemfile +1 -0
- data/README.md +0 -1
- data/Rakefile +1 -1
- data/benchmark.rake +99 -42
- data/build_matrix.yml +10 -12
- data/gemfiles/webmachine1.gemfile +5 -4
- data/lib/appsignal/cli/demo.rb +0 -1
- data/lib/appsignal/config.rb +57 -97
- data/lib/appsignal/demo.rb +15 -20
- data/lib/appsignal/environment.rb +6 -1
- data/lib/appsignal/event_formatter/rom/sql_formatter.rb +1 -0
- data/lib/appsignal/event_formatter.rb +3 -2
- data/lib/appsignal/helpers/instrumentation.rb +490 -16
- data/lib/appsignal/hooks/action_cable.rb +21 -16
- data/lib/appsignal/hooks/active_job.rb +15 -14
- data/lib/appsignal/hooks/delayed_job.rb +1 -1
- data/lib/appsignal/hooks/shoryuken.rb +3 -63
- data/lib/appsignal/integrations/action_cable.rb +5 -7
- data/lib/appsignal/integrations/active_support_notifications.rb +1 -0
- data/lib/appsignal/integrations/capistrano/capistrano_2_tasks.rb +36 -35
- data/lib/appsignal/integrations/data_mapper.rb +1 -0
- data/lib/appsignal/integrations/delayed_job_plugin.rb +27 -33
- data/lib/appsignal/integrations/dry_monitor.rb +1 -0
- data/lib/appsignal/integrations/excon.rb +1 -0
- data/lib/appsignal/integrations/http.rb +1 -0
- data/lib/appsignal/integrations/net_http.rb +1 -0
- data/lib/appsignal/integrations/object.rb +6 -0
- data/lib/appsignal/integrations/padrino.rb +21 -25
- data/lib/appsignal/integrations/que.rb +13 -20
- data/lib/appsignal/integrations/railtie.rb +1 -1
- data/lib/appsignal/integrations/rake.rb +45 -15
- data/lib/appsignal/integrations/redis.rb +1 -0
- data/lib/appsignal/integrations/redis_client.rb +1 -0
- data/lib/appsignal/integrations/resque.rb +2 -5
- data/lib/appsignal/integrations/shoryuken.rb +75 -0
- data/lib/appsignal/integrations/sidekiq.rb +7 -25
- data/lib/appsignal/integrations/unicorn.rb +1 -0
- data/lib/appsignal/integrations/webmachine.rb +12 -9
- data/lib/appsignal/logger.rb +7 -3
- data/lib/appsignal/probes/helpers.rb +1 -0
- data/lib/appsignal/probes/mri.rb +1 -0
- data/lib/appsignal/probes/sidekiq.rb +1 -0
- data/lib/appsignal/probes.rb +3 -0
- data/lib/appsignal/rack/abstract_middleware.rb +67 -24
- data/lib/appsignal/rack/body_wrapper.rb +143 -0
- data/lib/appsignal/rack/event_handler.rb +39 -8
- data/lib/appsignal/rack/generic_instrumentation.rb +6 -4
- data/lib/appsignal/rack/grape_middleware.rb +3 -2
- data/lib/appsignal/rack/hanami_middleware.rb +1 -1
- data/lib/appsignal/rack/instrumentation_middleware.rb +62 -0
- data/lib/appsignal/rack/rails_instrumentation.rb +1 -3
- data/lib/appsignal/rack/sinatra_instrumentation.rb +1 -3
- data/lib/appsignal/rack/streaming_listener.rb +14 -59
- data/lib/appsignal/rack.rb +60 -0
- data/lib/appsignal/span.rb +1 -0
- data/lib/appsignal/transaction.rb +353 -104
- data/lib/appsignal/utils/data.rb +0 -1
- data/lib/appsignal/utils/hash_sanitizer.rb +0 -1
- data/lib/appsignal/utils/integration_logger.rb +0 -13
- data/lib/appsignal/utils/integration_memory_logger.rb +0 -13
- data/lib/appsignal/utils/json.rb +0 -1
- data/lib/appsignal/utils/query_params_sanitizer.rb +0 -1
- data/lib/appsignal/utils/stdout_and_logger_message.rb +0 -1
- data/lib/appsignal/utils.rb +6 -0
- data/lib/appsignal/version.rb +1 -1
- data/lib/appsignal.rb +9 -6
- data/spec/lib/appsignal/capistrano2_spec.rb +1 -1
- data/spec/lib/appsignal/config_spec.rb +139 -43
- data/spec/lib/appsignal/hooks/action_cable_spec.rb +43 -74
- data/spec/lib/appsignal/hooks/activejob_spec.rb +9 -0
- data/spec/lib/appsignal/hooks/delayed_job_spec.rb +2 -443
- data/spec/lib/appsignal/hooks/rake_spec.rb +100 -17
- data/spec/lib/appsignal/hooks/shoryuken_spec.rb +0 -171
- data/spec/lib/appsignal/integrations/delayed_job_plugin_spec.rb +459 -0
- data/spec/lib/appsignal/integrations/padrino_spec.rb +181 -131
- data/spec/lib/appsignal/integrations/que_spec.rb +3 -4
- data/spec/lib/appsignal/integrations/shoryuken_spec.rb +167 -0
- data/spec/lib/appsignal/integrations/sidekiq_spec.rb +4 -4
- data/spec/lib/appsignal/integrations/sinatra_spec.rb +10 -2
- data/spec/lib/appsignal/integrations/webmachine_spec.rb +77 -17
- data/spec/lib/appsignal/rack/abstract_middleware_spec.rb +144 -11
- data/spec/lib/appsignal/rack/body_wrapper_spec.rb +263 -0
- data/spec/lib/appsignal/rack/event_handler_spec.rb +81 -10
- data/spec/lib/appsignal/rack/generic_instrumentation_spec.rb +70 -17
- data/spec/lib/appsignal/rack/grape_middleware_spec.rb +1 -1
- data/spec/lib/appsignal/rack/instrumentation_middleware_spec.rb +38 -0
- data/spec/lib/appsignal/rack/rails_instrumentation_spec.rb +4 -2
- data/spec/lib/appsignal/rack/streaming_listener_spec.rb +43 -120
- data/spec/lib/appsignal/rack_spec.rb +63 -0
- data/spec/lib/appsignal/transaction_spec.rb +1675 -953
- data/spec/lib/appsignal/utils/integration_logger_spec.rb +12 -16
- data/spec/lib/appsignal/utils/integration_memory_logger_spec.rb +0 -10
- data/spec/lib/appsignal_spec.rb +517 -13
- data/spec/support/helpers/transaction_helpers.rb +44 -20
- data/spec/support/matchers/transaction.rb +15 -1
- data/spec/support/mocks/dummy_app.rb +1 -1
- data/spec/support/testing.rb +1 -1
- metadata +12 -4
- data/support/check_versions +0 -22
|
@@ -15,31 +15,64 @@ if DependencyHelper.webmachine_present?
|
|
|
15
15
|
|
|
16
16
|
describe Appsignal::Integrations::WebmachineIntegration do
|
|
17
17
|
let(:request) do
|
|
18
|
-
Webmachine::Request.new(
|
|
18
|
+
Webmachine::Request.new(
|
|
19
|
+
"GET",
|
|
20
|
+
"http://google.com:80/foo?param1=value1¶m2=value2",
|
|
21
|
+
{
|
|
22
|
+
"REQUEST_METHOD" => "GET",
|
|
23
|
+
"PATH_INFO" => "/some/path",
|
|
24
|
+
"ignored_header" => "something"
|
|
25
|
+
},
|
|
26
|
+
nil
|
|
27
|
+
)
|
|
19
28
|
end
|
|
20
|
-
let(:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
let(:app) do
|
|
30
|
+
proc do
|
|
31
|
+
def to_html
|
|
32
|
+
"Some HTML"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
let(:resource) do
|
|
37
|
+
app_block = app
|
|
38
|
+
Class.new(Webmachine::Resource) do
|
|
39
|
+
class_eval(&app_block) if app_block
|
|
25
40
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
expect(request).to respond_to(:query)
|
|
41
|
+
def self.name
|
|
42
|
+
"MyResource"
|
|
43
|
+
end
|
|
30
44
|
end
|
|
31
45
|
end
|
|
46
|
+
let(:resource_instance) { resource.new(request, response) }
|
|
47
|
+
let(:response) { Webmachine::Response.new }
|
|
48
|
+
let(:fsm) { Webmachine::Decision::FSM.new(resource_instance, request, response) }
|
|
49
|
+
before { start_agent }
|
|
50
|
+
around { |example| keep_transactions { example.run } }
|
|
32
51
|
|
|
33
52
|
describe "#run" do
|
|
34
|
-
before { allow(fsm).to receive(:call).and_call_original }
|
|
35
|
-
|
|
36
53
|
it "creates a transaction" do
|
|
37
54
|
expect { fsm.run }.to(change { created_transactions.count }.by(1))
|
|
38
55
|
end
|
|
39
56
|
|
|
40
57
|
it "sets the action" do
|
|
41
58
|
fsm.run
|
|
42
|
-
expect(last_transaction).to have_action("
|
|
59
|
+
expect(last_transaction).to have_action("MyResource#GET")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
context "with action already set" do
|
|
63
|
+
let(:app) do
|
|
64
|
+
proc do
|
|
65
|
+
def to_html
|
|
66
|
+
Appsignal.set_action("Custom Action")
|
|
67
|
+
"Some HTML"
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it "doesn't overwrite the action" do
|
|
73
|
+
fsm.run
|
|
74
|
+
expect(last_transaction).to have_action("Custom Action")
|
|
75
|
+
end
|
|
43
76
|
end
|
|
44
77
|
|
|
45
78
|
it "records an instrumentation event" do
|
|
@@ -47,16 +80,43 @@ if DependencyHelper.webmachine_present?
|
|
|
47
80
|
expect(last_transaction).to include_event("name" => "process_action.webmachine")
|
|
48
81
|
end
|
|
49
82
|
|
|
83
|
+
it "sets the params" do
|
|
84
|
+
fsm.run
|
|
85
|
+
expect(last_transaction).to include_params("param1" => "value1", "param2" => "value2")
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it "sets the headers" do
|
|
89
|
+
fsm.run
|
|
90
|
+
expect(last_transaction).to include_environment(
|
|
91
|
+
"REQUEST_METHOD" => "GET",
|
|
92
|
+
"PATH_INFO" => "/some/path"
|
|
93
|
+
)
|
|
94
|
+
end
|
|
95
|
+
|
|
50
96
|
it "closes the transaction" do
|
|
51
97
|
fsm.run
|
|
52
98
|
expect(last_transaction).to be_completed
|
|
53
99
|
expect(current_transaction?).to be_falsy
|
|
54
100
|
end
|
|
55
101
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
102
|
+
context "with parent transaction" do
|
|
103
|
+
let(:transaction) { http_request_transaction }
|
|
104
|
+
before { set_current_transaction(transaction) }
|
|
105
|
+
|
|
106
|
+
it "sets the action" do
|
|
107
|
+
fsm.run
|
|
108
|
+
expect(last_transaction).to have_action("MyResource#GET")
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
it "sets the params" do
|
|
112
|
+
fsm.run
|
|
113
|
+
last_transaction._sample
|
|
114
|
+
expect(last_transaction).to include_params("param1" => "value1", "param2" => "value2")
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it "does not close the transaction" do
|
|
118
|
+
expect(last_transaction).to_not be_completed
|
|
119
|
+
end
|
|
60
120
|
end
|
|
61
121
|
end
|
|
62
122
|
|
|
@@ -5,7 +5,8 @@ describe Appsignal::Rack::AbstractMiddleware do
|
|
|
5
5
|
Rack::MockRequest.env_for(
|
|
6
6
|
request_path,
|
|
7
7
|
"REQUEST_METHOD" => "GET",
|
|
8
|
-
:params => { "page" => 2, "query" => "lorem" }
|
|
8
|
+
:params => { "page" => 2, "query" => "lorem" },
|
|
9
|
+
"rack.session" => { "session" => "data", "user_id" => 123 }
|
|
9
10
|
)
|
|
10
11
|
end
|
|
11
12
|
let(:options) { {} }
|
|
@@ -45,6 +46,11 @@ describe Appsignal::Rack::AbstractMiddleware do
|
|
|
45
46
|
expect(last_transaction).to have_namespace(Appsignal::Transaction::HTTP_REQUEST)
|
|
46
47
|
end
|
|
47
48
|
|
|
49
|
+
it "wraps the response body in a BodyWrapper subclass" do
|
|
50
|
+
_status, _headers, body = make_request
|
|
51
|
+
expect(body).to be_kind_of(Appsignal::Rack::BodyWrapper)
|
|
52
|
+
end
|
|
53
|
+
|
|
48
54
|
context "without an error" do
|
|
49
55
|
before { make_request }
|
|
50
56
|
|
|
@@ -56,8 +62,20 @@ describe Appsignal::Rack::AbstractMiddleware do
|
|
|
56
62
|
expect(last_transaction).to_not have_error
|
|
57
63
|
end
|
|
58
64
|
|
|
59
|
-
|
|
60
|
-
|
|
65
|
+
context "without :instrument_event_name option set" do
|
|
66
|
+
let(:options) { {} }
|
|
67
|
+
|
|
68
|
+
it "does not record an instrumentation event" do
|
|
69
|
+
expect(last_transaction).to_not include_event
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
context "with :instrument_event_name option set" do
|
|
74
|
+
let(:options) { { :instrument_event_name => "event_name.category" } }
|
|
75
|
+
|
|
76
|
+
it "records an instrumentation event" do
|
|
77
|
+
expect(last_transaction).to include_event(:name => "event_name.category")
|
|
78
|
+
end
|
|
61
79
|
end
|
|
62
80
|
|
|
63
81
|
it "completes the transaction" do
|
|
@@ -66,8 +84,8 @@ describe Appsignal::Rack::AbstractMiddleware do
|
|
|
66
84
|
.to be_kind_of(Appsignal::Transaction::NilTransaction)
|
|
67
85
|
end
|
|
68
86
|
|
|
69
|
-
context "when
|
|
70
|
-
let(:options) { { :
|
|
87
|
+
context "when instrument_event_name option is nil" do
|
|
88
|
+
let(:options) { { :instrument_event_name => nil } }
|
|
71
89
|
|
|
72
90
|
it "does not record an instrumentation event" do
|
|
73
91
|
expect(last_transaction).to_not include_events
|
|
@@ -148,21 +166,61 @@ describe Appsignal::Rack::AbstractMiddleware do
|
|
|
148
166
|
end
|
|
149
167
|
|
|
150
168
|
context "with appsignal.route env" do
|
|
169
|
+
before { env["appsignal.route"] = "POST /my-route" }
|
|
170
|
+
|
|
151
171
|
it "reports the appsignal.route value as the action name" do
|
|
152
|
-
env["appsignal.route"] = "POST /my-route"
|
|
153
172
|
make_request
|
|
154
173
|
|
|
155
174
|
expect(last_transaction).to have_action("POST /my-route")
|
|
156
175
|
end
|
|
176
|
+
|
|
177
|
+
it "prints a deprecation warning" do
|
|
178
|
+
err_stream = std_stream
|
|
179
|
+
capture_std_streams(std_stream, err_stream) do
|
|
180
|
+
make_request
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
expect(err_stream.read).to include(
|
|
184
|
+
"Setting the action name with the request env 'appsignal.route' is deprecated."
|
|
185
|
+
)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
it "logs a deprecation warning" do
|
|
189
|
+
logs = capture_logs { make_request }
|
|
190
|
+
expect(logs).to contains_log(
|
|
191
|
+
:warn,
|
|
192
|
+
"Setting the action name with the request env 'appsignal.route' is deprecated."
|
|
193
|
+
)
|
|
194
|
+
end
|
|
157
195
|
end
|
|
158
196
|
|
|
159
197
|
context "with appsignal.action env" do
|
|
160
|
-
|
|
161
|
-
|
|
198
|
+
before { env["appsignal.action"] = "POST /my-action" }
|
|
199
|
+
|
|
200
|
+
it "reports the appsignal.action value as the action name" do
|
|
162
201
|
make_request
|
|
163
202
|
|
|
164
203
|
expect(last_transaction).to have_action("POST /my-action")
|
|
165
204
|
end
|
|
205
|
+
|
|
206
|
+
it "prints a deprecation warning" do
|
|
207
|
+
err_stream = std_stream
|
|
208
|
+
capture_std_streams(std_stream, err_stream) do
|
|
209
|
+
make_request
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
expect(err_stream.read).to include(
|
|
213
|
+
"Setting the action name with the request env 'appsignal.action' is deprecated."
|
|
214
|
+
)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
it "logs a deprecation warning" do
|
|
218
|
+
logs = capture_logs { make_request }
|
|
219
|
+
expect(logs).to contains_log(
|
|
220
|
+
:warn,
|
|
221
|
+
"Setting the action name with the request env 'appsignal.action' is deprecated."
|
|
222
|
+
)
|
|
223
|
+
end
|
|
166
224
|
end
|
|
167
225
|
|
|
168
226
|
describe "request metadata" do
|
|
@@ -190,7 +248,7 @@ describe Appsignal::Rack::AbstractMiddleware do
|
|
|
190
248
|
end
|
|
191
249
|
end
|
|
192
250
|
|
|
193
|
-
context "
|
|
251
|
+
context "when fetching the request method raises an error" do
|
|
194
252
|
class BrokenRequestMethodRequest < Rack::Request
|
|
195
253
|
def request_method
|
|
196
254
|
raise "uh oh!"
|
|
@@ -198,11 +256,16 @@ describe Appsignal::Rack::AbstractMiddleware do
|
|
|
198
256
|
end
|
|
199
257
|
|
|
200
258
|
let(:options) { { :request_class => BrokenRequestMethodRequest } }
|
|
259
|
+
|
|
201
260
|
it "does not store the invalid HTTP request method" do
|
|
202
261
|
env["REQUEST_METHOD"] = "FOO"
|
|
203
|
-
make_request
|
|
262
|
+
logs = capture_logs { make_request }
|
|
204
263
|
|
|
205
264
|
expect(last_transaction).to_not include_metadata("method" => anything)
|
|
265
|
+
expect(logs).to contains_log(
|
|
266
|
+
:error,
|
|
267
|
+
"Exception while fetching the HTTP request method: RuntimeError: uh oh"
|
|
268
|
+
)
|
|
206
269
|
end
|
|
207
270
|
end
|
|
208
271
|
|
|
@@ -228,6 +291,35 @@ describe Appsignal::Rack::AbstractMiddleware do
|
|
|
228
291
|
expect(last_transaction).to include_params("custom" => "param")
|
|
229
292
|
end
|
|
230
293
|
end
|
|
294
|
+
|
|
295
|
+
context "when fetching the request method raises an error" do
|
|
296
|
+
class BrokenRequestParamsRequest < Rack::Request
|
|
297
|
+
def params
|
|
298
|
+
raise "uh oh!"
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
let(:options) do
|
|
303
|
+
{ :request_class => BrokenRequestParamsRequest, :params_method => :params }
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
it "does not store the invalid HTTP request method" do
|
|
307
|
+
logs = capture_logs { make_request }
|
|
308
|
+
|
|
309
|
+
expect(last_transaction).to_not include_params
|
|
310
|
+
expect(logs).to contains_log(
|
|
311
|
+
:error,
|
|
312
|
+
"Exception while fetching params " \
|
|
313
|
+
"from 'BrokenRequestParamsRequest#params': RuntimeError uh oh!"
|
|
314
|
+
)
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
it "sets session data" do
|
|
319
|
+
make_request
|
|
320
|
+
|
|
321
|
+
expect(last_transaction).to include_session_data("session" => "data", "user_id" => 123)
|
|
322
|
+
end
|
|
231
323
|
end
|
|
232
324
|
|
|
233
325
|
context "with queue start header" do
|
|
@@ -259,6 +351,10 @@ describe Appsignal::Rack::AbstractMiddleware do
|
|
|
259
351
|
def filtered_params
|
|
260
352
|
{ "abc" => "123" }
|
|
261
353
|
end
|
|
354
|
+
|
|
355
|
+
def session
|
|
356
|
+
{ "data" => "value" }
|
|
357
|
+
end
|
|
262
358
|
end
|
|
263
359
|
|
|
264
360
|
context "with overridden request class and params method" do
|
|
@@ -271,11 +367,19 @@ describe Appsignal::Rack::AbstractMiddleware do
|
|
|
271
367
|
|
|
272
368
|
expect(last_transaction).to include_params("abc" => "123")
|
|
273
369
|
end
|
|
370
|
+
|
|
371
|
+
it "uses the overridden request class to fetch session data" do
|
|
372
|
+
make_request
|
|
373
|
+
|
|
374
|
+
expect(last_transaction).to include_session_data("data" => "value")
|
|
375
|
+
end
|
|
274
376
|
end
|
|
275
377
|
|
|
276
378
|
context "with parent instrumentation" do
|
|
379
|
+
let(:transaction) { http_request_transaction }
|
|
277
380
|
before do
|
|
278
|
-
env[Appsignal::Rack::APPSIGNAL_TRANSACTION] =
|
|
381
|
+
env[Appsignal::Rack::APPSIGNAL_TRANSACTION] = transaction
|
|
382
|
+
set_current_transaction(transaction)
|
|
279
383
|
end
|
|
280
384
|
|
|
281
385
|
it "uses the existing transaction" do
|
|
@@ -284,6 +388,35 @@ describe Appsignal::Rack::AbstractMiddleware do
|
|
|
284
388
|
expect { make_request }.to_not(change { created_transactions.count })
|
|
285
389
|
end
|
|
286
390
|
|
|
391
|
+
it "wraps the response body in a BodyWrapper subclass" do
|
|
392
|
+
_status, _headers, body = make_request
|
|
393
|
+
expect(body).to be_kind_of(Appsignal::Rack::BodyWrapper)
|
|
394
|
+
|
|
395
|
+
body.to_ary
|
|
396
|
+
response_events =
|
|
397
|
+
last_transaction.to_h["events"].count do |event|
|
|
398
|
+
event["name"] == "process_response_body.rack"
|
|
399
|
+
end
|
|
400
|
+
expect(response_events).to eq(1)
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
context "when response body is already a BodyWrapper subclass" do
|
|
404
|
+
let(:body) { Appsignal::Rack::BodyWrapper.wrap(["hello!"], transaction) }
|
|
405
|
+
let(:app) { DummyApp.new { [200, {}, body] } }
|
|
406
|
+
|
|
407
|
+
it "doesn't wrap the body again" do
|
|
408
|
+
_status, _headers, body = make_request
|
|
409
|
+
expect(body).to eq(body)
|
|
410
|
+
|
|
411
|
+
body.to_ary
|
|
412
|
+
response_events =
|
|
413
|
+
last_transaction.to_h["events"].count do |event|
|
|
414
|
+
event["name"] == "process_response_body.rack"
|
|
415
|
+
end
|
|
416
|
+
expect(response_events).to eq(1)
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
|
|
287
420
|
context "with error" do
|
|
288
421
|
let(:app) { lambda { |_env| raise ExampleException, "error message" } }
|
|
289
422
|
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
describe Appsignal::Rack::BodyWrapper do
|
|
2
|
+
let(:transaction) { http_request_transaction }
|
|
3
|
+
before do
|
|
4
|
+
start_agent
|
|
5
|
+
set_current_transaction(transaction)
|
|
6
|
+
end
|
|
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
|
+
)
|
|
18
|
+
|
|
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
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
describe "with a body only supporting each()" do
|
|
28
|
+
it "wraps with appropriate class" do
|
|
29
|
+
fake_body = double(:each => nil)
|
|
30
|
+
|
|
31
|
+
wrapped = described_class.wrap(fake_body, transaction)
|
|
32
|
+
expect(wrapped).to respond_to(:each)
|
|
33
|
+
expect(wrapped).to_not respond_to(:to_ary)
|
|
34
|
+
expect(wrapped).to_not respond_to(:call)
|
|
35
|
+
expect(wrapped).to respond_to(:close)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "reads out the body in full using each" do
|
|
39
|
+
fake_body = double
|
|
40
|
+
expect(fake_body).to receive(:each).once.and_yield("a").and_yield("b").and_yield("c")
|
|
41
|
+
|
|
42
|
+
wrapped = described_class.wrap(fake_body, transaction)
|
|
43
|
+
expect { |b| wrapped.each(&b) }.to yield_successive_args("a", "b", "c")
|
|
44
|
+
|
|
45
|
+
expect(transaction).to include_event(
|
|
46
|
+
"name" => "process_response_body.rack",
|
|
47
|
+
"title" => "Process Rack response body (#each)"
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it "returns an Enumerator if each() gets called without a block" do
|
|
52
|
+
fake_body = double
|
|
53
|
+
expect(fake_body).to receive(:each).once.and_yield("a").and_yield("b").and_yield("c")
|
|
54
|
+
|
|
55
|
+
wrapped = described_class.wrap(fake_body, transaction)
|
|
56
|
+
enum = wrapped.each
|
|
57
|
+
expect(enum).to be_kind_of(Enumerator)
|
|
58
|
+
expect { |b| enum.each(&b) }.to yield_successive_args("a", "b", "c")
|
|
59
|
+
|
|
60
|
+
expect(transaction).to_not include_event("name" => "process_response_body.rack")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it "sets the exception raised inside each() on the transaction" do
|
|
64
|
+
fake_body = double
|
|
65
|
+
expect(fake_body).to receive(:each).once.and_raise(ExampleException, "error message")
|
|
66
|
+
|
|
67
|
+
wrapped = described_class.wrap(fake_body, transaction)
|
|
68
|
+
expect do
|
|
69
|
+
expect { |b| wrapped.each(&b) }.to yield_control
|
|
70
|
+
end.to raise_error(ExampleException, "error message")
|
|
71
|
+
|
|
72
|
+
expect(transaction).to have_error("ExampleException", "error message")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it "closes the body and tracks an instrumentation event when it gets closed" do
|
|
76
|
+
fake_body = double(:close => nil)
|
|
77
|
+
expect(fake_body).to receive(:each).once.and_yield("a").and_yield("b").and_yield("c")
|
|
78
|
+
|
|
79
|
+
wrapped = described_class.wrap(fake_body, transaction)
|
|
80
|
+
expect { |b| wrapped.each(&b) }.to yield_successive_args("a", "b", "c")
|
|
81
|
+
wrapped.close
|
|
82
|
+
|
|
83
|
+
expect(transaction).to include_event("name" => "close_response_body.rack")
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
describe "with a body supporting both each() and call" do
|
|
88
|
+
it "wraps with the wrapper that conceals call() and exposes each" do
|
|
89
|
+
fake_body = double
|
|
90
|
+
allow(fake_body).to receive(:each)
|
|
91
|
+
allow(fake_body).to receive(:call)
|
|
92
|
+
|
|
93
|
+
wrapped = described_class.wrap(fake_body, transaction)
|
|
94
|
+
expect(wrapped).to respond_to(:each)
|
|
95
|
+
expect(wrapped).to_not respond_to(:to_ary)
|
|
96
|
+
expect(wrapped).to_not respond_to(:call)
|
|
97
|
+
expect(wrapped).to_not respond_to(:to_path)
|
|
98
|
+
expect(wrapped).to respond_to(:close)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
describe "with a body supporting both to_ary and each" do
|
|
103
|
+
let(:fake_body) { double(:each => nil, :to_ary => []) }
|
|
104
|
+
|
|
105
|
+
it "wraps with appropriate class" do
|
|
106
|
+
wrapped = described_class.wrap(fake_body, transaction)
|
|
107
|
+
expect(wrapped).to respond_to(:each)
|
|
108
|
+
expect(wrapped).to respond_to(:to_ary)
|
|
109
|
+
expect(wrapped).to_not respond_to(:call)
|
|
110
|
+
expect(wrapped).to_not respond_to(:to_path)
|
|
111
|
+
expect(wrapped).to respond_to(:close)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
it "reads out the body in full using each" do
|
|
115
|
+
expect(fake_body).to receive(:each).once.and_yield("a").and_yield("b").and_yield("c")
|
|
116
|
+
|
|
117
|
+
wrapped = described_class.wrap(fake_body, transaction)
|
|
118
|
+
expect { |b| wrapped.each(&b) }.to yield_successive_args("a", "b", "c")
|
|
119
|
+
|
|
120
|
+
expect(transaction).to include_event(
|
|
121
|
+
"name" => "process_response_body.rack",
|
|
122
|
+
"title" => "Process Rack response body (#each)"
|
|
123
|
+
)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
it "sets the exception raised inside each() into the Appsignal transaction" do
|
|
127
|
+
expect(fake_body).to receive(:each).once.and_raise(ExampleException, "error message")
|
|
128
|
+
|
|
129
|
+
wrapped = described_class.wrap(fake_body, transaction)
|
|
130
|
+
expect do
|
|
131
|
+
expect { |b| wrapped.each(&b) }.to yield_control
|
|
132
|
+
end.to raise_error(ExampleException, "error message")
|
|
133
|
+
|
|
134
|
+
expect(transaction).to have_error("ExampleException", "error message")
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
it "reads out the body in full using to_ary" do
|
|
138
|
+
expect(fake_body).to receive(:to_ary).and_return(["one", "two", "three"])
|
|
139
|
+
|
|
140
|
+
wrapped = described_class.wrap(fake_body, transaction)
|
|
141
|
+
expect(wrapped.to_ary).to eq(["one", "two", "three"])
|
|
142
|
+
|
|
143
|
+
expect(transaction).to include_event(
|
|
144
|
+
"name" => "process_response_body.rack",
|
|
145
|
+
"title" => "Process Rack response body (#to_ary)"
|
|
146
|
+
)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it "sends the exception raised inside to_ary() into the Appsignal and closes transaction" do
|
|
150
|
+
fake_body = double
|
|
151
|
+
allow(fake_body).to receive(:each)
|
|
152
|
+
expect(fake_body).to receive(:to_ary).once.and_raise(ExampleException, "error message")
|
|
153
|
+
expect(fake_body).to_not receive(:close) # Per spec we expect the body has closed itself
|
|
154
|
+
|
|
155
|
+
wrapped = described_class.wrap(fake_body, transaction)
|
|
156
|
+
expect do
|
|
157
|
+
wrapped.to_ary
|
|
158
|
+
end.to raise_error(ExampleException, "error message")
|
|
159
|
+
|
|
160
|
+
expect(transaction).to have_error("ExampleException", "error message")
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
describe "with a body supporting both to_path and each" do
|
|
165
|
+
let(:fake_body) { double(:each => nil, :to_path => nil) }
|
|
166
|
+
|
|
167
|
+
it "wraps with appropriate class" do
|
|
168
|
+
wrapped = described_class.wrap(fake_body, transaction)
|
|
169
|
+
expect(wrapped).to respond_to(:each)
|
|
170
|
+
expect(wrapped).to_not respond_to(:to_ary)
|
|
171
|
+
expect(wrapped).to_not respond_to(:call)
|
|
172
|
+
expect(wrapped).to respond_to(:to_path)
|
|
173
|
+
expect(wrapped).to respond_to(:close)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
it "reads out the body in full using each()" do
|
|
177
|
+
expect(fake_body).to receive(:each).once.and_yield("a").and_yield("b").and_yield("c")
|
|
178
|
+
|
|
179
|
+
wrapped = described_class.wrap(fake_body, transaction)
|
|
180
|
+
expect { |b| wrapped.each(&b) }.to yield_successive_args("a", "b", "c")
|
|
181
|
+
|
|
182
|
+
expect(transaction).to include_event(
|
|
183
|
+
"name" => "process_response_body.rack",
|
|
184
|
+
"title" => "Process Rack response body (#each)"
|
|
185
|
+
)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
it "sets the exception raised inside each() into the Appsignal transaction" do
|
|
189
|
+
expect(fake_body).to receive(:each).once.and_raise(ExampleException, "error message")
|
|
190
|
+
|
|
191
|
+
wrapped = described_class.wrap(fake_body, transaction)
|
|
192
|
+
expect do
|
|
193
|
+
expect { |b| wrapped.each(&b) }.to yield_control
|
|
194
|
+
end.to raise_error(ExampleException, "error message")
|
|
195
|
+
|
|
196
|
+
expect(transaction).to have_error("ExampleException", "error message")
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
it "sets the exception raised inside to_path() into the Appsignal transaction" do
|
|
200
|
+
allow(fake_body).to receive(:to_path).once.and_raise(ExampleException, "error message")
|
|
201
|
+
|
|
202
|
+
wrapped = described_class.wrap(fake_body, transaction)
|
|
203
|
+
expect do
|
|
204
|
+
wrapped.to_path
|
|
205
|
+
end.to raise_error(ExampleException, "error message")
|
|
206
|
+
|
|
207
|
+
expect(transaction).to have_error("ExampleException", "error message")
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
it "exposes to_path to the sender" do
|
|
211
|
+
allow(fake_body).to receive(:to_path).and_return("/tmp/file.bin")
|
|
212
|
+
|
|
213
|
+
wrapped = described_class.wrap(fake_body, transaction)
|
|
214
|
+
expect(wrapped.to_path).to eq("/tmp/file.bin")
|
|
215
|
+
|
|
216
|
+
expect(transaction).to include_event(
|
|
217
|
+
"name" => "process_response_body.rack",
|
|
218
|
+
"title" => "Process Rack response body (#to_path)"
|
|
219
|
+
)
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
describe "with a body only supporting call()" do
|
|
224
|
+
let(:fake_body) { double(:call => nil) }
|
|
225
|
+
|
|
226
|
+
it "wraps with appropriate class" do
|
|
227
|
+
wrapped = described_class.wrap(fake_body, transaction)
|
|
228
|
+
expect(wrapped).to_not respond_to(:each)
|
|
229
|
+
expect(wrapped).to_not respond_to(:to_ary)
|
|
230
|
+
expect(wrapped).to respond_to(:call)
|
|
231
|
+
expect(wrapped).to_not respond_to(:to_path)
|
|
232
|
+
expect(wrapped).to respond_to(:close)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
it "passes the stream into the call() of the body" do
|
|
236
|
+
fake_rack_stream = double("stream")
|
|
237
|
+
expect(fake_body).to receive(:call).with(fake_rack_stream)
|
|
238
|
+
|
|
239
|
+
wrapped = described_class.wrap(fake_body, transaction)
|
|
240
|
+
wrapped.call(fake_rack_stream)
|
|
241
|
+
|
|
242
|
+
expect(transaction).to include_event(
|
|
243
|
+
"name" => "process_response_body.rack",
|
|
244
|
+
"title" => "Process Rack response body (#call)"
|
|
245
|
+
)
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
it "sets the exception raised inside call() into the Appsignal transaction" do
|
|
249
|
+
fake_rack_stream = double
|
|
250
|
+
allow(fake_body).to receive(:call)
|
|
251
|
+
.with(fake_rack_stream)
|
|
252
|
+
.and_raise(ExampleException, "error message")
|
|
253
|
+
|
|
254
|
+
wrapped = described_class.wrap(fake_body, transaction)
|
|
255
|
+
|
|
256
|
+
expect do
|
|
257
|
+
wrapped.call(fake_rack_stream)
|
|
258
|
+
end.to raise_error(ExampleException, "error message")
|
|
259
|
+
|
|
260
|
+
expect(transaction).to have_error("ExampleException", "error message")
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|