appsignal 3.9.3 → 3.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +22 -19
  3. data/CHANGELOG.md +92 -0
  4. data/README.md +0 -1
  5. data/Rakefile +1 -1
  6. data/build_matrix.yml +10 -12
  7. data/gemfiles/webmachine1.gemfile +5 -4
  8. data/lib/appsignal/config.rb +4 -0
  9. data/lib/appsignal/environment.rb +6 -1
  10. data/lib/appsignal/helpers/instrumentation.rb +163 -1
  11. data/lib/appsignal/hooks/active_job.rb +1 -6
  12. data/lib/appsignal/integrations/padrino.rb +21 -25
  13. data/lib/appsignal/integrations/rake.rb +46 -12
  14. data/lib/appsignal/integrations/sidekiq.rb +1 -11
  15. data/lib/appsignal/integrations/webmachine.rb +15 -9
  16. data/lib/appsignal/rack/abstract_middleware.rb +49 -12
  17. data/lib/appsignal/rack/body_wrapper.rb +143 -0
  18. data/lib/appsignal/rack/generic_instrumentation.rb +5 -4
  19. data/lib/appsignal/rack/grape_middleware.rb +1 -1
  20. data/lib/appsignal/rack/hanami_middleware.rb +1 -1
  21. data/lib/appsignal/rack/instrumentation_middleware.rb +62 -0
  22. data/lib/appsignal/rack/rails_instrumentation.rb +1 -3
  23. data/lib/appsignal/rack/sinatra_instrumentation.rb +1 -3
  24. data/lib/appsignal/rack/streaming_listener.rb +13 -59
  25. data/lib/appsignal/rack.rb +31 -0
  26. data/lib/appsignal/transaction.rb +50 -8
  27. data/lib/appsignal/version.rb +1 -1
  28. data/lib/appsignal.rb +3 -1
  29. data/spec/lib/appsignal/config_spec.rb +1 -0
  30. data/spec/lib/appsignal/hooks/rake_spec.rb +100 -17
  31. data/spec/lib/appsignal/integrations/padrino_spec.rb +181 -131
  32. data/spec/lib/appsignal/integrations/sinatra_spec.rb +10 -2
  33. data/spec/lib/appsignal/integrations/webmachine_spec.rb +65 -17
  34. data/spec/lib/appsignal/rack/abstract_middleware_spec.rb +96 -8
  35. data/spec/lib/appsignal/rack/body_wrapper_spec.rb +263 -0
  36. data/spec/lib/appsignal/rack/generic_instrumentation_spec.rb +70 -17
  37. data/spec/lib/appsignal/rack/grape_middleware_spec.rb +1 -1
  38. data/spec/lib/appsignal/rack/instrumentation_middleware_spec.rb +38 -0
  39. data/spec/lib/appsignal/rack/streaming_listener_spec.rb +43 -120
  40. data/spec/lib/appsignal/transaction_spec.rb +163 -4
  41. data/spec/lib/appsignal_spec.rb +197 -6
  42. data/spec/support/mocks/dummy_app.rb +1 -1
  43. metadata +8 -4
  44. data/support/check_versions +0 -22
@@ -3,40 +3,133 @@ if DependencyHelper.padrino_present?
3
3
  require "appsignal/integrations/padrino"
4
4
 
5
5
  describe Appsignal::Integrations::PadrinoPlugin do
6
- it "starts AppSignal on init" do
7
- expect(Appsignal).to receive(:start)
6
+ let(:callbacks) { { :before_load => nil } }
7
+ before do
8
+ Appsignal.config = nil
9
+ allow(Padrino).to receive(:before_load)
10
+ .and_wrap_original do |original_method, *args, &block|
11
+ callbacks[:before_load] = block
12
+ original_method.call(*args, &block)
13
+ end
14
+ end
15
+ after { uninstall_padrino_integration }
16
+
17
+ def uninstall_padrino_integration
18
+ expected_middleware = [
19
+ Rack::Events,
20
+ Appsignal::Rack::SinatraBaseInstrumentation
21
+ ]
22
+ Padrino.middleware.delete_if do |middleware|
23
+ expected_middleware.include?(middleware.first)
24
+ end
8
25
  end
