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
@@ -24,7 +24,7 @@ describe Appsignal::CLI::Helpers do
24
24
  describe ".colorize" do
25
25
  subject { cli.send(:colorize, "text", :green) }
26
26
 
27
- context "on windows" do
27
+ context "when on windows" do
28
28
  before { allow(Gem).to receive(:win_platform?).and_return(true) }
29
29
 
30
30
  it "outputs plain string" do
@@ -32,11 +32,19 @@ describe Appsignal::CLI::Helpers do
32
32
  end
33
33
  end
34
34
 
35
- context "not on windows" do
35
+ context "when coloring is set to false" do
36
+ before { cli.send(:coloring=, false) }
37
+
38
+ it "outputs plain string" do
39
+ expect(subject).to eq "text"
40
+ end
41
+ end
42
+
43
+ context "when not on windows" do
36
44
  before { allow(Gem).to receive(:win_platform?).and_return(false) }
37
45
 
38
46
  it "wraps text in color tags" do
39
- expect(subject).to eq "\e[32mtext\e[0m"
47
+ expect(subject).to have_colorized_text(:green, "text")
40
48
  end
41
49
  end
42
50
  end
@@ -9,6 +9,7 @@ describe Appsignal::CLI::Install do
9
9
  let(:config) { Appsignal::Config.new(tmp_dir, "") }
10
10
  let(:config_file_path) { File.join(tmp_dir, "config", "appsignal.yml") }
11
11
  let(:config_file) { File.read(config_file_path) }
12
+ let(:options) { {} }
12
13
  before do
13
14
  stub_api_validation_request
14
15
  # Stub calls to speed up tests
@@ -91,7 +92,7 @@ describe Appsignal::CLI::Install do
91
92
  Dir.chdir tmp_dir do
92
93
  prepare_cli_input
93
94
  capture_stdout(out_stream) do
94
- run_cli(["install", push_api_key])
95
+ run_cli(["install", push_api_key], options)
95
96
  end
96
97
  end
97
98
  end
@@ -667,6 +668,34 @@ describe Appsignal::CLI::Install do
667
668
  it_behaves_like "push_api_key validation"
668
669
  it_behaves_like "demo data"
669
670
 
671
+ context "without color options" do
672
+ let(:options) { {} }
673
+
674
+ it "prints the instructions in color" do
675
+ run
676
+ expect(output).to have_colorized_text(:green, "## Starting AppSignal Installer ##")
677
+ end
678
+ end
679
+
680
+ context "with --color option" do
681
+ let(:options) { { "color" => nil } }
682
+
683
+ it "prints the instructions in color" do
684
+ run
685
+ expect(output).to have_colorized_text(:green, "## Starting AppSignal Installer ##")
686
+ end
687
+ end
688
+
689
+ context "with --no-color option" do
690
+ let(:options) { { "no-color" => nil } }
691
+
692
+ it "prints the instructions without special colors" do
693
+ run
694
+ expect(output).to include("Starting AppSignal Installer")
695
+ expect(output).to_not have_color_markers
696
+ end
697
+ end
698
+
670
699
  it "prints a message about unknown framework" do
671
700
  run
672
701
 
@@ -47,15 +47,43 @@ describe Appsignal::Config do
47
47
  subject { config[:active] }
48
48
 
49
49
  context "with APPSIGNAL_PUSH_API_KEY env variable" do
50
- before { ENV["APPSIGNAL_PUSH_API_KEY"] = "abc" }
50
+ context "when not empty" do
51
+ before { ENV["APPSIGNAL_PUSH_API_KEY"] = "abc" }
51
52
 
52
- it "becomes active" do
53
- expect(subject).to be_truthy
53
+ it "becomes active" do
54
+ expect(subject).to be_truthy
55
+ end
56
+
57
+ it "sets the push_api_key as loaded through the env_config" do
58
+ expect(config.env_config).to eq(:push_api_key => "abc")
59
+ expect(config.system_config).to eq(:active => true)
60
+ end
61
+ end
62
+
63
+ context "when empty string" do
64
+ before { ENV["APPSIGNAL_PUSH_API_KEY"] = "" }
65
+
66
+ it "does not becomes active" do
67
+ expect(subject).to be_falsy
68
+ end
69
+
70
+ it "sets the push_api_key as loaded through the env_config" do
71
+ expect(config.env_config).to eq(:push_api_key => "")
72
+ expect(config.system_config).to be_empty
73
+ end
54
74
  end
