appsignal 4.0.2 → 4.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -1363,30 +1363,5 @@ describe Appsignal::Config do
1363
1363
 
1364
1364
  expect(dsl.cpu_count).to eq(1.0)
1365
1365
  end
1366
-
1367
- describe "#app_path=" do
1368
- it "prints a deprecation warning" do
1369
- err_stream = std_stream
1370
- capture_std_streams(std_stream, err_stream) do
1371
- dsl.app_path = "foo"
1372
- end
1373
-
1374
- expect(err_stream.read).to include(
1375
- "appsignal WARNING: The `Appsignal.configure`'s `app_path=` writer is deprecated"
1376
- )
1377
- end
1378
-
1379
- it "logs a deprecation warning" do
1380
- logs =
1381
- capture_logs do
1382
- silence { dsl.app_path = "foo" }
1383
- end
1384
-
1385
- expect(logs).to contains_log(
1386
- :warn,
1387
- "The `Appsignal.configure`'s `app_path=` writer is deprecated"
1388
- )
1389
- end
1390
- end
1391
1366
  end
1392
1367
  end
@@ -115,12 +115,33 @@ describe Appsignal::Environment do
115
115
  end
116
116
 
117
117
  describe ".report_supported_gems" do
118
- it "reports about all AppSignal supported gems in the bundle" do
118
+ it "reports about all AppSignal supported gems in the bundle using Bundler all_specs" do
119
119
  logs = capture_logs { described_class.report_supported_gems }
120
120
 
121
121
  expect(logs).to be_empty
122
122
 
123
- bundle_gem_specs = ::Bundler.rubygems.all_specs
123
+ unless Bundler.rubygems.respond_to?(:all_specs)
124
+ skip "Using new Bundler version without `all_specs` method"
125
+ end
126
+ bundle_gem_specs = silence { ::Bundler.rubygems.all_specs }
127
+ rack_spec = bundle_gem_specs.find { |s| s.name == "rack" }
128
+ rake_spec = bundle_gem_specs.find { |s| s.name == "rake" }
129
+ expect_environment_metadata("ruby_rack_version", rack_spec.version.to_s)
130
+ expect_environment_metadata("ruby_rake_version", rake_spec.version.to_s)
131
+ expect(rack_spec.version.to_s).to_not be_empty
132
+ expect(rake_spec.version.to_s).to_not be_empty
133
+ end
134
+
135
+ it "reports about all AppSignal supported gems in the bundle using bundler installed_specs" do
136
+ unless Bundler.rubygems.respond_to?(:installed_specs)
137
+ skip "Using old Bundler version without `installed_specs` method"
138
+ end
139
+
140
+ logs = capture_logs { described_class.report_supported_gems }
141
+
142
+ expect(logs).to be_empty
143
+
144
+ bundle_gem_specs = ::Bundler.rubygems.installed_specs
124
145
  rack_spec = bundle_gem_specs.find { |s| s.name == "rack" }
125
146
  rake_spec = bundle_gem_specs.find { |s| s.name == "rake" }
126
147
  expect_environment_metadata("ruby_rack_version", rack_spec.version.to_s)
@@ -80,4 +80,15 @@ describe Appsignal::Hooks::AtExit::AtExitCallback do
80
80
  end.to_not change { created_transactions.count }.from(1)
81
81
  end
82
82
  end
83
+
84
+ it "doesn't report the error if it is a SignalException exception" do
85
+ with_error(SignalException, "TERM") do |error|
86
+ Appsignal.report_error(error)
87
+ expect(created_transactions.count).to eq(1)
88
+
89
+ expect do
90
+ call_callback
91
+ end.to_not change { created_transactions.count }.from(1)
92
+ end
93
+ end
83
94
  end
@@ -229,15 +229,36 @@ if DependencyHelper.rails_present?
229
229
  expect(last_transaction).to have_error("ExampleStandardError", "error message")
230
230
  end
231
231
 
232
- it "ignores Sidekiq::JobRetry::Skip errors" do
233
- require "sidekiq"
234
- require "sidekiq/job_retry"
232
+ context "Sidekiq internal errors" do
233
+ before do
234
+ require "sidekiq"
235
+ require "sidekiq/job_retry"
236
+ end
235
237
 
