appsignal 3.8.1 → 3.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,19 +1,83 @@
1
1
  require "appsignal/integrations/sidekiq"
2
2
 
3
- describe Appsignal::Integrations::SidekiqErrorHandler do
4
- let(:log) { StringIO.new }
5
- before do
6
- start_agent
7
- Appsignal.internal_logger = test_logger(log)
8
- end
3
+ describe Appsignal::Integrations::SidekiqDeathHandler do
4
+ before { start_agent }
9
5
  around { |example| keep_transactions { example.run } }
10
6
 
11
- context "without a current transaction" do
12
- let(:exception) do
13
- raise ExampleStandardError, "uh oh"
14
- rescue => error
15
- error
7
+ let(:exception) do
8
+ raise ExampleStandardError, "uh oh"
9
+ rescue => error
10
+ error
11
+ end
12
+ let(:job_context) { {} }
13
+ let(:transaction) { http_request_transaction }
14
+ before { set_current_transaction(transaction) }
15
+
16
+ def call_handler
17
+ expect do
18
+ described_class.new.call(job_context, exception)
19
+ end.to_not(change { created_transactions.count })
20
+ end
21
+
22
+ def expect_error_on_transaction
23
+ expect(last_transaction.to_h).to include(
24
+ "error" => hash_including(
25
+ "name" => "ExampleStandardError",
26
+ "message" => "uh oh",
27
+ "backtrace" => kind_of(String)
28
+ )
29
+ )
30
+ end
31
+
32
+ def expect_no_error_on_transaction
33
+ expect(last_transaction.to_h).to include("error" => nil)
34
+ end
35
+
36
+ context "when sidekiq_report_errors = none" do
37
+ before do
38
+ Appsignal.config[:sidekiq_report_errors] = "none"
39
+ call_handler
40
+ end
41
+
42
+ it "doesn't track the error on the transaction" do
43
+ expect_no_error_on_transaction
16
44
  end
45
+ end
46
+
47
+ context "when sidekiq_report_errors = all" do
48
+ before do
49
+ Appsignal.config[:sidekiq_report_errors] = "all"
50
+ call_handler
51
+ end
52
+
53
+ it "doesn't track the error on the transaction" do
54
+ expect_no_error_on_transaction
55
+ end
56
+ end
57
+
58
+ context "when sidekiq_report_errors = discard" do
59
+ before do
60
+ Appsignal.config[:sidekiq_report_errors] = "discard"
61
+ call_handler
62
+ end
63
+
64
+ it "records each occurrence of the error on the transaction" do
65
+ expect_error_on_transaction
66
+ end
67
+ end
68
+ end
69
+
70
+ describe Appsignal::Integrations::SidekiqErrorHandler do
71
+ before { start_agent }
72
+ around { |example| keep_transactions { example.run } }
73
+
74
+ let(:exception) do
75
+ raise ExampleStandardError, "uh oh"
76
+ rescue => error
77
+ error
78
+ end
79
+
80
+ context "when error is an internal error" do
17
81
  let(:job_context) do
18
82
  {
19
83
  :context => "Sidekiq internal error!",
@@ -21,14 +85,19 @@ describe Appsignal::Integrations::SidekiqErrorHandler do
21
85
  }
22
86
  end
23
87
 
24
- it "tracks error on a new transaction" do
25
- described_class.new.call(exception, job_context)
88
+ def expect_report_internal_error
89
+ expect do
90
+ described_class.new.call(exception, job_context)
91
+ end.to(change { created_transactions.count }.by(1))
26
92
 
27
93
  transaction_hash = last_transaction.to_h