55
75
 
56
- it "sets the push_api_key as loaded through the env_config" do
57
- expect(config.env_config).to eq(:push_api_key => "abc")
58
- expect(config.system_config).to eq(:active => true)
76
+ context "when blank string" do
77
+ before { ENV["APPSIGNAL_PUSH_API_KEY"] = " " }
78
+
79
+ it "does not becomes active" do
80
+ expect(subject).to be_falsy
81
+ end
82
+
83
+ it "sets the push_api_key as loaded through the env_config" do
84
+ expect(config.env_config).to eq(:push_api_key => " ")
85
+ expect(config.system_config).to be_empty
86
+ end
59
87
  end
60
88
  end
61
89
 
@@ -215,6 +243,30 @@ describe Appsignal::Config do
215
243
  end
216
244
  end
217
245
 
246
+ context "with the config file causing an error" do
247
+ let(:config_path) do
248
+ File.expand_path(
249
+ File.join(File.dirname(__FILE__), "../../support/fixtures/projects/broken")
250
+ )
251
+ end
252
+ let(:config) { Appsignal::Config.new(config_path, "foo") }
253
+
254
+ it "logs & prints an error, skipping the file source" do
255
+ stdout = std_stream
256
+ stderr = std_stream
257
+ log = capture_logs { capture_std_streams(stdout, stderr) { config } }
258
+ message = "An error occured while loading the AppSignal config file. " \
259
+ "Skipping file config.\n" \
260
+ "File: #{File.join(config_path, "config", "appsignal.yml").inspect}\n" \
261
+ "NotExistingConstant: uninitialized constant NotExistingConstant\n"
262
+ expect(log).to contains_log :error, message
263
+ expect(log).to include("/appsignal/config.rb:") # Backtrace
264
+ expect(stdout.read).to_not include("appsignal:")
265
+ expect(stderr.read).to include "appsignal: #{message}"
266
+ expect(config.file_config).to eql({})
267
+ end
268
+ end
269
+
218
270
  it "sets the file_config" do
219
271
  # config found in spec/support/project_fixture/config/appsignal.yml