236
- with_rails_error_reporter do
237
- Rails.error.handle { raise Sidekiq::JobRetry::Skip, "error message" }
238
+ it "ignores Sidekiq::JobRetry::Handled errors" do
239
+ with_rails_error_reporter do
240
+ Rails.error.handle { raise Sidekiq::JobRetry::Handled, "error message" }
241
+ end
242
+
243
+ expect(last_transaction).to_not have_error
238
244
  end
239
245
 
240
- expect(last_transaction).to_not have_error
246
+ it "ignores Sidekiq::JobRetry::Skip errors" do
247
+ with_rails_error_reporter do
248
+ Rails.error.handle { raise Sidekiq::JobRetry::Skip, "error message" }
249
+ end
250
+
251
+ expect(last_transaction).to_not have_error
252
+ end
253
+
254
+ it "doesn't crash when no Sidekiq error classes are found" do
255
+ hide_const("Sidekiq::JobRetry")
256
+ with_rails_error_reporter do
257
+ Rails.error.handle { raise ExampleStandardError, "error message" }
258
+ end
259
+
260
+ expect(last_transaction).to have_error("ExampleStandardError", "error message")
261
+ end
241
262
  end
242
263
 
243
264
  context "when no transaction is active" do
@@ -5,23 +5,29 @@ describe Appsignal::Rack::BodyWrapper do
5
5
  set_current_transaction(transaction)
6
6
  end
7
7
 
8
- describe "with a body that supports all possible features" do
9
- it "reduces the supported methods to just each()" do
10
- # which is the safest thing to do, since the body is likely broken
11
- fake_body = double(
12
- :each => nil,
13
- :call => nil,
14
- :to_ary => [],
15
- :to_path => "/tmp/foo.bin",
16
- :close => nil
17
- )
8
+ it "forwards method calls to the body if the method doesn't exist" do
9
+ fake_body = double(
10
+ :body => ["some body"],
11
+ :some_method => :some_value
12
+ )
13
+
14
+ wrapped = described_class.wrap(fake_body, transaction)
15
+ expect(wrapped).to respond_to(:body)
16
+ expect(wrapped.body).to eq(["some body"])
17
+
18
+ expect(wrapped).to respond_to(:some_method)
19
+ expect(wrapped.some_method).to eq(:some_value)
20
+ end
18
21
 
19
- wrapped = described_class.wrap(fake_body, transaction)
20
- expect(wrapped).to respond_to(:each)
21
- expect(wrapped).to_not respond_to(:to_ary)
22
- expect(wrapped).to_not respond_to(:call)
23
- expect(wrapped).to respond_to(:close)
24
- end
22
+ it "doesn't respond to methods the Rack::BodyProxy doesn't respond to" do
23
+ body = Rack::BodyProxy.new(["body"])
24
+ wrapped = described_class.wrap(body, transaction)
25
+
26
+ expect(wrapped).to_not respond_to(:to_str)
27
+ expect { wrapped.to_str }.to raise_error(NoMethodError)
28
+
29
+ expect(wrapped).to_not respond_to(:body)
30
+ expect { wrapped.body }.to raise_error(NoMethodError)
25
31
  end
26
32
 
27
33
  describe "with a body only supporting each()" do
@@ -97,15 +103,17 @@ describe Appsignal::Rack::BodyWrapper do
97
103
  end
98
104
 
99
105
  describe "with a body supporting both each() and call" do
100
- it "wraps with the wrapper that conceals call() and exposes each" do
101
- fake_body = double
102
- allow(fake_body).to receive(:each)
103
- allow(fake_body).to receive(:call)
106
+ it "wraps with the wrapper that exposes each" do
107
+ fake_body = double(
108
+ :each => true,
109
+ :call => "original call"
110
+ )
104
111
 
105
112
  wrapped = described_class.wrap(fake_body, transaction)
106
113
  expect(wrapped).to respond_to(:each)
107
114
  expect(wrapped).to_not respond_to(:to_ary)
