appsignal 2.9.18 → 2.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +0 -6
  3. data/CHANGELOG.md +17 -1
  4. data/Rakefile +16 -2
  5. data/lib/appsignal/cli.rb +9 -2
  6. data/lib/appsignal/cli/diagnose.rb +20 -19
  7. data/lib/appsignal/cli/helpers.rb +22 -10
  8. data/lib/appsignal/cli/install.rb +2 -1
  9. data/lib/appsignal/config.rb +18 -7
  10. data/lib/appsignal/event_formatter.rb +4 -4
  11. data/lib/appsignal/minutely.rb +4 -4
  12. data/lib/appsignal/rack/js_exception_catcher.rb +6 -0
  13. data/lib/appsignal/transaction.rb +1 -1
  14. data/lib/appsignal/version.rb +1 -1
  15. data/spec/lib/appsignal/cli/diagnose_spec.rb +54 -11
  16. data/spec/lib/appsignal/cli/helpers_spec.rb +11 -3
  17. data/spec/lib/appsignal/cli/install_spec.rb +30 -1
  18. data/spec/lib/appsignal/config_spec.rb +75 -7
  19. data/spec/lib/appsignal/hooks/action_cable_spec.rb +1 -5
  20. data/spec/lib/appsignal/hooks/rake_spec.rb +41 -39
  21. data/spec/lib/appsignal/hooks/sidekiq_spec.rb +2 -15
  22. data/spec/lib/appsignal/integrations/object_spec.rb +2 -2
  23. data/spec/lib/appsignal/integrations/que_spec.rb +26 -39
  24. data/spec/lib/appsignal/integrations/resque_active_job_spec.rb +108 -46
  25. data/spec/lib/appsignal/integrations/resque_spec.rb +40 -39
  26. data/spec/lib/appsignal/minutely_spec.rb +3 -3
  27. data/spec/lib/appsignal/rack/js_exception_catcher_spec.rb +19 -5
  28. data/spec/lib/appsignal/transaction_spec.rb +4 -12
  29. data/spec/lib/appsignal_spec.rb +7 -8
  30. data/spec/spec_helper.rb +11 -11
  31. data/spec/support/fixtures/projects/broken/config/appsignal.yml +1 -0
  32. data/spec/support/helpers/cli_helpers.rb +15 -1
  33. data/spec/support/helpers/transaction_helpers.rb +53 -0
  34. data/spec/support/matchers/be_completed.rb +5 -0
  35. data/spec/support/matchers/have_colorized_text.rb +28 -0
  36. data/spec/support/testing.rb +113 -0
  37. metadata +10 -2
@@ -17,67 +17,129 @@ if DependencyHelper.resque_present? && DependencyHelper.active_job_present?
17
17
  end
18
18
  end
19
19
 
20
- it "wraps it in a transaction with the correct params" do
21
- expect(Appsignal).to receive(:monitor_single_transaction).with(
22
- "perform_job.resque",
23
- :class => "TestActiveJob",
24
- :method => "perform",
25
- :params => ["argument"],
26
- :metadata => {
27
- :id => kind_of(String),
28
- :queue => "default"
29
- }
30
- )
20
+ def perform
21
+ keep_transactions do
22
+ job.perform_now
23
+ end
31
24
  end
32
25
 
33
- context "with complex arguments" do
34
- let(:args) do
35
- {
36
- :foo => "Foo",
37
- :bar => "Bar"
38
- }
26
+ context "without error" do
27
+ it "creates a new transaction" do
28
+ expect { perform }.to change { created_transactions.length }.by(1)
29
+
30
+ expect(last_transaction.to_h).to include(
31
+ "namespace" => Appsignal::Transaction::BACKGROUND_JOB,
32
+ "action" => "TestActiveJob#perform",
33
+ "error" => nil,
34
+ "events" => [
35
+ hash_including(
36
+ "name" => "perform_job.resque",
37
+ "title" => "",
38
+ "body" => "",
39
+ "body_format" => Appsignal::EventFormatter::DEFAULT,
40
+ "count" => 1,
41
+ "duration" => kind_of(Float)
42
+ )
43
+ ],
44
+ "sample_data" => hash_including(
45
+ "params" => ["argument"],
46
+ "metadata" => {
47
+ "id" => kind_of(String),
48
+ "queue" => "default"
49
+ }
50
+ )
51
+ )
39
52
  end
