appsignal 3.6.1-java → 3.6.3-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/CHANGELOG.md +27 -4
- data/Rakefile +1 -1
- data/ext/agent.rb +27 -27
- data/lib/appsignal/auth_check.rb +1 -1
- data/lib/appsignal/config.rb +14 -1
- data/lib/appsignal/event_formatter/sequel/sql_formatter.rb +1 -1
- data/lib/appsignal/integrations/railtie.rb +14 -2
- data/lib/appsignal/logger.rb +5 -5
- 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/lib/puma/plugin/appsignal.rb +1 -1
- data/spec/lib/appsignal/capistrano2_spec.rb +2 -2
- data/spec/lib/appsignal/capistrano3_spec.rb +2 -2
- data/spec/lib/appsignal/cli/diagnose_spec.rb +1 -1
- data/spec/lib/appsignal/config_spec.rb +10 -5
- data/spec/lib/appsignal/environment_spec.rb +3 -3
- data/spec/lib/appsignal/event_formatter/mongo_ruby_driver/query_formatter_spec.rb +1 -1
- data/spec/lib/appsignal/hooks/resque_spec.rb +1 -1
- data/spec/lib/appsignal/hooks_spec.rb +1 -1
- data/spec/lib/appsignal/integrations/railtie_spec.rb +27 -2
- data/spec/lib/appsignal/integrations/sidekiq_spec.rb +1 -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
- data/spec/lib/appsignal/transaction_spec.rb +2 -2
- data/spec/lib/appsignal_spec.rb +1 -1
- data/spec/lib/puma/appsignal_spec.rb +1 -1
- metadata +3 -5
- data/lib/appsignal/rack/body_wrapper.rb +0 -161
- data/spec/lib/appsignal/rack/body_wrapper_spec.rb +0 -220
@@ -160,7 +160,7 @@ if DependencyHelper.capistrano2_present?
|
|
160
160
|
run
|
161
161
|
end
|
162
162
|
|
163
|
-
it "transmits the
|
163
|
+
it "transmits the overridden revision" do
|
164
164
|
expect(output).to include \
|
165
165
|
"Notifying AppSignal of deploy with: revision: abc123, user: batman",
|
166
166
|
"AppSignal has been notified of this deploy!"
|
@@ -174,7 +174,7 @@ if DependencyHelper.capistrano2_present?
|
|
174
174
|
run
|
175
175
|
end
|
176
176
|
|
177
|
-
it "transmits the
|
177
|
+
it "transmits the overridden deploy user" do
|
178
178
|
expect(output).to include \
|
179
179
|
"Notifying AppSignal of deploy with: revision: 503ce0923ed177a3ce000005," \
|
180
180
|
" user: robin",
|
@@ -172,7 +172,7 @@ if DependencyHelper.capistrano3_present?
|
|
172
172
|
run
|
173
173
|
end
|
174
174
|
|
175
|
-
it "transmits the
|
175
|
+
it "transmits the overridden revision" do
|
176
176
|
expect(output).to include \
|
177
177
|
"Notifying AppSignal of deploy with: revision: abc123, user: batman",
|
178
178
|
"AppSignal has been notified of this deploy!"
|
@@ -186,7 +186,7 @@ if DependencyHelper.capistrano3_present?
|
|
186
186
|
run
|
187
187
|
end
|
188
188
|
|
189
|
-
it "transmits the
|
189
|
+
it "transmits the overridden deploy user" do
|
190
190
|
expect(output).to include \
|
191
191
|
"Notifying AppSignal of deploy with: revision: 503ce0923ed177a3ce000005, " \
|
192
192
|
"user: robin",
|
@@ -466,7 +466,7 @@ describe Appsignal::CLI::Diagnose, :api_stub => true, :send_report => :yes_cli_i
|
|
466
466
|
end
|
467
467
|
end
|
468
468
|
|
469
|
-
context "when the
|
469
|
+
context "when the extension returns invalid JSON" do
|
470
470
|
before do
|
471
471
|
expect(Appsignal::Extension).to receive(:diagnose).and_return("invalid agent\njson")
|
472
472
|
run
|
@@ -6,7 +6,8 @@ describe Appsignal::Config do
|
|
6
6
|
configured_env_keys = (
|
7
7
|
config::ENV_STRING_KEYS +
|
8
8
|
config::ENV_BOOLEAN_KEYS +
|
9
|
-
config::ENV_ARRAY_KEYS
|
9
|
+
config::ENV_ARRAY_KEYS +
|
10
|
+
config::ENV_FLOAT_KEYS
|
10
11
|
).sort
|
11
12
|
|
12
13
|
expect(mapped_env_keys).to eql(configured_env_keys)
|
@@ -264,7 +265,7 @@ describe Appsignal::Config do
|
|
264
265
|
end
|
265
266
|
end
|
266
267
|
|
267
|
-
context "with an
|
268
|
+
context "with an overridden config file" do
|
268
269
|
let(:config) do
|
269
270
|
project_fixture_config("production", {}, Appsignal.internal_logger,
|
270
271
|
File.join(project_fixture_path, "config", "appsignal.yml"))
|
@@ -275,7 +276,7 @@ describe Appsignal::Config do
|
|
275
276
|
expect(config.active?).to be_truthy
|
276
277
|
end
|
277
278
|
|
278
|
-
context "with an invalid
|
279
|
+
context "with an invalid overridden config file" do
|
279
280
|
let(:config) do
|
280
281
|
project_fixture_config("production", {}, Appsignal.internal_logger,
|
281
282
|
File.join(project_fixture_path, "config", "missing.yml"))
|
@@ -300,7 +301,7 @@ describe Appsignal::Config do
|
|
300
301
|
stdout = std_stream
|
301
302
|
stderr = std_stream
|
302
303
|
log = capture_logs { capture_std_streams(stdout, stderr) { config } }
|
303
|
-
message = "An error
|
304
|
+
message = "An error occurred while loading the AppSignal config file. " \
|
304
305
|
"Skipping file config. " \
|
305
306
|
"In future versions AppSignal will not start on a config file " \
|
306
307
|
"error. To opt-in to this new behavior set " \
|
@@ -326,7 +327,7 @@ describe Appsignal::Config do
|
|
326
327
|
ENV["APPSIGNAL_PUSH_API_KEY"] = "something valid"
|
327
328
|
ENV["APPSIGNAL_INACTIVE_ON_CONFIG_FILE_ERROR"] = "1"
|
328
329
|
log = capture_logs { capture_std_streams(stdout, stderr) { config } }
|
329
|
-
message = "An error
|
330
|
+
message = "An error occurred while loading the AppSignal config file. " \
|
330
331
|
"Not starting AppSignal because APPSIGNAL_INACTIVE_ON_CONFIG_FILE_ERROR is set.\n" \
|
331
332
|
"File: #{File.join(config_path, "config", "appsignal.yml").inspect}\n" \
|
332
333
|
"KeyError: key not found"
|
@@ -414,6 +415,7 @@ describe Appsignal::Config do
|
|
414
415
|
:push_api_key => "aaa-bbb-ccc",
|
415
416
|
:active => true,
|
416
417
|
:bind_address => "0.0.0.0",
|
418
|
+
:cpu_count => 1.5,
|
417
419
|
:name => "App name",
|
418
420
|
:debug => true,
|
419
421
|
:dns_servers => ["8.8.8.8", "8.8.4.4"],
|
@@ -436,6 +438,7 @@ describe Appsignal::Config do
|
|
436
438
|
ENV["APPSIGNAL_ACTIVE"] = "true"
|
437
439
|
ENV["APPSIGNAL_APP_NAME"] = "App name"
|
438
440
|
ENV["APPSIGNAL_BIND_ADDRESS"] = "0.0.0.0"
|
441
|
+
ENV["APPSIGNAL_CPU_COUNT"] = "1.5"
|
439
442
|
ENV["APPSIGNAL_DEBUG"] = "true"
|
440
443
|
ENV["APPSIGNAL_DNS_SERVERS"] = "8.8.8.8,8.8.4.4"
|
441
444
|
ENV["APPSIGNAL_IGNORE_ACTIONS"] = "action1,action2"
|
@@ -631,6 +634,7 @@ describe Appsignal::Config do
|
|
631
634
|
let(:config) { project_fixture_config(:production) }
|
632
635
|
before do
|
633
636
|
config[:bind_address] = "0.0.0.0"
|
637
|
+
config[:cpu_count] = 1.5
|
634
638
|
config[:logging_endpoint] = "http://localhost:123"
|
635
639
|
config[:http_proxy] = "http://localhost"
|
636
640
|
config[:ignore_actions] = %w[action1 action2]
|
@@ -654,6 +658,7 @@ describe Appsignal::Config do
|
|
654
658
|
.to end_with("spec/support/fixtures/projects/valid")
|
655
659
|
expect(ENV.fetch("_APPSIGNAL_AGENT_PATH", nil)).to end_with("/ext")
|
656
660
|
expect(ENV.fetch("_APPSIGNAL_BIND_ADDRESS", nil)).to eq("0.0.0.0")
|
661
|
+
expect(ENV.fetch("_APPSIGNAL_CPU_COUNT", nil)).to eq("1.5")
|
657
662
|
expect(ENV.fetch("_APPSIGNAL_DEBUG_LOGGING", nil)).to eq "false"
|
658
663
|
expect(ENV.fetch("_APPSIGNAL_LOG", nil)).to eq "stdout"
|
659
664
|
expect(ENV.fetch("_APPSIGNAL_LOG_FILE_PATH", nil)).to end_with("/tmp/appsignal.log")
|
@@ -90,7 +90,7 @@ describe Appsignal::Environment do
|
|
90
90
|
end
|
91
91
|
end
|
92
92
|
|
93
|
-
context "when something
|
93
|
+
context "when something unforeseen errors" do
|
94
94
|
it "does not re-raise the error and writes it to the log" do
|
95
95
|
klass = Class.new do
|
96
96
|
def inspect
|
@@ -127,7 +127,7 @@ describe Appsignal::Environment do
|
|
127
127
|
expect(rake_spec.version.to_s).to_not be_empty
|
128
128
|
end
|
129
129
|
|
130
|
-
context "when something
|
130
|
+
context "when something unforeseen errors" do
|
131
131
|
it "does not re-raise the error and writes it to the log" do
|
132
132
|
expect(Bundler).to receive(:rubygems).and_raise(RuntimeError, "bundler error")
|
133
133
|
|
@@ -148,7 +148,7 @@ describe Appsignal::Environment do
|
|
148
148
|
expect_environment_metadata("ruby_a_test_enabled", "true")
|
149
149
|
end
|
150
150
|
|
151
|
-
context "when something
|
151
|
+
context "when something unforeseen errors" do
|
152
152
|
it "does not re-raise the error and writes it to the log" do
|
153
153
|
klass = Class.new do
|
154
154
|
def to_s
|
@@ -21,7 +21,7 @@ describe Appsignal::EventFormatter::MongoRubyDriver::QueryFormatter do
|
|
21
21
|
formatter.format(strategy, command)
|
22
22
|
end
|
23
23
|
|
24
|
-
context "when strategy is
|
24
|
+
context "when strategy is unknown" do
|
25
25
|
let(:strategy) { :bananas }
|
26
26
|
|
27
27
|
it "should return an empty hash" do
|
@@ -160,7 +160,7 @@ describe Appsignal::Hooks::ResqueHook do
|
|
160
160
|
end
|
161
161
|
after { Object.send(:remove_const, :ActiveJobMock) }
|
162
162
|
|
163
|
-
it "does not set arguments but lets the ActiveJob
|
163
|
+
it "does not set arguments but lets the ActiveJob integration handle it" do
|
164
164
|
perform_job(
|
165
165
|
ResqueTestJob,
|
166
166
|
"class" => "ActiveJob::QueueAdapters::ResqueAdapter::JobWrapper",
|
@@ -47,7 +47,7 @@ describe Appsignal::Hooks do
|
|
47
47
|
Appsignal::Hooks.hooks.delete(:mock_present_hook)
|
48
48
|
end
|
49
49
|
|
50
|
-
it "should not install if
|
50
|
+
it "should not install if dependencies are not present" do
|
51
51
|
Appsignal::Hooks::Hook.register(:mock_not_present_hook, MockNotPresentHook)
|
52
52
|
|
53
53
|
expect(Appsignal::Hooks.hooks[:mock_not_present_hook]).to be_instance_of(MockNotPresentHook)
|
@@ -256,10 +256,28 @@ if DependencyHelper.rails_present?
|
|
256
256
|
end
|
257
257
|
|
258
258
|
context "when no transaction is active" do
|
259
|
+
class ExampleRailsRequestMock
|
260
|
+
def path
|
261
|
+
"path"
|
262
|
+
end
|
263
|
+
|
264
|
+
def method
|
265
|
+
"GET"
|
266
|
+
end
|
267
|
+
|
268
|
+
def filtered_parameters
|
269
|
+
{ :user_id => 123, :password => "[FILTERED]" }
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
259
273
|
class ExampleRailsControllerMock
|
260
274
|
def action_name
|
261
275
|
"index"
|
262
276
|
end
|
277
|
+
|
278
|
+
def request
|
279
|
+
@request ||= ExampleRailsRequestMock.new
|
280
|
+
end
|
263
281
|
end
|
264
282
|
|
265
283
|
class ExampleRailsJobMock
|
@@ -275,7 +293,7 @@ if DependencyHelper.rails_present?
|
|
275
293
|
clear_current_transaction!
|
276
294
|
end
|
277
295
|
|
278
|
-
it "fetches the action from the controller in the context" do
|
296
|
+
it "fetches the action, path and method from the controller in the context" do
|
279
297
|
# The controller key is set by Rails when raised in a controller
|
280
298
|
given_context = { :controller => ExampleRailsControllerMock.new }
|
281
299
|
with_rails_error_reporter do
|
@@ -285,7 +303,14 @@ if DependencyHelper.rails_present?
|
|
285
303
|
transaction = last_transaction
|
286
304
|
transaction_hash = transaction.to_h
|
287
305
|
expect(transaction_hash).to include(
|
288
|
-
"action" => "ExampleRailsControllerMock#index"
|
306
|
+
"action" => "ExampleRailsControllerMock#index",
|
307
|
+
"metadata" => hash_including(
|
308
|
+
"path" => "path",
|
309
|
+
"method" => "GET"
|
310
|
+
),
|
311
|
+
"sample_data" => hash_including(
|
312
|
+
"params" => { "user_id" => 123, "password" => "[FILTERED]" }
|
313
|
+
)
|
289
314
|
)
|
290
315
|
end
|
291
316
|
|
@@ -8,7 +8,7 @@ describe Appsignal::Integrations::SidekiqErrorHandler do
|
|
8
8
|
end
|
9
9
|
around { |example| keep_transactions { example.run } }
|
10
10
|
|
11
|
-
context "without a current
|
11
|
+
context "without a current transaction" do
|
12
12
|
let(:exception) do
|
13
13
|
raise ExampleStandardError, "uh oh"
|
14
14
|
rescue => error
|
@@ -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
|
@@ -303,7 +303,7 @@ describe Appsignal::Transaction do
|
|
303
303
|
context "with overridden options" do
|
304
304
|
let(:options) { { :params_method => :filtered_params } }
|
305
305
|
|
306
|
-
it "sets the
|
306
|
+
it "sets the overridden :params_method" do
|
307
307
|
expect(subject[:params_method]).to eq :filtered_params
|
308
308
|
end
|
309
309
|
end
|
@@ -751,7 +751,7 @@ describe Appsignal::Transaction do
|
|
751
751
|
e
|
752
752
|
end
|
753
753
|
|
754
|
-
it "should also respond to add_exception for
|
754
|
+
it "should also respond to add_exception for backwards compatibility" do
|
755
755
|
expect(transaction).to respond_to(:add_exception)
|
756
756
|
end
|
757
757
|
|
data/spec/lib/appsignal_spec.rb
CHANGED
@@ -204,7 +204,7 @@ RSpec.describe "Puma plugin" do
|
|
204
204
|
}
|
205
205
|
end
|
206
206
|
|
207
|
-
it "collects puma stats as
|
207
|
+
it "collects puma stats as gauge metrics with the (summed) worker metrics" do
|
208
208
|
run_plugin(stats_data, appsignal_plugin) do
|
209
209
|
expect(logs).to_not include([:error, kind_of(String)])
|
210
210
|
expect_gauge(:workers, 2, "type" => "count")
|
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.3
|
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-20 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
|