108
- expect(wrapped).to_not respond_to(:call)
115
+ expect(wrapped).to respond_to(:call)
116
+ expect(wrapped.call).to eq("original call")
109
117
  expect(wrapped).to_not respond_to(:to_path)
110
118
  expect(wrapped).to respond_to(:close)
111
119
  end
@@ -52,13 +52,41 @@ describe Appsignal::Transmitter do
52
52
  }
53
53
  ).to_return(:status => 200)
54
54
  end
55
- let(:response) { instance.transmit(:the => :payload) }
55
+
56
+ let(:response) { instance.transmit({ :the => :payload }) }
56
57
 
57
58
  it "returns Net::HTTP response" do
58
59
  expect(response).to be_kind_of(Net::HTTPResponse)
59
60
  expect(response.code).to eq "200"
60
61
  end
61
62
 
63
+ describe "with :ndjson format" do
64
+ before do
65
+ stub_request(:post, "https://push.appsignal.com/1/action").with(
66
+ :query => {
67
+ :api_key => "abc",
68
+ :environment => "production",
69
+ :gem_version => Appsignal::VERSION,
70
+ :hostname => config[:hostname],
71
+ :name => "TestApp"
72
+ },
73
+ :body => "{\"the\":\"payload\"}\n{\"part\":\"two\"}",
74
+ :headers => {
75
+ "Content-Type" => "application/x-ndjson; charset=UTF-8"
76
+ }
77
+ ).to_return(:status => 200)
78
+ end
79
+
80
+ let(:response) do
81
+ instance.transmit([{ :the => :payload }, { :part => :two }], :format => :ndjson)
82
+ end
83
+
84
+ it "returns Net::HTTP response" do
85
+ expect(response).to be_kind_of(Net::HTTPResponse)
86
+ expect(response.code).to eq "200"
87
+ end
88
+ end
89
+
62
90
  context "with ca_file_path config option set" do
63
91
  context "when file does not exist" do
64
92
  before do
@@ -106,7 +134,7 @@ describe Appsignal::Transmitter do
106
134
  end
107
135
 
108
136
  describe "#http_post" do
109
- subject { instance.send(:http_post, "the" => "payload") }
137
+ subject { instance.send(:http_post, { "the" => "payload" }, :format => :json) }
110
138
 
111
139
  it "sets the path" do
112
140
  expect(subject.path).to eq instance.uri.request_uri
@@ -115,6 +143,24 @@ describe Appsignal::Transmitter do
115
143
  it "sets the correct headers" do
116
144
  expect(subject["Content-Type"]).to eq "application/json; charset=UTF-8"
117
145
  end
146
+
147
+ it "serialises the payload to JSON" do
148
+ expect(subject.body).to eq "{\"the\":\"payload\"}"
149
+ end
150
+
151
+ describe "with :ndjson format" do
152
+ subject do
153
+ instance.send(:http_post, [{ "the" => "payload" }, { "part" => "two" }], :format => :ndjson)
154
+ end
155
+
156
+ it "sets the correct headers" do
157
+ expect(subject["Content-Type"]).to eq "application/x-ndjson; charset=UTF-8"
158
+ end
159
+
160
+ it "serialises the payload to NDJSON" do
161
+ expect(subject.body).to eq "{\"the\":\"payload\"}\n{\"part\":\"two\"}"
162
+ end
163
+ end
118
164
  end
119
165
 
120
166
  describe "#http_client" do
@@ -581,6 +581,11 @@ describe Appsignal do
581
581
  expect(Appsignal.active?).to be_falsy
582
582
  end
583
583
  end
584
+
585
+ it "calls stop on the check-in scheduler" do
586
+ expect(Appsignal::CheckIn.scheduler).to receive(:stop)
587
+ Appsignal.stop
588
+ end
584
589
  end
585
590
 
586
591
  describe ".started?" do
