appsignal 3.6.1-java → 3.6.2-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/lib/appsignal/rack/generic_instrumentation.rb +4 -17
- data/lib/appsignal/rack/rails_instrumentation.rb +3 -15
- data/lib/appsignal/rack/sinatra_instrumentation.rb +3 -15
- data/lib/appsignal/rack/streaming_listener.rb +35 -26
- data/lib/appsignal/version.rb +1 -1
- data/lib/appsignal.rb +0 -1
- data/spec/lib/appsignal/rack/generic_instrumentation_spec.rb +2 -3
- data/spec/lib/appsignal/rack/rails_instrumentation_spec.rb +2 -4
- data/spec/lib/appsignal/rack/sinatra_instrumentation_spec.rb +1 -3
- data/spec/lib/appsignal/rack/streaming_listener_spec.rb +53 -9
- metadata +3 -5
- data/lib/appsignal/rack/body_wrapper.rb +0 -161
- data/spec/lib/appsignal/rack/body_wrapper_spec.rb +0 -220
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3a301e6247ef3ffd84430308f44d847483807a96c04356346fb0a281ad9bea0f
|
4
|
+
data.tar.gz: ae2bd9370beec37c374885e5bf10b5316394243ed73038f5d377553734789597
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a1ffcccbd922782d969fabd46828571c9137905674943f0aa790df83fcae7c71cfcc5ac26ab5a6d5227f1e461061ff00da8ccf38ade23bb86fe7c41137774251
|
7
|
+
data.tar.gz: 9e732c7b94dfa76013a8def22d31202feb6c7e4ecffb74a74408e2787ed8ae7b98fd8477b9a2af7242edc35d912d65f2c60a9228ef2ce019574082919ae4d622
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
# AppSignal for Ruby gem Changelog
|
2
2
|
|
3
|
+
## 3.6.2
|
4
|
+
|
5
|
+
_Published on 2024-03-08._
|
6
|
+
|
7
|
+
### Fixed
|
8
|
+
|
9
|
+
- [c3921865](https://github.com/appsignal/appsignal-ruby/commit/c392186573a72fd9afe22299fabcd14dcfe96139) patch - Revert Rack middleware changes (see [changelog](https://github.com/appsignal/appsignal-ruby/blob/main/CHANGELOG.md#360)) to fix issues relating to Unicorn broken pipe errors and multiple requests merging into a single sample.
|
10
|
+
|
3
11
|
## 3.6.1
|
4
12
|
|
5
13
|
_Published on 2024-03-05._
|
@@ -16,9 +16,7 @@ module Appsignal
|
|
16
16
|
if Appsignal.active?
|
17
17
|
call_with_appsignal_monitoring(env)
|
18
18
|
else
|
19
|
-
|
20
|
-
status, headers, obody = @app.call(env)
|
21
|
-
[status, headers, Appsignal::Rack::BodyWrapper.wrap(obody, nil_transaction)]
|
19
|
+
@app.call(env)
|
22
20
|
end
|
23
21
|
end
|
24
22
|
|
@@ -29,30 +27,19 @@ module Appsignal
|
|
29
27
|
Appsignal::Transaction::HTTP_REQUEST,
|
30
28
|
request
|
31
29
|
)
|
32
|
-
# We need to complete the transaction if there is an exception inside the `call`
|
33
|
-
# of the app. If there isn't one and the app returns us a Rack response triplet, we let
|
34
|
-
# the BodyWrapper complete the transaction when #close gets called on it
|
35
|
-
# (guaranteed by the webserver)
|
36
|
-
complete_transaction_without_body = false
|
37
30
|
begin
|
38
31
|
Appsignal.instrument("process_action.generic") do
|
39
|
-
|
40
|
-
[status, headers, Appsignal::Rack::BodyWrapper.wrap(obody, transaction)]
|
32
|
+
@app.call(env)
|
41
33
|
end
|
42
34
|
rescue Exception => error # rubocop:disable Lint/RescueException
|
43
35
|
transaction.set_error(error)
|
44
|
-
complete_transaction_without_body = true
|
45
36
|
raise error
|
46
37
|
ensure
|
47
|
-
|
48
|
-
transaction.set_action_if_nil(default_action)
|
38
|
+
transaction.set_action_if_nil(env["appsignal.route"] || "unknown")
|
49
39
|
transaction.set_metadata("path", request.path)
|
50
40
|
transaction.set_metadata("method", request.request_method)
|
51
41
|
transaction.set_http_or_background_queue_start
|
52
|
-
|
53
|
-
# Transaction gets completed when the body gets read out, except in cases when
|
54
|
-
# the app failed before returning us the Rack response triplet.
|
55
|
-
Appsignal::Transaction.complete_current! if complete_transaction_without_body
|
42
|
+
Appsignal::Transaction.complete_current!
|
56
43
|
end
|
57
44
|
end
|
58
45
|
end
|
@@ -16,9 +16,7 @@ module Appsignal
|
|
16
16
|
if Appsignal.active?
|
17
17
|
call_with_appsignal_monitoring(env)
|
18
18
|
else
|
19
|
-
|
20
|
-
status, headers, obody = @app.call(env)
|
21
|
-
[status, headers, Appsignal::Rack::BodyWrapper.wrap(obody, nil_transaction)]
|
19
|
+
@app.call(env)
|
22
20
|
end
|
23
21
|
end
|
24
22
|
|
@@ -30,17 +28,10 @@ module Appsignal
|
|
30
28
|
request,
|
31
29
|
:params_method => :filtered_parameters
|
32
30
|
)
|
33
|
-
# We need to complete the transaction if there is an exception exception inside the `call`
|
34
|
-
# of the app. If there isn't one and the app returns us a Rack response triplet, we let
|
35
|
-
# the BodyWrapper complete the transaction when #close gets called on it
|
36
|
-
# (guaranteed by the webserver)
|
37
|
-
complete_transaction_without_body = false
|
38
31
|
begin
|
39
|
-
|
40
|
-
[status, headers, Appsignal::Rack::BodyWrapper.wrap(obody, transaction)]
|
32
|
+
@app.call(env)
|
41
33
|
rescue Exception => error # rubocop:disable Lint/RescueException
|
42
34
|
transaction.set_error(error)
|
43
|
-
complete_transaction_without_body = true
|
44
35
|
raise error
|
45
36
|
ensure
|
46
37
|
controller = env["action_controller.instance"]
|
@@ -54,10 +45,7 @@ module Appsignal
|
|
54
45
|
rescue => error
|
55
46
|
Appsignal.internal_logger.error("Unable to report HTTP request method: '#{error}'")
|
56
47
|
end
|
57
|
-
|
58
|
-
# Transaction gets completed when the body gets read out, except in cases when
|
59
|
-
# the app failed before returning us the Rack response triplet.
|
60
|
-
Appsignal::Transaction.complete_current! if complete_transaction_without_body
|
48
|
+
Appsignal::Transaction.complete_current!
|
61
49
|
end
|
62
50
|
end
|
63
51
|
|
@@ -42,9 +42,7 @@ module Appsignal
|
|
42
42
|
if Appsignal.active?
|
43
43
|
call_with_appsignal_monitoring(env)
|
44
44
|
else
|
45
|
-
|
46
|
-
status, headers, obody = @app.call(env)
|
47
|
-
[status, headers, Appsignal::Rack::BodyWrapper.wrap(obody, nil_transaction)]
|
45
|
+
@app.call(env)
|
48
46
|
end
|
49
47
|
end
|
50
48
|
|
@@ -58,19 +56,12 @@ module Appsignal
|
|
58
56
|
request,
|
59
57
|
options
|
60
58
|
)
|
61
|
-
# We need to complete the transaction if there is an exception exception inside the `call`
|
62
|
-
# of the app. If there isn't one and the app returns us a Rack response triplet, we let
|
63
|
-
# the BodyWrapper complete the transaction when #close gets called on it
|
64
|
-
# (guaranteed by the webserver)
|
65
|
-
complete_transaction_without_body = false
|
66
59
|
begin
|
67
60
|
Appsignal.instrument("process_action.sinatra") do
|
68
|
-
|
69
|
-
[status, headers, Appsignal::Rack::BodyWrapper.wrap(obody, transaction)]
|
61
|
+
@app.call(env)
|
70
62
|
end
|
71
63
|
rescue Exception => error # rubocop:disable Lint/RescueException
|
72
64
|
transaction.set_error(error)
|
73
|
-
complete_transaction_without_body = true
|
74
65
|
raise error
|
75
66
|
ensure
|
76
67
|
# If raise_error is off versions of Sinatra don't raise errors, but store
|
@@ -82,10 +73,7 @@ module Appsignal
|
|
82
73
|
transaction.set_metadata("path", request.path)
|
83
74
|
transaction.set_metadata("method", request.request_method)
|
84
75
|
transaction.set_http_or_background_queue_start
|
85
|
-
|
86
|
-
# Transaction gets completed when the body gets read out, except in cases when
|
87
|
-
# the app failed before returning us the Rack response triplet.
|
88
|
-
Appsignal::Transaction.complete_current! if complete_transaction_without_body
|
76
|
+
Appsignal::Transaction.complete_current!
|
89
77
|
end
|
90
78
|
end
|
91
79
|
|
@@ -16,9 +16,7 @@ module Appsignal
|
|
16
16
|
if Appsignal.active?
|
17
17
|
call_with_appsignal_monitoring(env)
|
18
18
|
else
|
19
|
-
|
20
|
-
status, headers, obody = @app.call(env)
|
21
|
-
[status, headers, Appsignal::Rack::BodyWrapper.wrap(obody, nil_transaction)]
|
19
|
+
@app.call(env)
|
22
20
|
end
|
23
21
|
end
|
24
22
|
|
@@ -30,35 +28,46 @@ module Appsignal
|
|
30
28
|
request
|
31
29
|
)
|
32
30
|
|
33
|
-
# We need to complete the transaction if there is an exception exception inside the `call`
|
34
|
-
# of the app. If there isn't one and the app returns us a Rack response triplet, we let
|
35
|
-
# the BodyWrapper complete the transaction when #close gets called on it
|
36
|
-
# (guaranteed by the webserver)
|
37
|
-
complete_transaction_without_body = false
|
38
|
-
|
39
31
|
# Instrument a `process_action`, to set params/action name
|
40
|
-
|
32
|
+
status, headers, body =
|
41
33
|
Appsignal.instrument("process_action.rack") do
|
42
|
-
|
43
|
-
|
34
|
+
@app.call(env)
|
35
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
36
|
+
transaction.set_error(e)
|
37
|
+
raise e
|
38
|
+
ensure
|
39
|
+
transaction.set_action_if_nil(env["appsignal.action"])
|
40
|
+
transaction.set_metadata("path", request.path)
|
41
|
+
transaction.set_metadata("method", request.request_method)
|
42
|
+
transaction.set_http_or_background_queue_start
|
44
43
|
end
|
45
|
-
rescue Exception => error # rubocop:disable Lint/RescueException
|
46
|
-
transaction.set_error(error)
|
47
|
-
complete_transaction_without_body = true
|
48
|
-
raise error
|
49
|
-
ensure
|
50
|
-
transaction.set_action_if_nil(env["appsignal.action"])
|
51
|
-
transaction.set_metadata("path", request.path)
|
52
|
-
transaction.set_metadata("method", request.request_method)
|
53
|
-
transaction.set_http_or_background_queue_start
|
54
44
|
|
55
|
-
|
56
|
-
|
57
|
-
Appsignal::Transaction.complete_current! if complete_transaction_without_body
|
58
|
-
end
|
45
|
+
# Wrap the result body with our StreamWrapper
|
46
|
+
[status, headers, StreamWrapper.new(body, transaction)]
|
59
47
|
end
|
60
48
|
end
|
61
49
|
end
|
62
50
|
|
63
|
-
StreamWrapper
|
51
|
+
class StreamWrapper
|
52
|
+
def initialize(stream, transaction)
|
53
|
+
@stream = stream
|
54
|
+
@transaction = transaction
|
55
|
+
end
|
56
|
+
|
57
|
+
def each(&block)
|
58
|
+
@stream.each(&block)
|
59
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
60
|
+
@transaction.set_error(e)
|
61
|
+
raise e
|
62
|
+
end
|
63
|
+
|
64
|
+
def close
|
65
|
+
@stream.close if @stream.respond_to?(:close)
|
66
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
67
|
+
@transaction.set_error(e)
|
68
|
+
raise e
|
69
|
+
ensure
|
70
|
+
Appsignal::Transaction.complete_current!
|
71
|
+
end
|
72
|
+
end
|
64
73
|
end
|
data/lib/appsignal/version.rb
CHANGED
data/lib/appsignal.rb
CHANGED
@@ -305,6 +305,5 @@ require "appsignal/garbage_collection"
|
|
305
305
|
require "appsignal/integrations/railtie" if defined?(::Rails)
|
306
306
|
require "appsignal/transaction"
|
307
307
|
require "appsignal/version"
|
308
|
-
require "appsignal/rack/body_wrapper"
|
309
308
|
require "appsignal/rack/generic_instrumentation"
|
310
309
|
require "appsignal/transmitter"
|
@@ -50,7 +50,7 @@ describe Appsignal::Rack::GenericInstrumentation do
|
|
50
50
|
expect(app).to receive(:call).with(env)
|
51
51
|
end
|
52
52
|
|
53
|
-
context "with an exception
|
53
|
+
context "with an exception", :error => true do
|
54
54
|
let(:error) { ExampleException }
|
55
55
|
let(:app) do
|
56
56
|
double.tap do |d|
|
@@ -58,9 +58,8 @@ describe Appsignal::Rack::GenericInstrumentation do
|
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
61
|
-
it "records the exception
|
61
|
+
it "records the exception" do
|
62
62
|
expect_any_instance_of(Appsignal::Transaction).to receive(:set_error).with(error)
|
63
|
-
expect(Appsignal::Transaction).to receive(:complete_current!)
|
64
63
|
end
|
65
64
|
end
|
66
65
|
|
@@ -65,8 +65,7 @@ if DependencyHelper.rails_present?
|
|
65
65
|
|
66
66
|
describe "#call_with_appsignal_monitoring" do
|
67
67
|
def run
|
68
|
-
|
69
|
-
body.close # Rack will always call close() on the body
|
68
|
+
middleware.call(env)
|
70
69
|
end
|
71
70
|
|
72
71
|
it "calls the wrapped app" do
|
@@ -127,8 +126,7 @@ if DependencyHelper.rails_present?
|
|
127
126
|
end
|
128
127
|
end
|
129
128
|
|
130
|
-
it "records the exception
|
131
|
-
expect(Appsignal::Transaction).to receive(:complete_current!)
|
129
|
+
it "records the exception" do
|
132
130
|
expect { run }.to raise_error(error)
|
133
131
|
|
134
132
|
transaction_hash = last_transaction.to_h
|
@@ -3,9 +3,7 @@ if DependencyHelper.sinatra_present?
|
|
3
3
|
|
4
4
|
module SinatraRequestHelpers
|
5
5
|
def make_request(env)
|
6
|
-
|
7
|
-
# Close the body so that the transaction gets completed
|
8
|
-
body&.close
|
6
|
+
middleware.call(env)
|
9
7
|
end
|
10
8
|
|
11
9
|
def make_request_with_error(env, error)
|
@@ -90,11 +90,10 @@ describe Appsignal::Rack::StreamingListener do
|
|
90
90
|
context "with an exception in the instrumentation call" do
|
91
91
|
let(:error) { ExampleException }
|
92
92
|
|
93
|
-
it "should add the exception to the transaction
|
93
|
+
it "should add the exception to the transaction" do
|
94
94
|
allow(app).to receive(:call).and_raise(error)
|
95
95
|
|
96
96
|
expect(transaction).to receive(:set_error).with(error)
|
97
|
-
expect(Appsignal::Transaction).to receive(:complete_current!).and_call_original
|
98
97
|
|
99
98
|
expect do
|
100
99
|
listener.call_with_appsignal_monitoring(env)
|
@@ -102,19 +101,64 @@ describe Appsignal::Rack::StreamingListener do
|
|
102
101
|
end
|
103
102
|
end
|
104
103
|
|
105
|
-
it "should wrap the
|
104
|
+
it "should wrap the body in a wrapper" do
|
105
|
+
expect(Appsignal::StreamWrapper).to receive(:new)
|
106
|
+
.with("body", transaction)
|
107
|
+
.and_return(wrapper)
|
108
|
+
|
106
109
|
body = listener.call_with_appsignal_monitoring(env)[2]
|
107
110
|
|
108
|
-
expect(body).to
|
111
|
+
expect(body).to be_a(Appsignal::StreamWrapper)
|
109
112
|
end
|
110
113
|
end
|
111
114
|
end
|
112
115
|
|
113
116
|
describe Appsignal::StreamWrapper do
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
117
|
+
let(:stream) { double }
|
118
|
+
let(:transaction) do
|
119
|
+
Appsignal::Transaction.create(SecureRandom.uuid, Appsignal::Transaction::HTTP_REQUEST, {})
|
120
|
+
end
|
121
|
+
let(:wrapper) { Appsignal::StreamWrapper.new(stream, transaction) }
|
122
|
+
|
123
|
+
describe "#each" do
|
124
|
+
it "calls the original stream" do
|
125
|
+
expect(stream).to receive(:each)
|
126
|
+
|
127
|
+
wrapper.each
|
128
|
+
end
|
129
|
+
|
130
|
+
context "when #each raises an error" do
|
131
|
+
let(:error) { ExampleException }
|
132
|
+
|
133
|
+
it "records the exception" do
|
134
|
+
allow(stream).to receive(:each).and_raise(error)
|
135
|
+
|
136
|
+
expect(transaction).to receive(:set_error).with(error)
|
137
|
+
|
138
|
+
expect { wrapper.send(:each) }.to raise_error(error)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
describe "#close" do
|
144
|
+
it "closes the original stream and completes the transaction" do
|
145
|
+
expect(stream).to receive(:close)
|
146
|
+
expect(Appsignal::Transaction).to receive(:complete_current!)
|
147
|
+
|
148
|
+
wrapper.close
|
149
|
+
end
|
150
|
+
|
151
|
+
context "when #close raises an error" do
|
152
|
+
let(:error) { ExampleException }
|
153
|
+
|
154
|
+
it "records the exception and completes the transaction" do
|
155
|
+
allow(stream).to receive(:close).and_raise(error)
|
156
|
+
|
157
|
+
expect(transaction).to receive(:set_error).with(error)
|
158
|
+
expect(transaction).to receive(:complete)
|
159
|
+
|
160
|
+
expect { wrapper.send(:close) }.to raise_error(error)
|
161
|
+
end
|
162
|
+
end
|
119
163
|
end
|
120
164
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: appsignal
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.6.
|
4
|
+
version: 3.6.2
|
5
5
|
platform: java
|
6
6
|
authors:
|
7
7
|
- Robert Beekman
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2024-03-
|
13
|
+
date: 2024-03-08 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rack
|
@@ -282,7 +282,6 @@ files:
|
|
282
282
|
- lib/appsignal/probes/helpers.rb
|
283
283
|
- lib/appsignal/probes/mri.rb
|
284
284
|
- lib/appsignal/probes/sidekiq.rb
|
285
|
-
- lib/appsignal/rack/body_wrapper.rb
|
286
285
|
- lib/appsignal/rack/generic_instrumentation.rb
|
287
286
|
- lib/appsignal/rack/rails_instrumentation.rb
|
288
287
|
- lib/appsignal/rack/sinatra_instrumentation.rb
|
@@ -381,7 +380,6 @@ files:
|
|
381
380
|
- spec/lib/appsignal/probes/gvl_spec.rb
|
382
381
|
- spec/lib/appsignal/probes/mri_spec.rb
|
383
382
|
- spec/lib/appsignal/probes/sidekiq_spec.rb
|
384
|
-
- spec/lib/appsignal/rack/body_wrapper_spec.rb
|
385
383
|
- spec/lib/appsignal/rack/generic_instrumentation_spec.rb
|
386
384
|
- spec/lib/appsignal/rack/rails_instrumentation_spec.rb
|
387
385
|
- spec/lib/appsignal/rack/sinatra_instrumentation_spec.rb
|
@@ -467,7 +465,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
467
465
|
- !ruby/object:Gem::Version
|
468
466
|
version: '0'
|
469
467
|
requirements: []
|
470
|
-
rubygems_version: 3.
|
468
|
+
rubygems_version: 3.3.7
|
471
469
|
signing_key:
|
472
470
|
specification_version: 4
|
473
471
|
summary: Logs performance and exception data from your app to appsignal.com
|
@@ -1,161 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Appsignal
|
4
|
-
# @api private
|
5
|
-
module Rack
|
6
|
-
class BodyWrapper
|
7
|
-
def self.wrap(original_body, appsignal_transaction)
|
8
|
-
# The logic of how Rack treats a response body differs based on which methods
|
9
|
-
# the body responds to. This means that to support the Rack 3.x spec in full
|
10
|
-
# we need to return a wrapper which matches the API of the wrapped body as closely
|
11
|
-
# as possible. Pick the wrapper from the most specific to the least specific.
|
12
|
-
# See https://github.com/rack/rack/blob/main/SPEC.rdoc#the-body-
|
13
|
-
#
|
14
|
-
# What is important is that our Body wrapper responds to the same methods Rack
|
15
|
-
# (or a webserver) would be checking and calling, and passes through that functionality
|
16
|
-
# to the original body. This can be done using delegation via i.e. SimpleDelegate
|
17
|
-
# but we also need "close" to get called correctly so that the Appsignal transaction
|
18
|
-
# gets completed - which will not happen, for example, when #to_ary gets called
|
19
|
-
# just on the delegated Rack body.
|
20
|
-
#
|
21
|
-
# This comment https://github.com/rails/rails/pull/49627#issuecomment-1769802573
|
22
|
-
# is of particular interest to understand why this has to be somewhat complicated.
|
23
|
-
if original_body.respond_to?(:to_path)
|
24
|
-
PathableBodyWrapper.new(original_body, appsignal_transaction)
|
25
|
-
elsif original_body.respond_to?(:to_ary)
|
26
|
-
ArrayableBodyWrapper.new(original_body, appsignal_transaction)
|
27
|
-
elsif !original_body.respond_to?(:each) && original_body.respond_to?(:call)
|
28
|
-
# This body only supports #call, so we must be running a Rack 3 application
|
29
|
-
# It is possible that a body exposes both `each` and `call` in the hopes of
|
30
|
-
# being backwards-compatible with both Rack 3.x and Rack 2.x, however
|
31
|
-
# this is not going to work since the SPEC says that if both are available,
|
32
|
-
# `each` should be used and `call` should be ignored.
|
33
|
-
# So for that case we can drop by to our default EnumerableBodyWrapper
|
34
|
-
CallableBodyWrapper.new(original_body, appsignal_transaction)
|
35
|
-
else
|
36
|
-
EnumerableBodyWrapper.new(original_body, appsignal_transaction)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def initialize(body, appsignal_transaction)
|
41
|
-
@body_already_closed = false
|
42
|
-
@body = body
|
43
|
-
@transaction = appsignal_transaction
|
44
|
-
end
|
45
|
-
|
46
|
-
# This must be present in all Rack bodies and will be called by the serving adapter
|
47
|
-
def close
|
48
|
-
# The @body_already_closed check is needed so that if `to_ary`
|
49
|
-
# of the body has already closed itself (as prescribed) we do not
|
50
|
-
# attempt to close it twice
|
51
|
-
if !@body_already_closed && @body.respond_to?(:close)
|
52
|
-
Appsignal.instrument("response_body_close.rack") { @body.close }
|
53
|
-
end
|
54
|
-
@body_already_closed = true
|
55
|
-
rescue Exception => error # rubocop:disable Lint/RescueException
|
56
|
-
@transaction.set_error(error)
|
57
|
-
raise error
|
58
|
-
ensure
|
59
|
-
complete_transaction!
|
60
|
-
end
|
61
|
-
|
62
|
-
def complete_transaction!
|
63
|
-
# We need to call the Transaction class method and not
|
64
|
-
# @transaction.complete because the transaction is still
|
65
|
-
# thread-local and it needs to remove itself from the
|
66
|
-
# thread variables correctly, which does not happen on
|
67
|
-
# Transaction#complete.
|
68
|
-
#
|
69
|
-
# In the future it would be a good idea to ensure
|
70
|
-
# that the current transaction is the same as @transaction,
|
71
|
-
# or allow @transaction to complete itself and remove
|
72
|
-
# itself from Thread.current
|
73
|
-
Appsignal::Transaction.complete_current!
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
# The standard Rack body wrapper which exposes "each" for iterating
|
78
|
-
# over the response body. This is supported across all 3 major Rack
|
79
|
-
# versions.
|
80
|
-
#
|
81
|
-
# @api private
|
82
|
-
class EnumerableBodyWrapper < BodyWrapper
|
83
|
-
def each(&blk)
|
84
|
-
# This is a workaround for the Rails bug when there was a bit too much
|
85
|
-
# eagerness in implementing to_ary, see:
|
86
|
-
# https://github.com/rails/rails/pull/44953
|
87
|
-
# https://github.com/rails/rails/pull/47092
|
88
|
-
# https://github.com/rails/rails/pull/49627
|
89
|
-
# https://github.com/rails/rails/issues/49588
|
90
|
-
# While the Rack SPEC does not mandate `each` to be callable
|
91
|
-
# in a blockless way it is still a good idea to have it in place.
|
92
|
-
return enum_for(:each) unless block_given?
|
93
|
-
|
94
|
-
Appsignal.instrument("process_response_body.rack", "Process Rack response body (#each)") do
|
95
|
-
@body.each(&blk)
|
96
|
-
end
|
97
|
-
rescue Exception => error # rubocop:disable Lint/RescueException
|
98
|
-
@transaction.set_error(error)
|
99
|
-
raise error
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
# The callable response bodies are a new Rack 3.x feature, and would not work
|
104
|
-
# with older Rack versions. They must not respond to `each` because
|
105
|
-
# "If it responds to each, you must call each and not call". This is why
|
106
|
-
# it inherits from BodyWrapper directly and not from EnumerableBodyWrapper
|
107
|
-
#
|
108
|
-
# @api private
|
109
|
-
class CallableBodyWrapper < BodyWrapper
|
110
|
-
def call(stream)
|
111
|
-
# `stream` will be closed by the app we are calling, no need for us
|
112
|
-
# to close it ourselves
|
113
|
-
Appsignal.instrument("process_response_body.rack", "Process Rack response body (#call)") do
|
114
|
-
@body.call(stream)
|
115
|
-
end
|
116
|
-
rescue Exception => error # rubocop:disable Lint/RescueException
|
117
|
-
@transaction.set_error(error)
|
118
|
-
raise error
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
# "to_ary" takes precedence over "each" and allows the response body
|
123
|
-
# to be read eagerly. If the body supports that method, it takes precedence
|
124
|
-
# over "each":
|
125
|
-
# "Middleware may call to_ary directly on the Body and return a new Body in its place"
|
126
|
-
# One could "fold" both the to_ary API and the each() API into one Body object, but
|
127
|
-
# to_ary must also call "close" after it executes - and in the Rails implementation
|
128
|
-
# this pecularity was not handled properly.
|
129
|
-
#
|
130
|
-
# @api private
|
131
|
-
class ArrayableBodyWrapper < EnumerableBodyWrapper
|
132
|
-
def to_ary
|
133
|
-
@body_already_closed = true
|
134
|
-
Appsignal.instrument(
|
135
|
-
"process_response_body.rack",
|
136
|
-
"Process Rack response body (#to_ary)"
|
137
|
-
) do
|
138
|
-
@body.to_ary
|
139
|
-
end
|
140
|
-
rescue Exception => error # rubocop:disable Lint/RescueException
|
141
|
-
@transaction.set_error(error)
|
142
|
-
raise error
|
143
|
-
ensure
|
144
|
-
# We do not call "close" on ourselves as the only action
|
145
|
-
# we need to complete is completing the transaction.
|
146
|
-
complete_transaction!
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
|
-
# Having "to_path" on a body allows Rack to serve out a static file, or to
|
151
|
-
# pass that file to the downstream webserver for sending using X-Sendfile
|
152
|
-
class PathableBodyWrapper < EnumerableBodyWrapper
|
153
|
-
def to_path
|
154
|
-
Appsignal.instrument("response_body_to_path.rack") { @body.to_path }
|
155
|
-
rescue Exception => error # rubocop:disable Lint/RescueException
|
156
|
-
@transaction.set_error(error)
|
157
|
-
raise error
|
158
|
-
end
|
159
|
-
end
|
160
|
-
end
|
161
|
-
end
|
@@ -1,220 +0,0 @@
|
|
1
|
-
describe Appsignal::Rack::BodyWrapper do
|
2
|
-
let(:nil_txn) { Appsignal::Transaction::NilTransaction.new }
|
3
|
-
|
4
|
-
describe "with a body that supports all possible features" do
|
5
|
-
it "reduces the supported methods to just each()" do
|
6
|
-
# which is the safest thing to do, since the body is likely broken
|
7
|
-
fake_body = double(:each => nil, :call => nil, :to_ary => [], :to_path => "/tmp/foo.bin",
|
8
|
-
:close => nil)
|
9
|
-
wrapped = described_class.wrap(fake_body, nil_txn)
|
10
|
-
expect(wrapped).to respond_to(:each)
|
11
|
-
expect(wrapped).not_to respond_to(:to_ary)
|
12
|
-
expect(wrapped).not_to respond_to(:call)
|
13
|
-
expect(wrapped).to respond_to(:close)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
describe "with a body only supporting each()" do
|
18
|
-
it "wraps with appropriate class" do
|
19
|
-
fake_body = double
|
20
|
-
allow(fake_body).to receive(:each)
|
21
|
-
|
22
|
-
wrapped = described_class.wrap(fake_body, nil_txn)
|
23
|
-
expect(wrapped).to respond_to(:each)
|
24
|
-
expect(wrapped).not_to respond_to(:to_ary)
|
25
|
-
expect(wrapped).not_to respond_to(:call)
|
26
|
-
expect(wrapped).to respond_to(:close)
|
27
|
-
end
|
28
|
-
|
29
|
-
it "reads out the body in full using each" do
|
30
|
-
fake_body = double
|
31
|
-
expect(fake_body).to receive(:each).once.and_yield("a").and_yield("b").and_yield("c")
|
32
|
-
wrapped = described_class.wrap(fake_body, nil_txn)
|
33
|
-
expect { |b| wrapped.each(&b) }.to yield_successive_args("a", "b", "c")
|
34
|
-
end
|
35
|
-
|
36
|
-
it "returns an Enumerator if each() gets called without a block" do
|
37
|
-
fake_body = double
|
38
|
-
expect(fake_body).to receive(:each).once.and_yield("a").and_yield("b").and_yield("c")
|
39
|
-
|
40
|
-
wrapped = described_class.wrap(fake_body, nil_txn)
|
41
|
-
enum = wrapped.each
|
42
|
-
expect(enum).to be_kind_of(Enumerator)
|
43
|
-
expect { |b| enum.each(&b) }.to yield_successive_args("a", "b", "c")
|
44
|
-
end
|
45
|
-
|
46
|
-
it "sets the exception raised inside each() into the Appsignal transaction" do
|
47
|
-
fake_body = double
|
48
|
-
expect(fake_body).to receive(:each).once.and_raise(Exception.new("Oops"))
|
49
|
-
|
50
|
-
txn = double("Appsignal transaction", "nil_transaction?" => false)
|
51
|
-
expect(txn).to receive(:set_error).once.with(instance_of(Exception))
|
52
|
-
|
53
|
-
wrapped = described_class.wrap(fake_body, txn)
|
54
|
-
expect do
|
55
|
-
expect { |b| wrapped.each(&b) }.to yield_control
|
56
|
-
end.to raise_error(/Oops/)
|
57
|
-
end
|
58
|
-
|
59
|
-
it "closes the body and the transaction when it gets closed" do
|
60
|
-
fake_body = double
|
61
|
-
expect(fake_body).to receive(:each).once.and_yield("a").and_yield("b").and_yield("c")
|
62
|
-
|
63
|
-
txn = double("Appsignal transaction", "nil_transaction?" => false)
|
64
|
-
expect(Appsignal::Transaction).to receive(:complete_current!).once
|
65
|
-
|
66
|
-
wrapped = described_class.wrap(fake_body, txn)
|
67
|
-
expect { |b| wrapped.each(&b) }.to yield_successive_args("a", "b", "c")
|
68
|
-
expect { wrapped.close }.not_to raise_error
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
describe "with a body supporting both each() and call" do
|
73
|
-
it "wraps with the wrapper that conceals call() and exposes each" do
|
74
|
-
fake_body = double
|
75
|
-
allow(fake_body).to receive(:each)
|
76
|
-
allow(fake_body).to receive(:call)
|
77
|
-
|
78
|
-
wrapped = described_class.wrap(fake_body, nil_txn)
|
79
|
-
expect(wrapped).to respond_to(:each)
|
80
|
-
expect(wrapped).not_to respond_to(:to_ary)
|
81
|
-
expect(wrapped).not_to respond_to(:call)
|
82
|
-
expect(wrapped).not_to respond_to(:to_path)
|
83
|
-
expect(wrapped).to respond_to(:close)
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
describe "with a body supporting both to_ary and each" do
|
88
|
-
let(:fake_body) { double(:each => nil, :to_ary => []) }
|
89
|
-
it "wraps with appropriate class" do
|
90
|
-
wrapped = described_class.wrap(fake_body, nil_txn)
|
91
|
-
expect(wrapped).to respond_to(:each)
|
92
|
-
expect(wrapped).to respond_to(:to_ary)
|
93
|
-
expect(wrapped).not_to respond_to(:call)
|
94
|
-
expect(wrapped).not_to respond_to(:to_path)
|
95
|
-
expect(wrapped).to respond_to(:close)
|
96
|
-
end
|
97
|
-
|
98
|
-
it "reads out the body in full using each" do
|
99
|
-
expect(fake_body).to receive(:each).once.and_yield("a").and_yield("b").and_yield("c")
|
100
|
-
|
101
|
-
wrapped = described_class.wrap(fake_body, nil_txn)
|
102
|
-
expect { |b| wrapped.each(&b) }.to yield_successive_args("a", "b", "c")
|
103
|
-
end
|
104
|
-
|
105
|
-
it "sets the exception raised inside each() into the Appsignal transaction" do
|
106
|
-
expect(fake_body).to receive(:each).once.and_raise(Exception.new("Oops"))
|
107
|
-
|
108
|
-
txn = double("Appsignal transaction", "nil_transaction?" => false)
|
109
|
-
expect(txn).to receive(:set_error).once.with(instance_of(Exception))
|
110
|
-
|
111
|
-
wrapped = described_class.wrap(fake_body, txn)
|
112
|
-
expect do
|
113
|
-
expect { |b| wrapped.each(&b) }.to yield_control
|
114
|
-
end.to raise_error(/Oops/)
|
115
|
-
end
|
116
|
-
|
117
|
-
it "reads out the body in full using to_ary" do
|
118
|
-
expect(fake_body).to receive(:to_ary).and_return(["one", "two", "three"])
|
119
|
-
|
120
|
-
wrapped = described_class.wrap(fake_body, nil_txn)
|
121
|
-
expect(wrapped.to_ary).to eq(["one", "two", "three"])
|
122
|
-
end
|
123
|
-
|
124
|
-
it "sends the exception raised inside to_ary() into the Appsignal and closes txn" do
|
125
|
-
fake_body = double
|
126
|
-
allow(fake_body).to receive(:each)
|
127
|
-
expect(fake_body).to receive(:to_ary).once.and_raise(Exception.new("Oops"))
|
128
|
-
expect(fake_body).not_to receive(:close) # Per spec we expect the body has closed itself
|
129
|
-
|
130
|
-
txn = double("Appsignal transaction", "nil_transaction?" => false)
|
131
|
-
expect(txn).to receive(:set_error).once.with(instance_of(Exception))
|
132
|
-
expect(Appsignal::Transaction).to receive(:complete_current!).once
|
133
|
-
|
134
|
-
wrapped = described_class.wrap(fake_body, txn)
|
135
|
-
expect { wrapped.to_ary }.to raise_error(/Oops/)
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
describe "with a body supporting both to_path and each" do
|
140
|
-
let(:fake_body) { double(:each => nil, :to_path => nil) }
|
141
|
-
|
142
|
-
it "wraps with appropriate class" do
|
143
|
-
wrapped = described_class.wrap(fake_body, nil_txn)
|
144
|
-
expect(wrapped).to respond_to(:each)
|
145
|
-
expect(wrapped).not_to respond_to(:to_ary)
|
146
|
-
expect(wrapped).not_to respond_to(:call)
|
147
|
-
expect(wrapped).to respond_to(:to_path)
|
148
|
-
expect(wrapped).to respond_to(:close)
|
149
|
-
end
|
150
|
-
|
151
|
-
it "reads out the body in full using each()" do
|
152
|
-
expect(fake_body).to receive(:each).once.and_yield("a").and_yield("b").and_yield("c")
|
153
|
-
|
154
|
-
wrapped = described_class.wrap(fake_body, nil_txn)
|
155
|
-
expect { |b| wrapped.each(&b) }.to yield_successive_args("a", "b", "c")
|
156
|
-
end
|
157
|
-
|
158
|
-
it "sets the exception raised inside each() into the Appsignal transaction" do
|
159
|
-
expect(fake_body).to receive(:each).once.and_raise(Exception.new("Oops"))
|
160
|
-
|
161
|
-
txn = double("Appsignal transaction", "nil_transaction?" => false)
|
162
|
-
expect(txn).to receive(:set_error).once.with(instance_of(Exception))
|
163
|
-
|
164
|
-
wrapped = described_class.wrap(fake_body, txn)
|
165
|
-
expect do
|
166
|
-
expect { |b| wrapped.each(&b) }.to yield_control
|
167
|
-
end.to raise_error(/Oops/)
|
168
|
-
end
|
169
|
-
|
170
|
-
it "sets the exception raised inside to_path() into the Appsignal transaction" do
|
171
|
-
allow(fake_body).to receive(:to_path).once.and_raise(Exception.new("Oops"))
|
172
|
-
|
173
|
-
txn = double("Appsignal transaction", "nil_transaction?" => false)
|
174
|
-
expect(txn).to receive(:set_error).once.with(instance_of(Exception))
|
175
|
-
expect(txn).not_to receive(:complete) # gets called by the caller via close()
|
176
|
-
|
177
|
-
wrapped = described_class.wrap(fake_body, txn)
|
178
|
-
expect { wrapped.to_path }.to raise_error(/Oops/)
|
179
|
-
end
|
180
|
-
|
181
|
-
it "exposes to_path to the sender" do
|
182
|
-
allow(fake_body).to receive(:to_path).and_return("/tmp/file.bin")
|
183
|
-
|
184
|
-
wrapped = described_class.wrap(fake_body, nil_txn)
|
185
|
-
expect(wrapped.to_path).to eq("/tmp/file.bin")
|
186
|
-
end
|
187
|
-
end
|
188
|
-
|
189
|
-
describe "with a body only supporting call()" do
|
190
|
-
let(:fake_body) { double(:call => nil) }
|
191
|
-
it "wraps with appropriate class" do
|
192
|
-
wrapped = described_class.wrap(fake_body, nil_txn)
|
193
|
-
expect(wrapped).not_to respond_to(:each)
|
194
|
-
expect(wrapped).not_to respond_to(:to_ary)
|
195
|
-
expect(wrapped).to respond_to(:call)
|
196
|
-
expect(wrapped).not_to respond_to(:to_path)
|
197
|
-
expect(wrapped).to respond_to(:close)
|
198
|
-
end
|
199
|
-
|
200
|
-
it "passes the stream into the call() of the body" do
|
201
|
-
fake_rack_stream = double("stream")
|
202
|
-
expect(fake_body).to receive(:call).with(fake_rack_stream)
|
203
|
-
|
204
|
-
wrapped = described_class.wrap(fake_body, nil_txn)
|
205
|
-
expect { wrapped.call(fake_rack_stream) }.not_to raise_error
|
206
|
-
end
|
207
|
-
|
208
|
-
it "sets the exception raised inside call() into the Appsignal transaction" do
|
209
|
-
fake_rack_stream = double
|
210
|
-
allow(fake_body).to receive(:call).with(fake_rack_stream).and_raise(Exception.new("Oopsie"))
|
211
|
-
|
212
|
-
txn = double("Appsignal transaction", "nil_transaction?" => false)
|
213
|
-
expect(txn).to receive(:set_error).once.with(instance_of(Exception))
|
214
|
-
expect(txn).not_to receive(:complete) # gets called by the caller via close()
|
215
|
-
wrapped = described_class.wrap(fake_body, txn)
|
216
|
-
|
217
|
-
expect { wrapped.call(fake_rack_stream) }.to raise_error(/Oopsie/)
|
218
|
-
end
|
219
|
-
end
|
220
|
-
end
|