28
- expect(transaction_hash["error"]).to include(
29
- "name" => "ExampleStandardError",
30
- "message" => "uh oh",
31
- "backtrace" => kind_of(String)
94
+ expect(transaction_hash).to include(
95
+ "action" => "SidekiqInternal",
96
+ "error" => hash_including(
97
+ "name" => "ExampleStandardError",
98
+ "message" => "uh oh",
99
+ "backtrace" => kind_of(String)
100
+ )
32
101
  )
33
102
  expect(transaction_hash["sample_data"]).to include(
34
103
  "params" => {
@@ -39,6 +108,95 @@ describe Appsignal::Integrations::SidekiqErrorHandler do
39
108
  "sidekiq_error" => "Sidekiq internal error!"
40
109
  )
41
110
  end
111
+
112
+ context "when sidekiq_report_errors = none" do
113
+ before { Appsignal.config[:sidekiq_report_errors] = "none" }
114
+
115
+ it "tracks the error on a new transaction" do
116
+ expect_report_internal_error
117
+ end
118
+ end
119
+
120
+ context "when sidekiq_report_errors = all" do
121
+ before { Appsignal.config[:sidekiq_report_errors] = "all" }
122
+
123
+ it "tracks the error on a new transaction" do
124
+ expect_report_internal_error
125
+ end
126
+ end
127
+
128
+ context "when sidekiq_report_errors = discard" do
129
+ before { Appsignal.config[:sidekiq_report_errors] = "discard" }
130
+
131
+ it "tracks the error on a new transaction" do
132
+ expect_report_internal_error
133
+ end
134
+ end
135
+ end
136
+
137
+ context "when error is a job error" do
138
+ let(:sidekiq_context) { { :job => {} } }
139
+ let(:transaction) { http_request_transaction }
140
+ before do
141
+ transaction.set_action("existing transaction action")
142
+ set_current_transaction(transaction)
143
+ end
144
+
145
+ def call_handler
146
+ expect do
147
+ described_class.new.call(exception, sidekiq_context)
148
+ end.to_not(change { created_transactions.count })
149
+ end
150
+
151
+ def expect_error_on_transaction
152
+ expect(last_transaction.to_h).to include(
153
+ "error" => hash_including(
154
+ "name" => "ExampleStandardError",
155
+ "message" => "uh oh",
156
+ "backtrace" => kind_of(String)
157
+ )
158
+ )
159
+ end
160
+
161
+ def expect_no_error_on_transaction
162
+ expect(last_transaction.to_h).to include("error" => nil)
163
+ end
164
+
165
+ context "when sidekiq_report_errors = none" do
166
+ before do
167
+ Appsignal.config[:sidekiq_report_errors] = "none"
168
+ call_handler
169
+ end
170
+
171
+ it "doesn't track the error on the transaction" do
172
+ expect_no_error_on_transaction
173
+ expect(last_transaction).to be_completed
174
+ end
175
+ end
176
+
177
+ context "when sidekiq_report_errors = all" do
178
+ before do
179
+ Appsignal.config[:sidekiq_report_errors] = "all"
180
+ call_handler
181
+ end
182
+
183
+ it "records each occurrence of the error on the transaction" do
184
+ expect_error_on_transaction
185
+ expect(last_transaction).to be_completed
186
+ end
187
+ end
188
+
189
+ context "when sidekiq_report_errors = discard" do
190
+ before do
191
+ Appsignal.config[:sidekiq_report_errors] = "discard"
192
+ call_handler
193
+ end
194
+
195
+ it "doesn't track the error on the transaction" do
196
+ expect_no_error_on_transaction
197
+ expect(last_transaction).to be_completed
198
+ end
199
+ end
42
200
  end
43
201
  end
44
202
 
@@ -13,59 +13,83 @@ if DependencyHelper.sinatra_present?
13
13
  end
14
14
 
15
15
  describe "Sinatra integration" do
16
- before { allow(Appsignal).to receive(:active?).and_return(true) }
16
+ before do
17
+ Appsignal.config = nil
18
+ end
17
19
  after { uninstall_sinatra_integration }
18
20
 
19
- context "Appsignal.internal_logger" do
20
- subject { Appsignal.internal_logger }
21
+ context "when active" do
22
+ before { allow(Appsignal).to receive(:active?).and_return(true) }
23
+
24
+ it "does not start AppSignal again" do
25
+ expect(Appsignal::Config).to_not receive(:new)
26
+ expect(Appsignal).to_not receive(:start)
27
+ expect(Appsignal).to_not receive(:start_logger)
28
+ install_sinatra_integration
29
+ end
21
30
 
22
- it "sets a logger" do
31
+ it "adds the instrumentation middleware to Sinatra::Base" do
23
32
  install_sinatra_integration
24
- is_expected.to be_a Logger
33
+ expect(Sinatra::Base.middleware.to_a).to include(
34
+ [Appsignal::Rack::SinatraBaseInstrumentation, [], nil]
35
+ )
25
36
  end
26
37
  end
27
38
 
28
- describe "middleware" do
29
- context "when AppSignal is not active" do
30
- before { allow(Appsignal).to receive(:active?).and_return(false) }
39
+ context "when not active" do
40
+ context "Appsignal.internal_logger" do
41
+ subject { Appsignal.internal_logger }
31
42
 
32
- it "does not add the instrumentation middleware to Sinatra::Base" do
43
+ it "sets a logger" do
33
44
  install_sinatra_integration
34
- expect(Sinatra::Base.middleware.to_a).to_not include(
35
- [Appsignal::Rack::SinatraBaseInstrumentation, [], nil]
36
- )
45
+ is_expected.to be_a Logger
37
46
  end
38
47
  end
39
48
 
40
- context "when AppSignal is active" do
41
- it "adds the instrumentation middleware to Sinatra::Base" do
42
- install_sinatra_integration
43
- expect(Sinatra::Base.middleware.to_a).to include(
44
- [Appsignal::Rack::SinatraBaseInstrumentation, [], nil]
45
- )
49
+ describe "middleware" do
50
+ context "when AppSignal is not active" do
51
+ it "does not add the instrumentation middleware to Sinatra::Base" do
52
+ install_sinatra_integration
53
+ expect(Sinatra::Base.middleware.to_a).to_not include(
54
+ [Appsignal::Rack::SinatraBaseInstrumentation, [], nil]
55
+ )
56
+ end
46
57
  end
47
- end
48
- end
49
-
50
- describe "environment" do
51
- subject { Appsignal.config.env }
52
58
 
53
- context "without APPSIGNAL_APP_ENV" do
54
- before { install_sinatra_integration }
59
+ context "when the new AppSignal config is active" do
60
+ it "adds the instrumentation middleware to Sinatra::Base" do
61
+ ENV["APPSIGNAL_APP_NAME"] = "My Sinatra app name"
62
+ ENV["APPSIGNAL_APP_ENV"] = "test"
63
+ ENV["APPSIGNAL_PUSH_API_KEY"] = "my-key"
55
64
 
56
- it "uses the app environment" do
57
- expect(subject).to eq("test")
65
+ install_sinatra_integration
66
+ expect(Sinatra::Base.middleware.to_a).to include(
67
+ [Appsignal::Rack::SinatraBaseInstrumentation, [], nil]
68
+ )
69
+ end
58
70
  end
59
71
  end
60
72
 
61
- context "with APPSIGNAL_APP_ENV" do
62
- before do
63
- ENV["APPSIGNAL_APP_ENV"] = "env-staging"
64
- install_sinatra_integration
73
+ describe "environment" do
74
+ subject { Appsignal.config.env }
75
+
76
+ context "without APPSIGNAL_APP_ENV" do
77
+ before { install_sinatra_integration }
78
+
79
+ it "uses the app environment" do
80
+ expect(subject).to eq("test")
81
+ end
65
82
  end
66
83
 
67
- it "uses the environment variable" do
68
- expect(subject).to eq("env-staging")
84
+ context "with APPSIGNAL_APP_ENV" do
85
+ before do
86
+ ENV["APPSIGNAL_APP_ENV"] = "env-staging"
87
+ install_sinatra_integration
88
+ end
89
+
90
+ it "uses the environment variable" do
91
+ expect(subject).to eq("env-staging")
92
+ end
69
93
  end
70
94
  end
71
95
  end
@@ -0,0 +1,250 @@
1
+ describe Appsignal::Rack::AbstractMiddleware do
2
+ let(:app) { double(:call => true) }
3
+ let(:request_path) { "/some/path" }
4
+ let(:env) do
5
+ Rack::MockRequest.env_for(
6
+ request_path,
7
+ "REQUEST_METHOD" => "GET",
8
+ :params => { "page" => 2, "query" => "lorem" }
9
+ )
10
+ end
11
+ let(:options) { {} }
12
+ let(:middleware) { Appsignal::Rack::AbstractMiddleware.new(app, options) }
13
+
14
+ before(:context) { start_agent }
15
+ around { |example| keep_transactions { example.run } }
16
+
17
+ def make_request(env)
18
+ middleware.call(env)
19
+ end
20
+
21
+ def make_request_with_error(env, error)
22
+ expect { make_request(env) }.to raise_error(error)
23
+ end
24
+
25
+ describe "#call" do
26
+ context "when appsignal is not active" do
27
+ before { allow(Appsignal).to receive(:active?).and_return(false) }
28
+
29
+ it "does not instrument requests" do
30
+ expect { make_request(env) }.to_not(change { created_transactions.count })
31
+ end
32
+
33
+ it "calls the next middleware in the stack" do
34
+ expect(app).to receive(:call).with(env)
35
+ make_request(env)
36
+ end
37
+ end
38
+
39
+ context "when appsignal is active" do
40
+ before { allow(Appsignal).to receive(:active?).and_return(true) }
41
+
42
+ it "calls the next middleware in the stack" do
43
+ make_request(env)
44
+
45
+ expect(app).to have_received(:call).with(env)
46
+ end
47
+
48
+ context "without an exception" do
49
+ it "create a transaction for the request" do
50
+ expect { make_request(env) }.to(change { created_transactions.count }.by(1))
51
+
52
+ expect(last_transaction.to_h).to include(
53
+ "namespace" => Appsignal::Transaction::HTTP_REQUEST,
54
+ "action" => nil,
55
+ "error" => nil
56
+ )
57
+ end
58
+
59
+ it "reports a process.abstract event" do
60
+ make_request(env)
61
+
62
+ expect(last_transaction.to_h).to include(
63
+ "events" => [
64
+ hash_including(
65
+ "body" => "",
66
+ "body_format" => Appsignal::EventFormatter::DEFAULT,
67
+ "count" => 1,
68
+ "name" => "process.abstract",
69
+ "title" => ""
70
+ )
71
+ ]
72
+ )
73
+ end
74
+
75
+ it "completes the transaction" do
76
+ make_request(env)
77
+ expect(last_transaction).to be_completed
78
+ end
79
+ end
80
+
81
+ context "with an exception" do
82
+ let(:error) { ExampleException.new("error message") }
83
+ before do
84
+ allow(app).to receive(:call).and_raise(error)
85
+ expect { make_request_with_error(env, error) }
86
+ .to(change { created_transactions.count }.by(1))
87
+ end
88
+
89
+ it "creates a transaction for the request and records the exception" do
90
+ expect(last_transaction.to_h).to include(
91
+ "error" => hash_including(
92
+ "name" => "ExampleException",
93
+ "message" => "error message"
94
+ )
95
+ )
96
+ end
97
+
98
+ it "completes the transaction" do
99
+ expect(last_transaction).to be_completed
100
+ end
101
+ end
102
+
103
+ context "without action name metadata" do
104
+ it "reports no action name" do
105
+ make_request(env)
106
+
107
+ expect(last_transaction.to_h).to include("action" => nil)
108
+ end
109
+ end
110
+
111
+ context "with appsignal.route env" do
112
+ before do
113
+ env["appsignal.route"] = "POST /my-route"
114
+ end
115
+
116
+ it "reports the appsignal.route value as the action name" do
117
+ make_request(env)
118
+
119
+ expect(last_transaction.to_h).to include("action" => "POST /my-route")
120
+ end
121
+ end
122
+
123
+ context "with appsignal.action env" do
124
+ before do
125
+ env["appsignal.action"] = "POST /my-action"
126
+ end
127
+
128
+ it "reports the appsignal.route value as the action name" do
129
+ make_request(env)
130
+
131
+ expect(last_transaction.to_h).to include("action" => "POST /my-action")
132
+ end
133
+ end
134
+
135
+ describe "request metadata" do
136
+ before do
137
+ env.merge("PATH_INFO" => "/some/path", "REQUEST_METHOD" => "GET")
138
+ end
139
+
140
+ it "sets request metadata" do
141
+ make_request(env)
142
+
143
+ expect(last_transaction.to_h).to include(
144
+ "metadata" => {
145
+ "method" => "GET",
146
+ "path" => "/some/path"
147
+ },
148
+ "sample_data" => hash_including(
149
+ "environment" => hash_including(
150
+ "REQUEST_METHOD" => "GET",
151
+ "PATH_INFO" => "/some/path"
152
+ # and more, but we don't need to test Rack mock defaults
153
+ )
154
+ )
155
+ )
156
+ end
157
+
158
+ it "sets request parameters" do
159
+ make_request(env)
160
+
161
+ expect(last_transaction.to_h).to include(
162
+ "sample_data" => hash_including(
163
+ "params" => hash_including(
164
+ "page" => "2",
165
+ "query" => "lorem"
166
+ )
167
+ )
168
+ )
169
+ end
170
+ end
171
+
172
+ context "with queue start header" do
173
+ let(:queue_start_time) { fixed_time * 1_000 }
174
+ before do
175
+ env["HTTP_X_REQUEST_START"] = "t=#{queue_start_time.to_i}" # in milliseconds
176
+ end
177
+
178
+ it "sets the queue start" do
179
+ make_request(env)
180
+
181
+ expect(last_transaction.ext.queue_start).to eq(queue_start_time)
182
+ end
183
+ end
184
+
185
+ class FilteredRequest
186
+ attr_reader :env
187
+
188
+ def initialize(env)
189
+ @env = env
190
+ end
191
+
192
+ def path
193
+ "/static/path"
194
+ end
195
+
196
+ def request_method
197
+ "GET"
198
+ end
199
+
200
+ def filtered_params
201
+ { "abc" => "123" }
202
+ end
203
+ end
204
+
205
+ context "with overridden request class and params method" do
206
+ let(:options) do
207
+ { :request_class => FilteredRequest, :params_method => :filtered_params }
208
+ end
209
+
210
+ it "uses the overridden request class and params method to fetch params" do
211
+ make_request(env)
212
+
213
+ expect(last_transaction.to_h).to include(
214
+ "sample_data" => hash_including(
215
+ "params" => { "abc" => "123" }
216
+ )
217
+ )
218
+ end
219
+ end
220
+
221
+ context "with parent instrumentation" do
222
+ before do
223
+ env[Appsignal::Rack::APPSIGNAL_TRANSACTION] = http_request_transaction
224
+ end
225
+
226
+ it "uses the existing transaction" do
227
+ make_request(env)
228
+
229
+ expect { make_request(env) }.to_not(change { created_transactions.count })
230
+ end
231
+
232
+ it "doesn't complete the existing transaction" do
233
+ make_request(env)
234
+
235
+ expect(env[Appsignal::Rack::APPSIGNAL_TRANSACTION]).to_not be_completed
236
+ end
237
+
238
+ context "with custom set action name" do
239
+ it "does not overwrite the action name" do
240
+ env[Appsignal::Rack::APPSIGNAL_TRANSACTION].set_action("My custom action")
241
+ env["appsignal.action"] = "POST /my-action"
242
+ make_request(env)
243
+
244
+ expect(last_transaction.to_h).to include("action" => "My custom action")
245
+ end
246
+ end
247
+ end
248
+ end
249
+ end
250
+ end
@@ -11,6 +11,7 @@ describe Appsignal::Rack::EventHandler do
11
11
  let(:response) { nil }
12
12
  let(:log_stream) { StringIO.new }
13
13
  let(:log) { log_contents(log_stream) }
14
+ let(:event_handler_instance) { described_class.new }
14
15
  before do
15
16
  start_agent
16
17
  Appsignal.internal_logger = test_logger(log_stream)
@@ -18,7 +19,7 @@ describe Appsignal::Rack::EventHandler do
18
19
  around { |example| keep_transactions { example.run } }
19
20
 
20
21
  def on_start
21
- described_class.new.on_start(request, response)
22
+ event_handler_instance.on_start(request, response)
22
23
  end
23
24
 
24
25
  describe "#on_start" do
@@ -34,6 +35,14 @@ describe Appsignal::Rack::EventHandler do
34
35
  expect(Appsignal::Transaction.current).to eq(last_transaction)
35
36
  end
36
37
 
38
+ context "when the handler is nested in another EventHandler" do
39
+ it "does not create a new transaction in the nested EventHandler" do
40
+ on_start
41
+ expect { described_class.new.on_start(request, response) }
42
+ .to_not(change { created_transactions.length })
43
+ end
44
+ end
45
+
37
46
  it "registers transaction on the request environment" do
38
47
  on_start
39
48
 
@@ -87,7 +96,7 @@ describe Appsignal::Rack::EventHandler do
87
96
 
88
97
  describe "#on_error" do
89
98
  def on_error(error)
90
- described_class.new.on_error(request, response, error)
99
+ event_handler_instance.on_error(request, response, error)
91
100
  end
92
101
 
93
102
  it "reports the error" do
@@ -103,6 +112,15 @@ describe Appsignal::Rack::EventHandler do
103
112
  )
104
113
  end
105
114
 
115
+ context "when the handler is nested in another EventHandler" do
116
+ it "does not report the error on the transaction" do
117
+ on_start
118
+ described_class.new.on_error(request, response, ExampleStandardError.new("the error"))
119
+
120
+ expect(last_transaction.to_h).to include("error" => nil)
121
+ end
122
+ end
123
+
106
124
  it "logs an error in case of an internal error" do
107
125
  on_start
108
126
 
@@ -122,7 +140,7 @@ describe Appsignal::Rack::EventHandler do
122
140
  let(:response) { Rack::Events::BufferedResponse.new(200, {}, ["body"]) }
123
141
 
124
142
  def on_finish
125
- described_class.new.on_finish(request, response)
143
+ event_handler_instance.on_finish(request, response)
126
144
  end
127
145
 
128
146
  it "doesn't do anything without a transaction" do
@@ -155,6 +173,22 @@ describe Appsignal::Rack::EventHandler do
155
173
  )
156
174
  )
157
175
  expect(last_transaction.ext.queue_start).to eq(queue_start_time)
176
+ expect(last_transaction).to be_completed
177
+ end
178
+
179
+ context "when the handler is nested in another EventHandler" do
180
+ it "does not complete the transaction" do
181
+ on_start
182
+ described_class.new.on_finish(request, response)
183
+
184
+ expect(last_transaction.to_h).to include(
185
+ "action" => nil,
186
+ "metadata" => {},
187
+ "sample_data" => {},
188
+ "events" => []
189
+ )
190
+ expect(last_transaction).to_not be_completed
191
+ end
158
192
  end
159
193
 
160
194
  it "doesn't set the action name if already set" do