@@ -0,0 +1,21 @@
1
+ module TakeAtMostHelper
2
+ # Assert that it takes at most a certain amount of time to run a block.
3
+ #
4
+ # @example
5
+ # # Assert that it takes at most 1 second to run the block
6
+ # take_at_most(1) { sleep 0.5 }
7
+ #
8
+ # @param time [Integer, Float] The maximum amount of time the block is allowed to
9
+ # run in seconds.
10
+ # @yield Block to run.
11
+ # @raise [StandardError] Raises error if the block takes longer than the
12
+ # specified time to run.
13
+ def take_at_most(time)
14
+ start = Time.now
15
+ yield
16
+ elapsed = Time.now - start
17
+ return if elapsed <= time
18
+
19
+ raise "Expected block to take at most #{time} seconds, but took #{elapsed}"
20
+ end
21
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: appsignal
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.2
4
+ version: 4.0.4
5
5
  platform: ruby
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-08-23 00:00:00.000000000 Z
13
+ date: 2024-08-29 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rack
@@ -188,6 +188,7 @@ files:
188
188
  - lib/appsignal/capistrano.rb
189
189
  - lib/appsignal/check_in.rb
190
190
  - lib/appsignal/check_in/cron.rb
191
+ - lib/appsignal/check_in/scheduler.rb
191
192
  - lib/appsignal/cli.rb
192
193
  - lib/appsignal/cli/demo.rb
193
194
  - lib/appsignal/cli/diagnose.rb
@@ -295,6 +296,7 @@ files:
295
296
  - lib/appsignal/utils/integration_logger.rb
296
297
  - lib/appsignal/utils/integration_memory_logger.rb
297
298
  - lib/appsignal/utils/json.rb
299
+ - lib/appsignal/utils/ndjson.rb
298
300
  - lib/appsignal/utils/query_params_sanitizer.rb
299
301
  - lib/appsignal/utils/rails_helper.rb
300
302
  - lib/appsignal/utils/stdout_and_logger_message.rb
@@ -308,7 +310,8 @@ files:
308
310
  - spec/lib/appsignal/auth_check_spec.rb
309
311
  - spec/lib/appsignal/capistrano2_spec.rb
310
312
  - spec/lib/appsignal/capistrano3_spec.rb
311
- - spec/lib/appsignal/check_in_spec.rb
313
+ - spec/lib/appsignal/check_in/cron_spec.rb
314
+ - spec/lib/appsignal/check_in/scheduler_spec.rb
312
315
  - spec/lib/appsignal/cli/demo_spec.rb
313
316
  - spec/lib/appsignal/cli/diagnose/paths_spec.rb
314
317
  - spec/lib/appsignal/cli/diagnose/utils_spec.rb
@@ -436,6 +439,7 @@ files:
436
439
  - spec/support/helpers/rails_helper.rb
437
440
  - spec/support/helpers/std_streams_helper.rb
438
441
  - spec/support/helpers/system_helpers.rb
442
+ - spec/support/helpers/take_at_most_helper.rb
439
443
  - spec/support/helpers/time_helpers.rb
440
444
  - spec/support/helpers/transaction_helpers.rb
441
445
  - spec/support/helpers/wait_for_helper.rb