9
26
 
10
- context "when not active" do
11
- before { allow(Appsignal).to receive(:active?).and_return(false) }
27
+ context "when already active" do
28
+ before { allow(Appsignal).to receive(:active?).and_return(true) }
12
29
 
13
- it "does not add the listener middleware to the stack" do
14
- expect(Padrino).to_not receive(:use)
30
+ it "does not start AppSignal again" do
31
+ expect(Appsignal::Config).to_not receive(:new)
32
+ expect(Appsignal).to_not receive(:start)
33
+
34
+ Appsignal::Integrations::PadrinoPlugin.init
35
+ callbacks[:before_load].call
36
+ end
37
+
38
+ it "adds the instrumentation middleware to Sinatra::Base" do
39
+ Appsignal::Integrations::PadrinoPlugin.init
40
+ callbacks[:before_load].call
41
+
42
+ middlewares = Padrino.middleware
43
+ expect(middlewares).to include(
44
+ [Rack::Events, [[instance_of(Appsignal::Rack::EventHandler)]], nil]
45
+ )
46
+ expect(middlewares).to include(
47
+ [
48
+ Appsignal::Rack::SinatraBaseInstrumentation,
49
+ [
50
+ :instrument_event_name => "process_action.padrino"
51
+ ],
52
+ nil
53
+ ]
54
+ )
15
55
  end
16
56
  end
17
57
 
18
- context "when APPSIGNAL_APP_ENV ENV var is provided" do
19
- it "uses this as the environment" do
20
- ENV["APPSIGNAL_APP_ENV"] = "custom"
58
+ context "with active config" do
59
+ before do
60
+ ENV["APPSIGNAL_APP_NAME"] = "My Padrino app name"
61
+ ENV["APPSIGNAL_APP_ENV"] = "test"
62
+ ENV["APPSIGNAL_PUSH_API_KEY"] = "my-key"
63
+ end
64
+
65
+ it "starts AppSignal on init" do
66
+ expect(Appsignal).to_not be_active
21
67
 
22
- # Reset the plugin to pull down the latest data
23
68
  Appsignal::Integrations::PadrinoPlugin.init
69
+ callbacks[:before_load].call
70
+
71
+ expect(Appsignal).to be_active
72
+ middlewares = Padrino.middleware
73
+ expect(middlewares).to include(
74
+ [Rack::Events, [[instance_of(Appsignal::Rack::EventHandler)]], nil]
75
+ )
76
+ expect(middlewares).to include(
77
+ [
78
+ Appsignal::Rack::SinatraBaseInstrumentation,
79
+ [
80
+ :instrument_event_name => "process_action.padrino"
81
+ ],
82
+ nil
83
+ ]
84
+ )
85
+ end
86
+
87
+ context "when APPSIGNAL_APP_ENV ENV var is provided" do
88
+ it "uses this as the environment" do
89
+ ENV["APPSIGNAL_APP_ENV"] = "custom"
90
+
91
+ Appsignal::Integrations::PadrinoPlugin.init
92
+ callbacks[:before_load].call
24
93
 
25
- expect(Appsignal.config.env).to eq("custom")
94
+ expect(Appsignal.config.env).to eq("custom")
95
+ end
96
+ end
97
+
98
+ context "when APPSIGNAL_APP_ENV ENV var is not provided" do
99
+ it "uses the Padrino environment" do
100
+ Appsignal::Integrations::PadrinoPlugin.init
101
+ callbacks[:before_load].call
102
+
103
+ expect(Padrino.env.to_s).to eq("test")
104
+ expect(Appsignal.config.env).to eq(Padrino.env.to_s)
105
+ end
26
106
  end
27
107
  end
28
108
 
