appsignal 3.10.0 → 3.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (135) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/CHANGELOG.md +197 -0
  4. data/Gemfile +1 -0
  5. data/Rakefile +1 -1
  6. data/benchmark.rake +99 -42
  7. data/lib/appsignal/cli/demo.rb +0 -1
  8. data/lib/appsignal/cli/diagnose.rb +1 -1
  9. data/lib/appsignal/config.rb +204 -130
  10. data/lib/appsignal/demo.rb +16 -26
  11. data/lib/appsignal/event_formatter/rom/sql_formatter.rb +1 -0
  12. data/lib/appsignal/event_formatter.rb +3 -2
  13. data/lib/appsignal/helpers/instrumentation.rb +331 -19
  14. data/lib/appsignal/hooks/action_cable.rb +21 -16
  15. data/lib/appsignal/hooks/active_job.rb +14 -8
  16. data/lib/appsignal/hooks/delayed_job.rb +1 -1
  17. data/lib/appsignal/hooks/shoryuken.rb +3 -63
  18. data/lib/appsignal/integrations/action_cable.rb +5 -7
  19. data/lib/appsignal/integrations/active_support_notifications.rb +1 -0
  20. data/lib/appsignal/integrations/capistrano/capistrano_2_tasks.rb +36 -35
  21. data/lib/appsignal/integrations/data_mapper.rb +1 -0
  22. data/lib/appsignal/integrations/delayed_job_plugin.rb +27 -33
  23. data/lib/appsignal/integrations/dry_monitor.rb +1 -0
  24. data/lib/appsignal/integrations/excon.rb +1 -0
  25. data/lib/appsignal/integrations/grape.rb +7 -0
  26. data/lib/appsignal/integrations/hanami.rb +8 -43
  27. data/lib/appsignal/integrations/http.rb +1 -0
  28. data/lib/appsignal/integrations/net_http.rb +1 -0
  29. data/lib/appsignal/integrations/object.rb +6 -0
  30. data/lib/appsignal/integrations/padrino.rb +8 -73
  31. data/lib/appsignal/integrations/que.rb +13 -20
  32. data/lib/appsignal/integrations/railtie.rb +36 -14
  33. data/lib/appsignal/integrations/rake.rb +1 -5
  34. data/lib/appsignal/integrations/redis.rb +1 -0
  35. data/lib/appsignal/integrations/redis_client.rb +1 -0
  36. data/lib/appsignal/integrations/resque.rb +2 -5
  37. data/lib/appsignal/integrations/shoryuken.rb +75 -0
  38. data/lib/appsignal/integrations/sidekiq.rb +7 -15
  39. data/lib/appsignal/integrations/sinatra.rb +8 -19
  40. data/lib/appsignal/integrations/unicorn.rb +1 -0
  41. data/lib/appsignal/integrations/webmachine.rb +2 -5
  42. data/lib/appsignal/loaders/grape.rb +13 -0
  43. data/lib/appsignal/loaders/hanami.rb +40 -0
  44. data/lib/appsignal/loaders/padrino.rb +68 -0
  45. data/lib/appsignal/loaders/sinatra.rb +24 -0
  46. data/lib/appsignal/loaders.rb +92 -0
  47. data/lib/appsignal/logger.rb +7 -3
  48. data/lib/appsignal/probes/helpers.rb +1 -0
  49. data/lib/appsignal/probes/mri.rb +1 -0
  50. data/lib/appsignal/probes/sidekiq.rb +1 -0
  51. data/lib/appsignal/probes.rb +3 -0
  52. data/lib/appsignal/rack/abstract_middleware.rb +20 -13
  53. data/lib/appsignal/rack/event_handler.rb +44 -13
  54. data/lib/appsignal/rack/generic_instrumentation.rb +1 -0
  55. data/lib/appsignal/rack/grape_middleware.rb +2 -1
  56. data/lib/appsignal/rack/streaming_listener.rb +1 -0
  57. data/lib/appsignal/rack.rb +35 -0
  58. data/lib/appsignal/span.rb +1 -0
  59. data/lib/appsignal/transaction.rb +308 -101
  60. data/lib/appsignal/utils/data.rb +0 -1
  61. data/lib/appsignal/utils/hash_sanitizer.rb +0 -1
  62. data/lib/appsignal/utils/integration_logger.rb +0 -13
  63. data/lib/appsignal/utils/integration_memory_logger.rb +0 -13
  64. data/lib/appsignal/utils/json.rb +0 -1
  65. data/lib/appsignal/utils/query_params_sanitizer.rb +0 -1
  66. data/lib/appsignal/utils/stdout_and_logger_message.rb +0 -1
  67. data/lib/appsignal/utils.rb +6 -0
  68. data/lib/appsignal/version.rb +1 -1
  69. data/lib/appsignal.rb +169 -14
  70. data/spec/lib/appsignal/capistrano2_spec.rb +1 -1
  71. data/spec/lib/appsignal/cli/demo_spec.rb +0 -1
  72. data/spec/lib/appsignal/cli/diagnose/paths_spec.rb +1 -1
  73. data/spec/lib/appsignal/cli/diagnose_spec.rb +0 -1
  74. data/spec/lib/appsignal/config_spec.rb +291 -44
  75. data/spec/lib/appsignal/demo_spec.rb +1 -2
  76. data/spec/lib/appsignal/environment_spec.rb +4 -2
  77. data/spec/lib/appsignal/hooks/action_cable_spec.rb +43 -74
  78. data/spec/lib/appsignal/hooks/active_support_notifications_spec.rb +3 -6
  79. data/spec/lib/appsignal/hooks/activejob_spec.rb +12 -3
  80. data/spec/lib/appsignal/hooks/delayed_job_spec.rb +2 -443
  81. data/spec/lib/appsignal/hooks/dry_monitor_spec.rb +4 -7
  82. data/spec/lib/appsignal/hooks/excon_spec.rb +3 -6
  83. data/spec/lib/appsignal/hooks/gvl_spec.rb +2 -2
  84. data/spec/lib/appsignal/hooks/http_spec.rb +1 -3
  85. data/spec/lib/appsignal/hooks/net_http_spec.rb +1 -1
  86. data/spec/lib/appsignal/hooks/redis_client_spec.rb +5 -8
  87. data/spec/lib/appsignal/hooks/redis_spec.rb +3 -6
  88. data/spec/lib/appsignal/hooks/resque_spec.rb +1 -1
  89. data/spec/lib/appsignal/hooks/sequel_spec.rb +3 -5
  90. data/spec/lib/appsignal/hooks/shoryuken_spec.rb +0 -171
  91. data/spec/lib/appsignal/hooks/sidekiq_spec.rb +1 -1
  92. data/spec/lib/appsignal/hooks/webmachine_spec.rb +1 -1
  93. data/spec/lib/appsignal/integrations/delayed_job_plugin_spec.rb +459 -0
  94. data/spec/lib/appsignal/integrations/grape_spec.rb +36 -0
  95. data/spec/lib/appsignal/integrations/hanami_spec.rb +9 -178
  96. data/spec/lib/appsignal/integrations/http_spec.rb +1 -5
  97. data/spec/lib/appsignal/integrations/mongo_ruby_driver_spec.rb +4 -2
  98. data/spec/lib/appsignal/integrations/net_http_spec.rb +1 -1
  99. data/spec/lib/appsignal/integrations/object_spec.rb +1 -3
  100. data/spec/lib/appsignal/integrations/padrino_spec.rb +8 -330
  101. data/spec/lib/appsignal/integrations/que_spec.rb +3 -4
  102. data/spec/lib/appsignal/integrations/railtie_spec.rb +275 -191
  103. data/spec/lib/appsignal/integrations/shoryuken_spec.rb +167 -0
  104. data/spec/lib/appsignal/integrations/sidekiq_spec.rb +15 -13
  105. data/spec/lib/appsignal/integrations/sinatra_spec.rb +9 -104
  106. data/spec/lib/appsignal/integrations/webmachine_spec.rb +13 -1
  107. data/spec/lib/appsignal/loaders/grape_spec.rb +12 -0
  108. data/spec/lib/appsignal/loaders/hanami_spec.rb +95 -0
  109. data/spec/lib/appsignal/loaders/padrino_spec.rb +277 -0
  110. data/spec/lib/appsignal/loaders/sinatra_spec.rb +47 -0
  111. data/spec/lib/appsignal/loaders_spec.rb +137 -0
  112. data/spec/lib/appsignal/probes/sidekiq_spec.rb +1 -1
  113. data/spec/lib/appsignal/probes_spec.rb +6 -5
  114. data/spec/lib/appsignal/rack/abstract_middleware_spec.rb +51 -5
  115. data/spec/lib/appsignal/rack/event_handler_spec.rb +114 -10
  116. data/spec/lib/appsignal/rack/generic_instrumentation_spec.rb +1 -1
  117. data/spec/lib/appsignal/rack/grape_middleware_spec.rb +2 -35
  118. data/spec/lib/appsignal/rack/hanami_middleware_spec.rb +1 -1
  119. data/spec/lib/appsignal/rack/rails_instrumentation_spec.rb +4 -2
  120. data/spec/lib/appsignal/rack/sinatra_instrumentation_spec.rb +3 -3
  121. data/spec/lib/appsignal/rack_spec.rb +63 -0
  122. data/spec/lib/appsignal/span_spec.rb +1 -3
  123. data/spec/lib/appsignal/transaction_spec.rb +1640 -1075
  124. data/spec/lib/appsignal/utils/integration_logger_spec.rb +12 -16
  125. data/spec/lib/appsignal/utils/integration_memory_logger_spec.rb +0 -10
  126. data/spec/lib/appsignal_spec.rb +601 -36
  127. data/spec/lib/puma/appsignal_spec.rb +0 -3
  128. data/spec/spec_helper.rb +5 -4
  129. data/spec/support/helpers/config_helpers.rb +2 -1
  130. data/spec/support/helpers/loader_helper.rb +21 -0
  131. data/spec/support/helpers/transaction_helpers.rb +44 -20
  132. data/spec/support/matchers/transaction.rb +15 -1
  133. data/spec/support/stubs/appsignal/loaders/loader_stub.rb +7 -0
  134. data/spec/support/testing.rb +47 -1
  135. metadata +19 -2