220
272
  expect(config.file_config).to match(
@@ -265,7 +317,7 @@ describe Appsignal::Config do
265
317
  expect_any_instance_of(Logger).to receive(:error).once
266
318
  .with("Not loading from config file: config for 'nonsense' not found")
267
319
  expect_any_instance_of(Logger).to receive(:error).once
268
- .with("Push api key not set after loading config")
320
+ .with("Push API key not set after loading config")
269
321
  config
270
322
  end
271
323
  end
@@ -724,6 +776,22 @@ describe Appsignal::Config do
724
776
  end
725
777
  end
726
778
 
779
+ context "with empty push_api_key" do
780
+ let(:push_api_key) { "" }
781
+
782
+ it "sets valid to false" do
783
+ is_expected.to eq(false)
784
+ end
785
+ end
786
+
787
+ context "with blank push_api_key" do
788
+ let(:push_api_key) { " " }
789
+
790
+ it "sets valid to false" do
791
+ is_expected.to eq(false)
792
+ end
793
+ end
794
+
727
795
  context "with push_api_key present" do
728
796
  let(:push_api_key) { "abc" }
729
797
 
@@ -54,16 +54,12 @@ describe Appsignal::Hooks::ActionCableHook do
54
54
  .with(transaction_id, Appsignal::Transaction::ACTION_CABLE, kind_of(ActionDispatch::Request))
55
55
  .and_return(transaction)
56
56
  allow(Appsignal::Transaction).to receive(:current).and_return(transaction)
57
- # Make sure sample data is added
58
- expect(transaction.ext).to receive(:finish).and_return(true)
59
- # Stub complete call, stops it from being cleared in the extension
60
- # And allows us to call `#to_h` on it after it's been completed.
61
- expect(transaction.ext).to receive(:complete)
62
57
 
63
58
  # Stub transmit call for subscribe/unsubscribe tests
64
59
  allow(connection).to receive(:websocket)
65
60
  .and_return(instance_double("ActionCable::Connection::WebSocket", :transmit => nil))
66
61
  end
62
+ around { |example| keep_transactions { example.run } }
67
63
 
68
64
  describe "#perform_action" do
69
65
  it "creates a transaction for an action" do
@@ -4,69 +4,71 @@ describe Appsignal::Hooks::RakeHook do
4
4
  let(:task) { Rake::Task.new("task:name", Rake::Application.new) }
5
5
  let(:arguments) { Rake::TaskArguments.new(["foo"], ["bar"]) }
6
6
  let(:generic_request) { Appsignal::Transaction::GenericRequest.new({}) }
7
- before(:context) do
8
- Appsignal.config = project_fixture_config
9
- expect(Appsignal.active?).to be_truthy
10
- Appsignal::Hooks.load_hooks
11
- end
7
+ before(:context) { start_agent }
12
8
 
13
9
  describe "#execute" do
14
10
  context "without error" do
11
+ before { expect(Appsignal).to_not receive(:stop) }
12
+
13
+ def perform
14
+ task.execute(arguments)
15
+ end
16
+
15
17
  it "creates no transaction" do
16
- expect(Appsignal::Transaction).to_not receive(:create)
18
+ expect(Appsignal::Transaction).to_not receive(:new)
19
+ expect { perform }.to_not(change { created_transactions })
17
20
  end
18
21
 
19
22
  it "calls the original task" do
20
- expect(task).to receive(:execute_without_appsignal).with("foo")
23
+ expect(task).to receive(:execute_without_appsignal).with(arguments)
24
+ perform
21
25
  end
22
-
23
- after { task.execute("foo") }
24
26
  end
25
27
 
26
28
  context "with error" do
27
29
  let(:error) { ExampleException }
28
- let(:transaction) { background_job_transaction }
29
30
  before do
30
- task.enhance { raise error }
31
-
32
- expect(Appsignal::Transaction).to receive(:create).with(
33
- kind_of(String),
34
- Appsignal::Transaction::BACKGROUND_JOB,
35
- kind_of(Appsignal::Transaction::GenericRequest)
36
- ).and_return(transaction)
31
+ task.enhance { raise error, "my error message" }
32
+ # We don't call `and_call_original` here as we don't want AppSignal to
33
+ # stop and start for every spec.
34
+ expect(Appsignal).to receive(:stop).with("rake")
37
35
  end
38
36
 
39
- it "sets the action" do
40
- expect(transaction).to receive(:set_action).with("task:name")
41
- end
42
-
43
- it "sets the error" do
44
- expect(transaction).to receive(:set_error).with(error)
37
+ def perform
38
+ keep_transactions do
39
+ expect { task.execute(arguments) }.to raise_error(error)
40
+ end
45
41
  end
46
42
 
47
- it "completes the transaction and stops" do
48
- expect(transaction).to receive(:complete).ordered
49
- expect(Appsignal).to receive(:stop).with("rake").ordered
50
- end
43
+ it "creates a background job transaction" do
44
+ perform
51
45
 
52
- it "adds the task arguments to the request" do
53
- expect(Appsignal::Transaction::GenericRequest).to receive(:new)
54
- .with(:params => { :foo => "bar" })
55
- .and_return(generic_request)
46
+ expect(last_transaction).to be_completed
47
+ expect(last_transaction.to_h).to include(
48
+ "id" => kind_of(String),
49
+ "namespace" => Appsignal::Transaction::BACKGROUND_JOB,
50
+ "action" => "task:name",
51
+ "error" => {
52
+ "name" => "ExampleException",
53
+ "message" => "my error message",
54
+ "backtrace" => kind_of(String)
55
+ },
56
+ "sample_data" => hash_including(
57
+ "params" => { "foo" => "bar" }
58
+ )
59
+ )
56
60
  end
57
61
 
58
62
  context "when first argument is not a `Rake::TaskArguments`" do
59
63
  let(:arguments) { nil }
60
64
 
61
- it "adds the first argument regardless" do
62
- expect(Appsignal::Transaction::GenericRequest).to receive(:new)
63
- .with(:params => nil)
64
- .and_return(generic_request)
65
- end
66
- end
65
+ it "does not add the params to the transaction" do
66
+ perform
67
67
 
68
- after do
69
- expect { task.execute(arguments) }.to raise_error ExampleException
68
+ expect(last_transaction.to_h).to include(
69
+ "sample_data" => hash_excluding("params")
70
+ )
71
+ end
70
72
  end
71
73
  end
72
74
  end
@@ -38,28 +38,15 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
38
38
  }