53
+ end
54
+
55
+ context "with error" do
56
+ let(:job) do
57
+ class BrokenTestActiveJob < ActiveJob::Base
58
+ include Appsignal::Integrations::ResqueActiveJobPlugin
40
59
 
41
- it "truncates large argument values" do
42
- expect(Appsignal).to receive(:monitor_single_transaction).with(
43
- "perform_job.resque",
44
- :class => "TestActiveJob",
45
- :method => "perform",
46
- :params => [
60
+ def perform(_)
61
+ raise ExampleException, "my error message"
62
+ end
63
+ end
64
+
65
+ BrokenTestActiveJob.new(args)
66
+ end
67
+
68
+ it "creates a new transaction with an error" do
69
+ expect do
70
+ expect { perform }.to raise_error(ExampleException, "my error message")
71
+ end.to change { created_transactions.length }.by(1)
72
+
73
+ expect(last_transaction.to_h).to include(
74
+ "namespace" => Appsignal::Transaction::BACKGROUND_JOB,
75
+ "action" => "BrokenTestActiveJob#perform",
76
+ "error" => {
77
+ "name" => "ExampleException",
78
+ "message" => "my error message",
79
+ "backtrace" => kind_of(String)
80
+ },
81
+ "sample_data" => hash_including(
82
+ "params" => ["argument"],
83
+ "metadata" => {
84
+ "id" => kind_of(String),
85
+ "queue" => "default"
86
+ }
87
+ )
88
+ )
89
+ end
90
+ end
91
+
92
+ context "with complex arguments" do
93
+ context "with too long values" do
94
+ let(:args) do
95
+ {
47
96
  :foo => "Foo",
48
- :bar => "Bar"
49
- ],
50
- :metadata => {
51
- :id => kind_of(String),
52
- :queue => "default"
97
+ :bar => "a" * 2001
53
98
  }
54
- )
99
+ end
100
+
101
+ it "truncates large argument values" do
102
+ perform
103
+ expect(last_transaction.to_h).to include(
104
+ "namespace" => Appsignal::Transaction::BACKGROUND_JOB,
105
+ "action" => "TestActiveJob#perform",
106
+ "error" => nil,
107
+ "sample_data" => hash_including(
108
+ "params" => ["foo" => "Foo", "bar" => "#{"a" * 2000}..."],
109
+ "metadata" => {
110
+ "id" => kind_of(String),
111
+ "queue" => "default"
112
+ }
113
+ )
114
+ )
115
+ end
55
116
  end
56
117
 
57
118
  context "with parameter filtering" do
58
- before do
59
- Appsignal.config = project_fixture_config("production")
60
- Appsignal.config[:filter_parameters] = ["foo"]
119
+ let(:args) do
120
+ {
121
+ :foo => "Foo",
122
+ :bar => "Bar"
123
+ }
61
124
  end
125
+ before { Appsignal.config[:filter_parameters] = ["foo"] }
62
126
 
63
127
  it "filters selected arguments" do
64
- expect(Appsignal).to receive(:monitor_single_transaction).with(
65
- "perform_job.resque",
66
- :class => "TestActiveJob",
67
- :method => "perform",
68
- :params => [
69
- :foo => "[FILTERED]",
70
- :bar => "Bar"
71
- ],
72
- :metadata => {
73
- :id => kind_of(String),
74
- :queue => "default"
75
- }
128
+ perform
129
+ expect(last_transaction.to_h).to include(
130
+ "namespace" => Appsignal::Transaction::BACKGROUND_JOB,
131
+ "action" => "TestActiveJob#perform",
132
+ "error" => nil,
133
+ "sample_data" => hash_including(
134
+ "params" => ["foo" => "[FILTERED]", "bar" => "Bar"],
135
+ "metadata" => {
136
+ "id" => kind_of(String),
137
+ "queue" => "default"
138
+ }
139
+ )
76
140
  )
77
141
  end
78
142
  end
79
143
  end
80
-
81
- after { job.perform_now }
82
144
  end
83
145
  end
@@ -18,65 +18,66 @@ if DependencyHelper.resque_present?
18
18
  extend Appsignal::Integrations::ResquePlugin
19
19
 
20
20
  def self.perform
21
- raise ExampleException
21
+ raise ExampleException, "my error message"
22
22
  end
23
23
  end
24
24
  end
25
25
 
26
26
  describe :around_perform_resque_plugin do
27
- let(:transaction) { Appsignal::Transaction.new("1", "background", {}, {}) }
28
27
  let(:job) { ::Resque::Job.new("default", "class" => "TestJob") }
29
- before do
30
- allow(transaction).to receive(:complete).and_return(true)
31
- allow(Appsignal::Transaction).to receive(:current).and_return(transaction)
32
- expect(Appsignal).to receive(:stop)
33
- end
28
+ before { expect(Appsignal).to receive(:stop) }
34
29
 
35
30
  context "without exception" do
36
31
  it "creates a new transaction" do
37
- expect(Appsignal::Transaction).to receive(:create).and_return(transaction)
38
- end
32
+ expect do
33
+ keep_transactions { job.perform }
34
+ end.to change { created_transactions.length }.by(1)
39
35
 
40
- it "wraps it in a transaction with the correct params" do
41
- expect(Appsignal).to receive(:monitor_transaction).with(
42
- "perform_job.resque",
43
- :class => "TestJob",
44
- :method => "perform"
36
+ expect(last_transaction).to be_completed
37
+ expect(last_transaction.to_h).to include(
38
+ "namespace" => Appsignal::Transaction::BACKGROUND_JOB,
39
+ "action" => "TestJob#perform",
40
+ "error" => nil,
41
+ "events" => [
42
+ hash_including(
43
+ "name" => "perform_job.resque",
44
+ "title" => "",
45
+ "body" => "",
46
+ "body_format" => Appsignal::EventFormatter::DEFAULT,
47
+ "count" => 1,
48
+ "duration" => kind_of(Float)
49
+ )
50
+ ]
45
51
  )
46
52
  end
47
-
48
- it "closes the transaction" do
49
- expect(transaction).to receive(:complete)
50
- end
51
-
52
- after { job.perform }
53
53
  end
54
54
 
55
55
  context "with exception" do
56
56
  let(:job) { ::Resque::Job.new("default", "class" => "BrokenTestJob") }
57
- let(:transaction) do
58
- Appsignal::Transaction.new(
59
- SecureRandom.uuid,
60
- Appsignal::Transaction::BACKGROUND_JOB,
61
- Appsignal::Transaction::GenericRequest.new({})
62
- )
63
- end
64
- before do
65
- allow(Appsignal::Transaction).to receive(:current).and_return(transaction)
66
- expect(Appsignal::Transaction).to receive(:create)
67
- .with(
68
- kind_of(String),
69
- Appsignal::Transaction::BACKGROUND_JOB,
70
- kind_of(Appsignal::Transaction::GenericRequest)
71
- ).and_return(transaction)
57
+
58
+ def perform
59
+ keep_transactions do
60
+ expect do
61
+ job.perform
62
+ end.to raise_error(ExampleException, "my error message")
63
+ end
72
64
  end
73
65
 
74
66
  it "sets the exception on the transaction" do
75
- expect(transaction).to receive(:set_error).with(ExampleException)
76
- end
67
+ expect do
68
+ perform
69
+ end.to change { created_transactions.length }.by(1)
77
70
 
78
- after do
79
- expect { job.perform }.to raise_error(ExampleException)
71
+ expect(last_transaction).to be_completed
72
+ expect(last_transaction.to_h).to include(
73
+ "namespace" => Appsignal::Transaction::BACKGROUND_JOB,
74
+ "action" => "BrokenTestJob#perform",
75
+ "error" => {
76
+ "name" => "ExampleException",
77
+ "message" => "my error message",
78
+ "backtrace" => kind_of(String)
79
+ }
80
+ )
80
81
  end
81
82
  end
82
83
  end
@@ -189,7 +189,7 @@ describe Appsignal::Minutely do
189
189
  expect(Appsignal::Minutely).to have_received(:initial_wait_time).once
190
190
  expect do
191
191
  # Fetch old thread
192
- thread = Appsignal::Minutely.class_variable_get(:@@thread)
192
+ thread = Appsignal::Minutely.instance_variable_get(:@thread)
193
193
  Appsignal::Minutely.start
194
194
  thread && thread.join # Wait for old thread to exit
195
195
  end.to_not(change { alive_thread_counter.call })
@@ -203,7 +203,7 @@ describe Appsignal::Minutely do
203
203
 
204
204
  it "stops the minutely thread" do
205
205
  Appsignal::Minutely.start
206
- thread = Appsignal::Minutely.class_variable_get(:@@thread)
206
+ thread = Appsignal::Minutely.instance_variable_get(:@thread)
207
207
  expect(%w[sleep run]).to include(thread.status)
208
208
  Appsignal::Minutely.stop
209
209
  thread.join
@@ -213,7 +213,7 @@ describe Appsignal::Minutely do
213
213
  it "clears the probe instances array" do
214
214
  Appsignal::Minutely.probes.register :my_probe, lambda {}
215
215
  Appsignal::Minutely.start
216
- thread = Appsignal::Minutely.class_variable_get(:@@thread)
216
+ thread = Appsignal::Minutely.instance_variable_get(:@thread)
217
217
  wait_for("probes initialized") do
218
218
  !Appsignal::Minutely.send(:probe_instances).empty?
219
219
  end
@@ -3,19 +3,33 @@ describe Appsignal::Rack::JSExceptionCatcher do
3
3
  let(:options) { nil }
4
4
  let(:config_options) { { :enable_frontend_error_catching => true } }
5
5
  let(:config) { project_fixture_config("production", config_options) }
6
+ let(:deprecation_message) do
7
+ "The Appsignal::Rack::JSExceptionCatcher is deprecated. " \
8
+ "Please use the official AppSignal JavaScript integration instead. " \
9
+ "https://docs.appsignal.com/front-end/"
10
+ end
6
11
  before { Appsignal.config = config }
7
12
 
8
13
  describe "#initialize" do
9
14
  it "logs to the logger" do
10
- expect(Appsignal.logger).to receive(:debug)
11
- .with("Initializing Appsignal::Rack::JSExceptionCatcher")
12
-
13
- Appsignal::Rack::JSExceptionCatcher.new(app, options)
15
+ stdout = std_stream
16
+ stderr = std_stream
17
+ log = capture_logs do
18
+ capture_std_streams(stdout, stderr) do
19
+ Appsignal::Rack::JSExceptionCatcher.new(app, options)
20
+ end
21
+ end
22
+ expect(log).to contains_log(:warn, deprecation_message)
23
+ expect(log).to contains_log(:debug, "Initializing Appsignal::Rack::JSExceptionCatcher")
24
+ expect(stdout.read).to include "appsignal WARNING: #{deprecation_message}"
25
+ expect(stderr.read).to_not include("appsignal:")
14
26
  end
15
27
  end
16
28
 
17
29
  describe "#call" do
18
- let(:catcher) { Appsignal::Rack::JSExceptionCatcher.new(app, options) }
30
+ let(:catcher) do
31
+ silence { Appsignal::Rack::JSExceptionCatcher.new(app, options) }
32
+ end
19
33
  after { catcher.call(env) }
20
34
 
21
35
  context "when path is not frontend_error_catching_path" do
@@ -64,7 +64,7 @@ describe Appsignal::Transaction do
64
64
 
65
65
  it "logs a debug message" do
66
66
  create_transaction("2")
67
- expect(log_contents(log)).to contains_log :debug,
67
+ expect(log_contents(log)).to contains_log :warn,
68
68
  "Trying to start new transaction with id '2', but a " \
69
69
  "transaction with id '#{transaction_id}' is already " \
70
70
  "running. Using transaction '#{transaction_id}'."
@@ -154,13 +154,8 @@ describe Appsignal::Transaction do
154
154
  describe "#complete" do
155
155
  context "when transaction is being sampled" do
156
156
  it "samples data" do
157
- expect(transaction.ext).to receive(:finish).and_return(true)
158
- # Stub call to extension, because that would remove the transaction
159
- # from the extension.
160
- expect(transaction.ext).to receive(:complete)
161
-
162
157
  transaction.set_tags(:foo => "bar")
163
- transaction.complete
158
+ keep_transactions { transaction.complete }
164
159
  expect(transaction.to_h["sample_data"]).to include(
165
160
  "tags" => { "foo" => "bar" }
166
161
  )
@@ -169,11 +164,8 @@ describe Appsignal::Transaction do
169
164
 
170
165
  context "when transaction is not being sampled" do
171
166
  it "does not sample data" do
172
- expect(transaction).to_not receive(:sample_data)
173
- expect(transaction.ext).to receive(:finish).and_return(false)
174
- expect(transaction.ext).to receive(:complete).and_call_original
175
-
176
- transaction.complete
167
+ keep_transactions(:sample => false) { transaction.complete }
168
+ expect(transaction.to_h["sample_data"]).to be_empty
177
169
  end
178
170
  end
179
171
 
@@ -30,7 +30,7 @@ describe Appsignal do
30
30
  context "with no config set beforehand" do
31
31
  it "should do nothing when config is not set and there is no valid config in the env" do
32
32
  expect(Appsignal.logger).to receive(:error).with(
33
- "Push api key not set after loading config"
33
+ "Push API key not set after loading config"
34
34
  ).once
35
35
  expect(Appsignal.logger).to receive(:error).with(
36
36
  "Not starting, no valid config for this environment"
@@ -701,13 +701,12 @@ describe Appsignal do
701
701
  context "when given a block" do
702
702
  it "yields the transaction and allows additional metadata to be set" do
703
703
  captured_transaction = nil
704
- Appsignal.send_error(StandardError.new("my_error")) do |transaction|
705
- captured_transaction = transaction
706
- transaction.set_action("my_action")
707
- transaction.set_namespace("my_namespace")
708
-
709
- # Don't flush the transaction, so we can inspect it
710
- expect(transaction).to receive(:complete)
704
+ keep_transactions do
705
+ Appsignal.send_error(StandardError.new("my_error")) do |transaction|
706
+ captured_transaction = transaction
707
+ transaction.set_action("my_action")
708
+ transaction.set_namespace("my_namespace")
709
+ end
711
710
  end
712
711
  expect(captured_transaction.to_h).to include(
713
712
  "namespace" => "my_namespace",
@@ -31,16 +31,9 @@ if DependencyHelper.rails_present?
31
31
  end
32
32
  end
33
33
  require "appsignal"
34
-
35
- module Appsignal
36
- class << self
37
- remove_method :testing?
38
-
39
- def testing?
40
- true
41
- end
42
- end
43
- end
34
+ # Include patches of AppSignal modules and classes to make test helpers
35
+ # available.
36
+ require File.join(DirectoryHelper.support_dir, "testing.rb")
44
37
 
45
38
  puts "Running specs in #{RUBY_VERSION} on #{RUBY_PLATFORM}\n\n"
46
39
 
@@ -72,6 +65,12 @@ RSpec.configure do |config|
72
65
 
73
66
  config.example_status_persistence_file_path = "spec/examples.txt"
74
67
  config.fail_if_no_examples = true
68
+ config.mock_with :rspec do |mocks|
69
+ mocks.syntax = :expect
70
+ end
71
+ config.expect_with :rspec do |expectations|
72
+ expectations.syntax = :expect
73
+ end
75
74
 
76
75
  def spec_system_tmp_dir
77
76
  File.join(tmp_dir, "system-tmp")
@@ -114,7 +113,8 @@ RSpec.configure do |config|
114
113
  end
115
114
 
116
115
  config.after do
117
- Thread.current[:appsignal_transaction] = nil
116
+ Appsignal::Testing.clear!
117
+ clear_current_transaction!
118
118
  stop_minutely_probes
119
119
  end
120
120