@@ -1,136 +0,0 @@
1
- describe Appsignal::CheckIn::Cron do
2
- let(:config) { project_fixture_config }
3
- let(:cron_checkin) { described_class.new(:identifier => "cron-checkin-name") }
4
- let(:transmitter) { Appsignal::Transmitter.new("http://cron_checkins/", config) }
5
-
6
- before(:each) do
7
- allow(Appsignal).to receive(:active?).and_return(true)
8
- config.logger = Logger.new(StringIO.new)
9
- allow(Appsignal::CheckIn::Cron).to receive(:transmitter).and_return(transmitter)
10
- end
11
-
12
- describe "when Appsignal is not active" do
13
- it "should not transmit any events" do
14
- allow(Appsignal).to receive(:active?).and_return(false)
15
- expect(transmitter).not_to receive(:transmit)
16
-
17
- cron_checkin.start
18
- cron_checkin.finish
19
- end
20
- end
21
-
22
- describe "#start" do
23
- it "should send a cron check-in start" do
24
- expect(transmitter).to receive(:transmit).with(hash_including(
25
- :identifier => "cron-checkin-name",
26
- :kind => "start",
27
- :check_in_type => "cron"
28
- )).and_return(Net::HTTPResponse.new(nil, "200", nil))
29
-
30
- expect(Appsignal.internal_logger).to receive(:debug).with(
31
- "Transmitted cron check-in `cron-checkin-name` (#{cron_checkin.digest}) start event"
32
- )
33
- expect(Appsignal.internal_logger).not_to receive(:error)
34
-
35
- cron_checkin.start
36
- end
37
-
38
- it "should log an error if it fails" do
39
- expect(transmitter).to receive(:transmit).with(hash_including(
40
- :identifier => "cron-checkin-name",
41
- :kind => "start",
42
- :check_in_type => "cron"
43
- )).and_return(Net::HTTPResponse.new(nil, "499", nil))
44
-
45
- expect(Appsignal.internal_logger).not_to receive(:debug)
46
- expect(Appsignal.internal_logger).to receive(:error).with(
47
- "Failed to transmit cron check-in start event: status code was 499"
48
- )
49
-
50
- cron_checkin.start
51
- end
52
- end
53
-
54
- describe "#finish" do
55
- it "should send a cron check-in finish" do
56
- expect(transmitter).to receive(:transmit).with(hash_including(
57
- :identifier => "cron-checkin-name",
58
- :kind => "finish",
59
- :check_in_type => "cron"
60
- )).and_return(Net::HTTPResponse.new(nil, "200", nil))
61
-
62
- expect(Appsignal.internal_logger).to receive(:debug).with(
63
- "Transmitted cron check-in `cron-checkin-name` (#{cron_checkin.digest}) finish event"
64
- )
65
- expect(Appsignal.internal_logger).not_to receive(:error)
66
-
67
- cron_checkin.finish
68
- end
69
-
70
- it "should log an error if it fails" do
71
- expect(transmitter).to receive(:transmit).with(hash_including(
72
- :identifier => "cron-checkin-name",
73
- :kind => "finish",
74
- :check_in_type => "cron"
75
- )).and_return(Net::HTTPResponse.new(nil, "499", nil))
76
-
77
- expect(Appsignal.internal_logger).not_to receive(:debug)
78
- expect(Appsignal.internal_logger).to receive(:error).with(
79
- "Failed to transmit cron check-in finish event: status code was 499"
80
- )
81
-
82
- cron_checkin.finish
83
- end
84
- end
85
-
86
- describe ".cron" do
87
- describe "when a block is given" do
88
- it "should send a cron check-in start and finish and return the block output" do
89
- expect(transmitter).to receive(:transmit).with(hash_including(
90
- :kind => "start",
91
- :identifier => "cron-checkin-with-block",
92
- :check_in_type => "cron"
93
- )).and_return(nil)
94
-
95
- expect(transmitter).to receive(:transmit).with(hash_including(
96
- :kind => "finish",
97
- :identifier => "cron-checkin-with-block",
98
- :check_in_type => "cron"
99
- )).and_return(nil)
100
-
101
- output = Appsignal::CheckIn.cron("cron-checkin-with-block") { "output" }
102
- expect(output).to eq("output")
103
- end
104
-
105
- it "should not send a cron check-in finish event when an error is raised" do
106
- expect(transmitter).to receive(:transmit).with(hash_including(
107
- :kind => "start",
108
- :identifier => "cron-checkin-with-block",
109
- :check_in_type => "cron"
110
- )).and_return(nil)
111
-
112
- expect(transmitter).not_to receive(:transmit).with(hash_including(
113
- :kind => "finish",
114
- :identifier => "cron-checkin-with-block",
115
- :check_in_type => "cron"
116
- ))
117
-
118
- expect do
119
- Appsignal::CheckIn.cron("cron-checkin-with-block") { raise "error" }
120
- end.to raise_error(RuntimeError, "error")
121
- end
122
- end
123
-
124
- describe "when no block is given" do
125
- it "should only send a cron check-in finish event" do
126
- expect(transmitter).to receive(:transmit).with(hash_including(
127
- :kind => "finish",
128
- :identifier => "cron-checkin-without-block",
129
- :check_in_type => "cron"
130
- )).and_return(nil)
131
-
132
- Appsignal::CheckIn.cron("cron-checkin-without-block")
133
- end
134
- end
135
- end
136
- end