39
39
  end
40
40
  let(:plugin) { Appsignal::Hooks::SidekiqPlugin.new }
41
- let(:test_store) { {} }
42
41
  let(:log) { StringIO.new }
43
42
  before do
44
43
  start_agent
45
44
  Appsignal.logger = test_logger(log)
46
-
47
- # Stub calls to extension, because that would remove the transaction
48
- # from the extension.
49
- allow_any_instance_of(Appsignal::Extension::Transaction).to receive(:finish).and_return(true)
50
- allow_any_instance_of(Appsignal::Extension::Transaction).to receive(:complete)
51
-
52
- # Stub removal of current transaction from current thread so we can fetch
53
- # it later.
54
- expect(Appsignal::Transaction).to receive(:clear_current_transaction!) do
55
- transaction = Thread.current[:appsignal_transaction]
56
- test_store[:transaction] = transaction if transaction
57
- end
58
45
  end
46
+ around { |example| keep_transactions { example.run } }
59
47
  after :with_yaml_parse_error => false do
60
48
  expect(log_contents(log)).to_not contains_log(:warn, "Unable to load YAML")
61
49
  end
62
- after { clear_current_transaction! }
63
50
 
64
51
  shared_examples "sidekiq metadata" do
65
52
  describe "internal Sidekiq job values" do
@@ -513,7 +500,7 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
513
500
  end
514
501
 
515
502
  def transaction
516
- test_store[:transaction]
503
+ last_transaction
517
504
  end
518
505
 
519
506
  def expect_transaction_to_have_sidekiq_event(transaction_hash)
@@ -16,8 +16,8 @@ describe Object do
16
16
  context "when active" do
17
17
  let(:transaction) { http_request_transaction }
18
18
  before do
19
- expect(Appsignal::Transaction).to receive(:current).at_least(:once).and_return(transaction)
20
19
  Appsignal.config = project_fixture_config
20
+ expect(Appsignal::Transaction).to receive(:current).at_least(:once).and_return(transaction)
21
21
  end
22
22
  after { Appsignal.config = nil }
23
23
 
@@ -137,9 +137,9 @@ describe Object do
137
137
  context "when active" do
138
138
  let(:transaction) { http_request_transaction }
139
139
  before do
140
+ Appsignal.config = project_fixture_config
140
141
  expect(Appsignal::Transaction).to receive(:current).at_least(:once)
141
142
  .and_return(transaction)
142
- Appsignal.config = project_fixture_config
143
143
  end
144
144
  after { Appsignal.config = nil }
145
145
 
@@ -37,47 +37,30 @@ if DependencyHelper.que_present?
37
37
  end
38
38
  end
39
39
  let(:instance) { job.new(job_attrs) }
40
- let(:transaction) do
41
- Appsignal::Transaction.new(
42
- SecureRandom.uuid,
43
- Appsignal::Transaction::BACKGROUND_JOB,
44
- Appsignal::Transaction::GenericRequest.new(env)
45
- )
46
- end
47
40
 
48
41
  before do
49
42
  allow(Que).to receive(:execute)
50
43
 
51
44
  start_agent
52
45
  expect(Appsignal.active?).to be_truthy
53
- transaction
54
-
55
- expect(Appsignal::Transaction).to receive(:create)
56
- .with(
57
- kind_of(String),
58
- Appsignal::Transaction::BACKGROUND_JOB,
59
- kind_of(Appsignal::Transaction::GenericRequest)
60
- ).and_return(transaction)
61
- allow(Appsignal::Transaction).to receive(:current).and_return(transaction)
62
- expect(transaction.ext).to receive(:finish).and_return(true)
63
- expect(transaction.ext).to receive(:complete)
64
46
  end
65
-
66
- subject { transaction.to_h }
47
+ around { |example| keep_transactions { example.run } }
67
48
 