@@ -0,0 +1,277 @@
1
+ if DependencyHelper.padrino_present?
2
+ describe "Appsignal::Loaders::PadrinoLoader" do
3
+ before { Appsignal.config = nil }
4
+
5
+ describe "#on_load" do
6
+ it "registers Padrino default config" do
7
+ load_loader(:padrino)
8
+
9
+ expect(Appsignal::Config.loader_defaults).to include([
10
+ :padrino,
11
+ {
12
+ :env => :test,
13
+ :root_path => Padrino.mounted_root
14
+ }
15
+ ])
16
+ end
17
+ end
18
+
19
+ describe "#on_start" do
20
+ let(:callbacks) { { :before_load => nil } }
21
+ before do
22
+ allow(Padrino).to receive(:before_load)
23
+ .and_wrap_original do |original_method, *args, &block|
24
+ callbacks[:before_load] = block
25
+ original_method.call(*args, &block)
26
+ end
27
+ end
28
+ after { uninstall_padrino_integration }
29
+
30
+ def uninstall_padrino_integration
31
+ expected_middleware = [
32
+ Rack::Events,
33
+ Appsignal::Rack::SinatraBaseInstrumentation
34
+ ]
35
+ Padrino.middleware.delete_if do |middleware|
36
+ expected_middleware.include?(middleware.first)
37
+ end
38
+ end
39
+
40
+ it "adds the instrumentation middleware to Padrino" do
41
+ load_loader(:padrino)
42
+ start_loader(:padrino)
43
+
44
+ callbacks[:before_load].call
45
+
46
+ middlewares = Padrino.middleware
47
+ expect(middlewares).to include(
48
+ [Rack::Events, [[instance_of(Appsignal::Rack::EventHandler)]], nil]
49
+ )
50
+ expect(middlewares).to include(
51
+ [
52
+ Appsignal::Rack::SinatraBaseInstrumentation,
53
+ [
54
+ :instrument_event_name => "process_action.padrino"
55
+ ],
56
+ nil
57
+ ]
58
+ )
59
+ end
60
+ end
61
+
62
+ describe "Padrino integration" do
63
+ class PadrinoClassWithRouter
64
+ include Padrino::Routing
65
+ end
66
+
67
+ let(:base) { double }
68
+ let(:router) { PadrinoClassWithRouter.new }
69
+ let(:env) { {} }
70
+ # TODO: use an instance double
71
+ let(:settings) { double(:name => "TestApp") }
72
+ around { |example| keep_transactions { example.run } }
73
+ before { Appsignal.config = nil }
74
+
75
+ describe "routes" do
76
+ let(:env) do
77
+ {
78
+ "REQUEST_METHOD" => "GET",
79
+ "PATH_INFO" => path,
80
+ "REQUEST_PATH" => path,
81
+ "rack.input" => StringIO.new
82
+ }
83
+ end
84
+ let(:app) do
85
+ Class.new(Padrino::Application) do
86
+ def self.name
87
+ "PadrinoTestApp"
88
+ end
89
+ end
90
+ end
91
+ let(:response) { app.call(env) }
92
+
93
+ def fetch_body(body)
94
+ if body.respond_to?(:response)
95
+ _, _, nested_body = body.response.to_a
96
+ fetch_body(nested_body)
97
+ elsif body.respond_to?(:to_ary)
98
+ body.to_ary
99
+ else
100
+ body
101
+ end
102
+ end
103
+
104
+ RSpec::Matchers.define :match_response do |expected_status, expected_body|
105
+ match do |response|
106
+ status, _headers, potential_body = response
107
+ body = fetch_body(potential_body)
108
+
109
+ matches_body =
110
+ if expected_body.is_a?(Regexp)
111
+ body.join =~ expected_body
112
+ else
113
+ body == [expected_body].compact
114
+ end
115
+ status == expected_status && matches_body
116
+ end
117
+ end
118
+
119
+ context "when AppSignal is not active" do
120
+ let(:path) { "/foo" }
121
+ before { app.controllers { get(:foo) { "content" } } }
122
+
123
+ it "does not instrument the request" do
124
+ expect do
125
+ expect(response).to match_response(200, "content")
126
+ end.to_not(change { created_transactions.count })
127
+ end
128
+ end
129
+
130
+ context "when AppSignal is active" do
131
+ let(:transaction) { http_request_transaction }
132
+ before do
133
+ start_agent
134
+ set_current_transaction(transaction)
135
+ end
136
+
137
+ context "with not existing route" do
138
+ let(:path) { "/404" }
139
+
140
+ it "instruments the request" do
141
+ expect(response).to match_response(404, /^GET /404/)
142
+ expect(last_transaction).to have_action("PadrinoTestApp#unknown")
143
+ end
144
+ end
145
+
146
+ context "when Sinatra tells us it's a static file" do
147
+ let(:path) { "/static" }
148
+ before do
149
+ env["sinatra.static_file"] = true
150
+ app.controllers { get(:static) { "Static!" } }
151
+ end
152
+
153
+ it "does not instrument the request" do
154
+ expect(response).to match_response(200, "Static!")
155
+ expect(last_transaction).to_not have_action
156
+ end
157
+ end
158
+
159
+ # Older Padrino versions don't support `action` (v11.0+)
160
+ context "without #action on Sinatra::Request" do
161
+ let(:path) { "/my_original_path/10" }
162
+ before do
163
+ allow_any_instance_of(Sinatra::Request).to receive(:action).and_return(nil)
164
+ app.controllers { get(:my_original_path, :with => :id) { "content" } }
165
+ end
166
+
167
+ it "falls back on Sinatra::Request#route_obj.original_path" do
168
+ expect(response).to match_response(200, "content")
169
+ expect(last_transaction).to have_action("PadrinoTestApp:/my_original_path/:id")
170
+ end
171
+ end
172
+
173
+ context "without Sinatra::Request#route_obj.original_path" do
174
+ let(:path) { "/my_original_path" }
175
+ before do
176
+ allow_any_instance_of(Sinatra::Request).to receive(:action).and_return(nil)
177
+ allow_any_instance_of(Sinatra::Request).to receive(:route_obj).and_return(nil)
178
+ app.controllers { get(:my_original_path) { "content" } }
179
+ end
180
+
181
+ it "falls back on app name" do
182
+ expect(response).to match_response(200, "content")
183
+ expect(last_transaction).to have_action("PadrinoTestApp#unknown")
184
+ end
185
+ end
186
+
187
+ context "with existing route" do
188
+ let(:path) { "/" }
189
+ def make_request
190
+ expect(response).to match_response(200, "content")
191
+ end
192
+
193
+ context "with action name as symbol" do
194
+ context "with :index helper" do
195
+ before do
196
+ # :index == "/"
197
+ app.controllers { get(:index) { "content" } }
198
+ end
199
+
200
+ it "sets the action with the app name and action name" do
201
+ make_request
202
+ expect(last_transaction).to have_action("PadrinoTestApp:#index")
203
+ end
204
+ end
205
+
206
+ context "with custom action name" do
207
+ let(:path) { "/foo" }
208
+ before do
209
+ app.controllers { get(:foo) { "content" } }
210
+ end
211
+
212
+ it "sets the action with the app name and action name" do
213
+ make_request
214
+ expect(last_transaction).to have_action("PadrinoTestApp:#foo")
215
+ end
216
+ end
217
+ end
218
+
219
+ context "with an action defined with a path" do
220
+ context "with root path" do
221
+ before do
222
+ # :index == "/"
223
+ app.controllers { get("/") { "content" } }
224
+ end
225
+
226
+ it "sets the action with the app name and action path" do
227
+ make_request
228
+ expect(last_transaction).to have_action("PadrinoTestApp:#/")
229
+ end
230
+ end
231
+
232
+ context "with custom path" do
233
+ let(:path) { "/foo" }
234
+ before do
235
+ app.controllers { get("/foo") { "content" } }
236
+ end
237
+
238
+ it "sets the action with the app name and action path" do
239
+ make_request
240
+ expect(last_transaction).to have_action("PadrinoTestApp:#/foo")
241
+ end
242
+ end
243
+ end
244
+
245
+ context "with controller" do
246
+ let(:path) { "/my_controller" }
247
+
248
+ context "with controller as name" do
249
+ before do
250
+ # :index == "/"
251
+ app.controllers(:my_controller) { get(:index) { "content" } }
252
+ end
253
+
254
+ it "sets the action with the app name, controller name and action name" do
255
+ make_request
256
+ expect(last_transaction).to have_action("PadrinoTestApp:my_controller#index")
257
+ end
258
+ end
259
+
260
+ context "with controller as path" do
261
+ before do
262
+ # :index == "/"
263
+ app.controllers("/my_controller") { get(:index) { "content" } }
264
+ end
265
+
266
+ it "sets the action with the app name, controller name and action path" do
267
+ make_request
268
+ expect(last_transaction).to have_action("PadrinoTestApp:/my_controller#index")
269
+ end
270
+ end
271
+ end
272
+ end
273
+ end
274
+ end
275
+ end
276
+ end
277
+ end
@@ -0,0 +1,47 @@
1
+ if DependencyHelper.sinatra_present?
2
+ describe "Appsignal::Loaders::SinatraLoader" do
3
+ before { Appsignal.config = nil }
4
+
5
+ describe "#on_load" do
6
+ it "registers Sinatra default config" do
7
+ ::Sinatra::Application.settings.root = "/some/path"
8
+ load_loader(:sinatra)
9
+
10
+ expect(Appsignal::Config.loader_defaults).to include([
11
+ :sinatra,
12
+ {
13
+ :env => :test,
14
+ :root_path => "/some/path"
15
+ }
16
+ ])
17
+ end
18
+ end
19
+
20
+ describe "#on_start" do
21
+ after { uninstall_sinatra_integration }
22
+
23
+ def uninstall_sinatra_integration
24
+ expected_middleware = [
25
+ Rack::Events,
26
+ Appsignal::Rack::SinatraBaseInstrumentation
27
+ ]
28
+ Sinatra::Base.instance_variable_get(:@middleware).delete_if do |middleware|
29
+ expected_middleware.include?(middleware.first)
30
+ end
31
+ end
32
+
33
+ it "adds the instrumentation middleware to Sinatra::Base" do
34
+ load_loader(:sinatra)
35
+ start_loader(:sinatra)
36
+
37
+ middlewares = Sinatra::Base.middleware.to_a
38
+ expect(middlewares).to include(
39
+ [Rack::Events, [[instance_of(Appsignal::Rack::EventHandler)]], nil]
40
+ )
41
+ expect(middlewares).to include(
42
+ [Appsignal::Rack::SinatraBaseInstrumentation, [], nil]
43
+ )
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,137 @@
1
+ describe Appsignal::Loaders do
2
+ describe ".register" do
3
+ before do
4
+ define_loader(:test_loader) do
5
+ def on_load
6
+ puts "do something on_load"
7
+ register_config_defaults(
8
+ :root_path => "/some/path",
9
+ :env => "test env",
10
+ :active => false
11
+ )
12
+ end
13
+ end
14
+ end
15
+
16
+ it "registers a loader" do
17
+ define_loader(:test_loader)
18
+ expect(Appsignal::Loaders.loaders).to have_key(:test_loader)
19
+ end
20
+ end
21
+
22
+ describe ".unregister" do
23
+ it "unregisters a loader" do
24
+ define_loader(:test_loader)
25
+ expect(Appsignal::Loaders.loaders).to have_key(:test_loader)
26
+
27
+ Appsignal::Loaders.unregister(:test_loader)
28
+ expect(Appsignal::Loaders.loaders).to_not have_key(:test_loader)
29
+ end
30
+ end
31
+
32
+ describe ".load" do
33
+ it "calls the Loader's on_loader method" do
34
+ Appsignal::Testing.store[:loader_loaded] = 0
35
+ define_loader(:test_loader) do
36
+ def on_load
37
+ Appsignal::Testing.store[:loader_loaded] += 1
38
+ end
39
+ end
40
+ Appsignal::Loaders.load(:test_loader)
41
+
42
+ expect(Appsignal::Testing.store[:loader_loaded]).to eq(1)
43
+ end
44
+
45
+ it "registers config defaults" do
46
+ define_loader(:test_loader) do
47
+ def on_load
48
+ register_config_defaults(:my_option => true)
49
+ end
50
+ end
51
+ Appsignal::Loaders.load(:test_loader)
52
+
53
+ expect(Appsignal::Config.loader_defaults).to eq([[:test_loader, { :my_option => true }]])
54
+ end
55
+
56
+ it "does not load errors that aren't registered" do
57
+ logs =
58
+ capture_logs do
59
+ Appsignal::Loaders.load(:unknown_loader)
60
+ end
61
+
62
+ expect(logs).to contains_log(:warn, "No loader found with the name 'unknown_loader'.")
63
+ end
64
+
65
+ it "loads the loader file on load" do
66
+ expect(Appsignal::Loaders.registered?(:loader_stub)).to be_falsy
67
+ Appsignal::Loaders.load(:loader_stub)
68
+
69
+ expect(Appsignal::Loaders.registered?(:loader_stub)).to be_truthy
70
+ end
71
+
72
+ it "does not error when a loader has no on_load method" do
73
+ define_loader(:test_loader) do
74
+ # Do nothing
75
+ end
76
+ Appsignal::Loaders.load(:test_loader)
77
+ end
78
+
79
+ it "logs an error when an error occurs on load" do
80
+ define_loader(:test_loader) do
81
+ def on_load
82
+ raise ExampleStandardError, "uh oh"
83
+ end
84
+ end
85
+ logs =
86
+ capture_logs do
87
+ Appsignal::Loaders.load(:test_loader)
88
+ end
89
+
90
+ expect(logs).to contains_log(
91
+ :error,
92
+ "An error occurred while loading the 'test_loader' loader: ExampleStandardError: uh oh"
93
+ )
94
+ end
95
+ end
96
+
97
+ describe ".start" do
98
+ it "starts all loaded loaders" do
99
+ Appsignal::Testing.store[:loader_started] = 0
100
+ define_loader(:test_loader) do
101
+ def on_start
102
+ Appsignal::Testing.store[:loader_started] += 1
103
+ end
104
+ end
105
+ Appsignal::Loaders.load(:test_loader)
106
+ Appsignal::Loaders.start
107
+
108
+ expect(Appsignal::Testing.store[:loader_started]).to eq(1)
109
+ end
110
+
111
+ it "does not error when a loader has no on_start method" do
112
+ define_loader(:test_loader) do
113
+ # Do nothing
114
+ end
115
+ Appsignal::Loaders.load(:test_loader)
116
+ Appsignal::Loaders.start
117
+ end
118
+
119
+ it "logs an error when an error occurs on start" do
120
+ define_loader(:test_loader) do
121
+ def on_start
122
+ raise ExampleStandardError, "uh oh"
123
+ end
124
+ end
125
+ logs =
126
+ capture_logs do
127
+ Appsignal::Loaders.load(:test_loader)
128
+ Appsignal::Loaders.start
129
+ end
130
+
131
+ expect(logs).to contains_log(
132
+ :error,
133
+ "An error occurred while starting the 'test_loader' loader: ExampleStandardError: uh oh"
134
+ )
135
+ end
136
+ end
137
+ end
@@ -6,7 +6,7 @@ describe Appsignal::Probes::SidekiqProbe do
6
6
  let(:redis_hostname) { "localhost" }