29
- context "when APPSIGNAL_APP_ENV ENV var is not provided" do
30
- it "uses the Padrino environment" do
31
- # Reset the plugin to pull down the latest data
109
+ context "when not active" do
110
+ it "does not add the listener middleware to the stack" do
111
+ expect(Appsignal).to_not be_active
112
+
32
113
  Appsignal::Integrations::PadrinoPlugin.init
114
+ callbacks[:before_load].call
33
115
 
34
- expect(Padrino.env.to_s).to eq("test")
35
- expect(Appsignal.config.env).to eq(Padrino.env.to_s)
116
+ expect(Appsignal).to_not be_active
117
+ middlewares = Padrino.middleware
118
+ expect(middlewares).to_not include(
119
+ [Rack::Events, [[instance_of(Appsignal::Rack::EventHandler)]], nil]
120
+ )
121
+ expect(middlewares).to_not include(
122
+ [
123
+ Appsignal::Rack::SinatraBaseInstrumentation,
124
+ [
125
+ :request_class => ::Sinatra::Request,
126
+ :instrument_event_name => "process_action.padrino"
127
+ ],
128
+ nil
129
+ ]
130
+ )
36
131
  end
37
132
  end
38
-
39
- after { Appsignal::Integrations::PadrinoPlugin.init }
40
133
  end
41
134
 
42
135
  describe Padrino::Routing::InstanceMethods do
@@ -50,9 +143,9 @@ if DependencyHelper.padrino_present?
50
143
  # TODO: use an instance double
51
144
  let(:settings) { double(:name => "TestApp") }
52
145
  around { |example| keep_transactions { example.run } }
146
+ before { Appsignal.config = nil }
53
147
 
54
148
  describe "routes" do
55
- let(:request_kind) { kind_of(Sinatra::Request) }
56
149
  let(:env) do