68
49
  context "success" do
69
50
  it "creates a transaction for a job" do
70
51
  expect do
71
52
  instance._run
72
- end.to_not raise_exception
53
+ end.to change { created_transactions.length }.by(1)
73
54
 
74
- expect(subject).to include(
55
+ expect(last_transaction).to be_completed
56
+ transaction_hash = last_transaction.to_h
57
+ expect(transaction_hash).to include(
75
58
  "action" => "MyQueJob#run",
76
59
  "id" => instance_of(String),
77
60
  "namespace" => Appsignal::Transaction::BACKGROUND_JOB
78
61
  )
79
- expect(subject["error"]).to be_nil
80
- expect(subject["events"].first).to include(
62
+ expect(transaction_hash["error"]).to be_nil
63
+ expect(transaction_hash["events"].first).to include(
81
64
  "allocation_count" => kind_of(Integer),
82
65
  "body" => "",
83
66
  "body_format" => Appsignal::EventFormatter::DEFAULT,
@@ -91,7 +74,7 @@ if DependencyHelper.que_present?
91
74
  "name" => "perform_job.que",
92
75
  "title" => ""
93
76
  )
94
- expect(subject["sample_data"]).to include(
77
+ expect(transaction_hash["sample_data"]).to include(
95
78
  "params" => %w[1 birds],
96
79
  "metadata" => {
97
80
  "attempts" => 0,
@@ -107,24 +90,28 @@ if DependencyHelper.que_present?
107
90
  context "with exception" do
108
91
  let(:error) { ExampleException.new("oh no!") }
109
92
 
110
- it "should report exceptions and re-raise them" do
93
+ it "reports exceptions and re-raise them" do
111
94
  allow(instance).to receive(:run).and_raise(error)
112
95
 
113
96
  expect do
114
- instance._run
115
- end.to raise_error(ExampleException)
116
-
117
- expect(subject).to include(
97
+ expect do
98
+ instance._run
99
+ end.to raise_error(ExampleException)
100
+ end.to change { created_transactions.length }.by(1)
101
+
102
+ expect(last_transaction).to be_completed
103
+ transaction_hash = last_transaction.to_h
104
+ expect(transaction_hash).to include(
118
105
  "action" => "MyQueJob#run",
119
106
  "id" => instance_of(String),
120
107
  "namespace" => Appsignal::Transaction::BACKGROUND_JOB
121
108
  )
122
- expect(subject["error"]).to include(
109
+ expect(transaction_hash["error"]).to include(
123
110
  "backtrace" => kind_of(String),
124
111
  "name" => error.class.name,
125
112
  "message" => error.message
126
113
  )
127
- expect(subject["sample_data"]).to include(
114
+ expect(transaction_hash["sample_data"]).to include(
128
115
  "params" => %w[1 birds],
129
116
  "metadata" => {
130
117
  "attempts" => 0,
@@ -140,24 +127,24 @@ if DependencyHelper.que_present?
140
127
  context "with error" do
141
128
  let(:error) { ExampleStandardError.new("oh no!") }
142
129
 
143
- it "should report errors and not re-raise them" do
130
+ it "reports errors and not re-raise them" do
144
131
  allow(instance).to receive(:run).and_raise(error)
145
132
 
146
- expect do
147
- instance._run
148
- end.to_not raise_error
133
+ expect { instance._run }.to change { created_transactions.length }.by(1)
149
134
 
150
- expect(subject).to include(
135
+ expect(last_transaction).to be_completed
136
+ transaction_hash = last_transaction.to_h
137
+ expect(transaction_hash).to include(
151
138
  "action" => "MyQueJob#run",
152
139
  "id" => instance_of(String),
153
140
  "namespace" => Appsignal::Transaction::BACKGROUND_JOB
154
141
  )
155
- expect(subject["error"]).to include(
142
+ expect(transaction_hash["error"]).to include(
156
143
  "backtrace" => kind_of(String),
157
144
  "name" => error.class.name,
158
145
  "message" => error.message
159
146
  )
160
- expect(subject["sample_data"]).to include(
147
+ expect(transaction_hash["sample_data"]).to include(
161
148
  "params" => %w[1 birds],
162
149
  "metadata" => {
163
150
  "attempts" => 0,