7
7
  let(:expected_default_tags) { { :hostname => "localhost" } }
8
8
  before do
9
- Appsignal.config = project_fixture_config
9
+ start_agent
10
10
 
11
11
  class SidekiqStats
12
12
  class << self
@@ -408,13 +408,14 @@ describe Appsignal::Probes do
408
408
  end
409
409
 
410
410
  context "logger" do
411
- let(:log_stream) { std_stream }
412
- let(:log) { log_contents(log_stream) }
411
+ before { start_agent }
413
412
 
414
- around { |example| use_logger_with(log_stream) { example.run } }
415
413
  it "logs a deprecation message" do
416
- silence { collection.register :my_probe, lambda {} }
417
- expect(log).to contains_log :warn,
414
+ logs =
415
+ capture_logs do
416
+ silence { collection.register :my_probe, lambda {} }
417
+ end
418
+ expect(logs).to contains_log :warn,
418
419
  "The method 'Appsignal::Probes.probes.register' is deprecated. " \
419
420
  "Use 'Appsignal::Probes.register' instead."
420
421
  end
@@ -5,13 +5,14 @@ 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) { {} }
12
13
  let(:middleware) { described_class.new(app, options) }
13
14
 
14
- before(:context) { start_agent }
15
+ before { start_agent }
15
16
  around { |example| keep_transactions { example.run } }