57
150
  {
58
151
  "REQUEST_METHOD" => "GET",
@@ -83,20 +176,7 @@ if DependencyHelper.padrino_present?
83
176
  end
84
177
  end
85
178
 
86
- def expect_a_transaction_to_be_created
87
- transaction = last_transaction
88
- expect(transaction).to have_id
89
- expect(transaction).to have_namespace(Appsignal::Transaction::HTTP_REQUEST)
90
- expect(transaction).to include_metadata(
91
- "path" => path,
92
- "method" => "GET"
93
- )
94
- expect(transaction).to include_event("name" => "process_action.padrino")
95
- expect(transaction).to be_completed
96
- end
97
-
98
179
  context "when AppSignal is not active" do
99
- before { allow(Appsignal).to receive(:active?).and_return(false) }
100
180
  let(:path) { "/foo" }
101
181
  before { app.controllers { get(:foo) { "content" } } }
102
182
 
@@ -108,16 +188,17 @@ if DependencyHelper.padrino_present?
108
188
  end
109
189
 
110
190
  context "when AppSignal is active" do
111
- before { start_agent }
191
+ let(:transaction) { http_request_transaction }
192
+ before do
193
+ start_agent
194
+ set_current_transaction(transaction)
195
+ end
112
196
 
113
197
  context "with not existing route" do
114
198
  let(:path) { "/404" }
115
199
 
116
200
  it "instruments the request" do
117
201
  expect(response).to match_response(404, /^GET /404/)
118
-
119
- expect_a_transaction_to_be_created
120
- # Uses path for action name
121
202
  expect(last_transaction).to have_action("PadrinoTestApp#unknown")
122
203
  end
123
204
  end
@@ -130,9 +211,8 @@ if DependencyHelper.padrino_present?
130
211
  end
131
212
 
132
213
  it "does not instrument the request" do
133
- expect do
134
- expect(response).to match_response(200, "Static!")
135
- end.to_not(change { created_transactions.count })
214
+ expect(response).to match_response(200, "Static!")
215
+ expect(last_transaction).to_not have_action
136
216
  end
137
217
  end
138
218
 
@@ -145,11 +225,7 @@ if DependencyHelper.padrino_present?
145
225
  end
146
226
 
147
227
  it "falls back on Sinatra::Request#route_obj.original_path" do
148
- expect do
149
- expect(response).to match_response(200, "content")
150
- end.to(change { created_transactions.count }.by(1))
151
-
152
- expect_a_transaction_to_be_created
228
+ expect(response).to match_response(200, "content")
153
229
  expect(last_transaction).to have_action("PadrinoTestApp:/my_original_path/:id")
154
230
  end
155
231
  end
@@ -164,118 +240,92 @@ if DependencyHelper.padrino_present?
164
240
 
165
241
  it "falls back on app name" do
166
242
  expect(response).to match_response(200, "content")
167
- expect_a_transaction_to_be_created
168
243
  expect(last_transaction).to have_action("PadrinoTestApp#unknown")
169
244
  end
170
245
  end
171
246
 
172
247
  context "with existing route" do
173
- context "with an exception in the controller" do
174
- let(:path) { "/exception" }
175
- before do
176
- app.controllers { get(:exception) { raise ExampleException, "error message" } }
177
- expect { response }.to raise_error(ExampleException, "error message")
178
- expect_a_transaction_to_be_created
179
- end
248
+ let(:path) { "/" }
249
+ def make_request
250
+ expect(response).to match_response(200, "content")
251
+ end
180
252
 
181
- it "sets the action name based on the app name and action name" do
182
- expect(last_transaction).to have_action("PadrinoTestApp:#exception")
183
- end
253
+ context "with action name as symbol" do
254
+ context "with :index helper" do
255
+ before do
256
+ # :index == "/"
257
+ app.controllers { get(:index) { "content" } }
258
+ end
184
259
 
185
- it "sets the error on the transaction" do
186
- expect(last_transaction).to have_error("ExampleException", "error message")
260
+ it "sets the action with the app name and action name" do
261
+ make_request
262
+ expect(last_transaction).to have_action("PadrinoTestApp:#index")
263
+ end
187
264
  end
188
- end
189
265
 
190
- context "without an exception in the controller" do
191
- let(:path) { "/" }
192
- def make_request
193
- expect(response).to match_response(200, "content")
266
+ context "with custom action name" do
267
+ let(:path) { "/foo" }
268
+ before do
269
+ app.controllers { get(:foo) { "content" } }
270
+ end
271
+
272
+ it "sets the action with the app name and action name" do
273
+ make_request
274
+ expect(last_transaction).to have_action("PadrinoTestApp:#foo")
275
+ end
194
276
  end
277
+ end
195
278
 
196
- context "with action name as symbol" do
197
- context "with :index helper" do
198
- before do
199
- # :index == "/"
200
- app.controllers { get(:index) { "content" } }
201
- end
202
-
203
- it "sets the action with the app name and action name" do
204
- make_request
205
- expect_a_transaction_to_be_created
206
- expect(last_transaction).to have_action("PadrinoTestApp:#index")
207
- end
279
+ context "with an action defined with a path" do
280
+ context "with root path" do
281
+ before do
282
+ # :index == "/"
283
+ app.controllers { get("/") { "content" } }
208
284
  end
209
285
 
210
- context "with custom action name" do
211
- let(:path) { "/foo" }
212
- before do
213
- app.controllers { get(:foo) { "content" } }
214
- end
215
-
216
- it "sets the action with the app name and action name" do
217
- make_request
218
- expect_a_transaction_to_be_created
219
- expect(last_transaction).to have_action("PadrinoTestApp:#foo")
220
- end
286
+ it "sets the action with the app name and action path" do
287
+ make_request
288
+ expect(last_transaction).to have_action("PadrinoTestApp:#/")
221
289
  end
222
290
  end
223
291
 
224
- context "with an action defined with a path" do
225
- context "with root path" do
226
- before do
227
- # :index == "/"
228
- app.controllers { get("/") { "content" } }
229
- end
230
-
231
- it "sets the action with the app name and action path" do
232
- make_request
233
- expect_a_transaction_to_be_created
234
- expect(last_transaction).to have_action("PadrinoTestApp:#/")
235
- end
292
+ context "with custom path" do
293
+ let(:path) { "/foo" }
294
+ before do
295
+ app.controllers { get("/foo") { "content" } }
236
296
  end
237
297
 
238
- context "with custom path" do
239
- let(:path) { "/foo" }
240
- before do
241
- app.controllers { get("/foo") { "content" } }
242
- end
243
-
244
- it "sets the action with the app name and action path" do
245
- make_request
246
- expect_a_transaction_to_be_created
247
- expect(last_transaction).to have_action("PadrinoTestApp:#/foo")
248
- end
298
+ it "sets the action with the app name and action path" do
299
+ make_request
300
+ expect(last_transaction).to have_action("PadrinoTestApp:#/foo")
249
301
  end
250
302
  end
303
+ end
304
+
305
+ context "with controller" do
306
+ let(:path) { "/my_controller" }
251
307
 
252
- context "with controller" do
253
- let(:path) { "/my_controller" }
308
+ context "with controller as name" do
309
+ before do
310
+ # :index == "/"
311
+ app.controllers(:my_controller) { get(:index) { "content" } }
312
+ end
254
313
 
255
- context "with controller as name" do
256
- before do
257
- # :index == "/"
258
- app.controllers(:my_controller) { get(:index) { "content" } }
259
- end
314
+ it "sets the action with the app name, controller name and action name" do
315
+ make_request
316
+ expect(last_transaction).to have_action("PadrinoTestApp:my_controller#index")
317
+ end
318
+ end
260
319
 
261
- it "sets the action with the app name, controller name and action name" do
262
- make_request
263
- expect_a_transaction_to_be_created
264
- expect(last_transaction).to have_action("PadrinoTestApp:my_controller#index")
265
- end
320
+ context "with controller as path" do
321
+ before do
322
+ # :index == "/"
323
+ app.controllers("/my_controller") { get(:index) { "content" } }
266
324
  end
267
325
 
268
- context "with controller as path" do
269
- before do
270
- # :index == "/"
271
- app.controllers("/my_controller") { get(:index) { "content" } }
272
- end
273
-
274
- it "sets the action with the app name, controller name and action path" do
275
- make_request
276
- expect_a_transaction_to_be_created
277
- expect(last_transaction).to have_action("PadrinoTestApp:/my_controller#index")
278
- end
326
+ it "sets the action with the app name, controller name and action path" do
327
+ make_request
328
+ expect(last_transaction).to have_action("PadrinoTestApp:/my_controller#index")
279
329
  end
280
330
  end
281
331
  end
@@ -7,8 +7,12 @@ if DependencyHelper.sinatra_present?
7
7
 
8
8
  # "Uninstall" the AppSignal integration
9
9
  def uninstall_sinatra_integration
10
+ expected_middleware = [
11
+ Rack::Events,
12
+ Appsignal::Rack::SinatraBaseInstrumentation
13
+ ]
10
14
  Sinatra::Base.instance_variable_get(:@middleware).delete_if do |middleware|
11
- middleware.first == Appsignal::Rack::SinatraBaseInstrumentation
15
+ expected_middleware.include?(middleware.first)
12
16
  end
13
17
  end
14
18
 
@@ -29,7 +33,11 @@ if DependencyHelper.sinatra_present?
29
33
 
30
34
  it "adds the instrumentation middleware to Sinatra::Base" do
31
35
  install_sinatra_integration
32
- expect(Sinatra::Base.middleware.to_a).to include(
36
+ middlewares = Sinatra::Base.middleware.to_a
37
+ expect(middlewares).to include(
38
+ [Rack::Events, [[instance_of(Appsignal::Rack::EventHandler)]], nil]
39
+ )
40
+ expect(middlewares).to include(
33
41
  [Appsignal::Rack::SinatraBaseInstrumentation, [], nil]
34
42
  )
35
43
  end
@@ -15,31 +15,60 @@ if DependencyHelper.webmachine_present?
15
15
 
16
16
  describe Appsignal::Integrations::WebmachineIntegration do
17
17
  let(:request) do
18
- Webmachine::Request.new("GET", "http://google.com:80/foo", {}, nil)
18
+ Webmachine::Request.new(
19
+ "GET",
20
+ "http://google.com:80/foo?param1=value1&param2=value2",
21
+ {},
22
+ nil
23
+ )
19
24
  end
20
- let(:resource) { double(:trace? => false, :handle_exception => true, :"code=" => nil) }
21
- let(:response) { Response.new }
22
- let(:fsm) { Webmachine::Decision::FSM.new(resource, request, response) }
23
- before(:context) { start_agent }
24
- around { |example| keep_transactions { example.run } }
25
+ let(:app) do
26
+ proc do
27
+ def to_html
28
+ "Some HTML"
29
+ end
30
+ end
31
+ end
32
+ let(:resource) do
33
+ app_block = app
34
+ Class.new(Webmachine::Resource) do
35
+ class_eval(&app_block) if app_block
25
36
 
26
- # Make sure the request responds to the method we need to get query params.
27
- describe "request" do
28
- it "responds to #query" do
29
- expect(request).to respond_to(:query)
37
+ def self.name
38
+ "MyResource"
39
+ end
30
40
  end
31
41
  end
42
+ let(:resource_instance) { resource.new(request, response) }
43
+ let(:response) { Webmachine::Response.new }
44
+ let(:fsm) { Webmachine::Decision::FSM.new(resource_instance, request, response) }
45
+ before { start_agent }
46
+ around { |example| keep_transactions { example.run } }
32
47
 
33
48
  describe "#run" do
34
- before { allow(fsm).to receive(:call).and_call_original }
35
-
36
49
  it "creates a transaction" do
37
50
  expect { fsm.run }.to(change { created_transactions.count }.by(1))
38
51
  end
39
52
 
40
53
  it "sets the action" do
41
54
  fsm.run
42
- expect(last_transaction).to have_action("RSpec::Mocks::Double#GET")
55
+ expect(last_transaction).to have_action("MyResource#GET")
56
+ end
57
+
58
+ context "with action already set" do
59
+ let(:app) do
60
+ proc do
61
+ def to_html
62
+ Appsignal.set_action("Custom Action")
63
+ "Some HTML"
64
+ end
65
+ end
66
+ end
67
+
68
+ it "doesn't overwrite the action" do
69
+ fsm.run
70
+ expect(last_transaction).to have_action("Custom Action")
71
+ end
43
72
  end
44
73
 
45
74
  it "records an instrumentation event" do
@@ -47,16 +76,35 @@ if DependencyHelper.webmachine_present?
47
76
  expect(last_transaction).to include_event("name" => "process_action.webmachine")
48
77
  end
49
78
 
79
+ it "sets the params" do
80
+ fsm.run
81
+ expect(last_transaction).to include_params("param1" => "value1", "param2" => "value2")
82
+ end
83
+
50
84
  it "closes the transaction" do
51
85
  fsm.run
52
86
  expect(last_transaction).to be_completed
53
87
  expect(current_transaction?).to be_falsy
54
88
  end
55
89
 
56
- it "sets a response code" do
57
- expect(fsm.response.code).to be_nil
58
- fsm.run
59
- expect(fsm.response.code).not_to be_nil
90
+ context "with parent transaction" do
91
+ let(:transaction) { http_request_transaction }
92
+ before { set_current_transaction(transaction) }
93
+
94
+ it "sets the action" do
95
+ fsm.run
96
+ expect(last_transaction).to have_action("MyResource#GET")
97
+ end
98
+
99
+ it "sets the params" do
100
+ fsm.run
101
+ last_transaction._sample
102
+ expect(last_transaction).to include_params("param1" => "value1", "param2" => "value2")
103
+ end
104
+
105
+ it "does not close the transaction" do
106
+ expect(last_transaction).to_not be_completed
107
+ end
60
108
  end
61
109
  end
62
110