16
17
 
17
18
  def make_request
@@ -247,7 +248,7 @@ describe Appsignal::Rack::AbstractMiddleware do
247
248
  end
248
249
  end
249
250
 
250
- context "with fetching the request method raises an error" do
251
+ context "when fetching the request method raises an error" do
251
252
  class BrokenRequestMethodRequest < Rack::Request
252
253
  def request_method
253
254
  raise "uh oh!"
@@ -255,11 +256,16 @@ describe Appsignal::Rack::AbstractMiddleware do
255
256
  end
256
257
 
257
258
  let(:options) { { :request_class => BrokenRequestMethodRequest } }
259
+
258
260
  it "does not store the invalid HTTP request method" do
259
261
  env["REQUEST_METHOD"] = "FOO"
260
- make_request
262
+ logs = capture_logs { make_request }
261
263
 
262
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
+ )
263
269
  end
264
270
  end
265
271
 
@@ -285,6 +291,35 @@ describe Appsignal::Rack::AbstractMiddleware do
285
291
  expect(last_transaction).to include_params("custom" => "param")
286
292
  end
287
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
288
323
  end
289
324
 
290
325
  context "with queue start header" do
@@ -316,6 +351,10 @@ describe Appsignal::Rack::AbstractMiddleware do
316
351
  def filtered_params
317
352
  { "abc" => "123" }
318
353
  end
354
+
355
+ def session
356
+ { "data" => "value" }
357
+ end
319
358
  end
320
359
 
321
360
  context "with overridden request class and params method" do
@@ -328,6 +367,12 @@ describe Appsignal::Rack::AbstractMiddleware do
328
367
 
329
368
  expect(last_transaction).to include_params("abc" => "123")
330
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
331
376
  end
332
377
 
333
378
  context "with parent instrumentation" do
@@ -355,11 +400,12 @@ describe Appsignal::Rack::AbstractMiddleware do
355
400
  expect(response_events).to eq(1)
356
401
  end
357
402
 
358
- context "when response body is already a BodyWrapper subclass" do
403
+ context "when the response body is already instrumented" do
359
404
  let(:body) { Appsignal::Rack::BodyWrapper.wrap(["hello!"], transaction) }
360
405
  let(:app) { DummyApp.new { [200, {}, body] } }
361
406
 
362
407
  it "doesn't wrap the body again" do
408
+ env[Appsignal::Rack::APPSIGNAL_RESPONSE_INSTRUMENTED] = true
363
409
  _status, _headers, body = make_request
364
410
  expect(body).to eq(body)
365
411