appsignal 1.4.0.alpha.2 → 1.4.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +3 -1
  3. data/.travis.yml +3 -1
  4. data/CHANGELOG.md +38 -1
  5. data/Rakefile +29 -12
  6. data/benchmark.rake +3 -7
  7. data/ext/agent.yml +11 -11
  8. data/ext/appsignal_extension.c +364 -72
  9. data/ext/extconf.rb +2 -4
  10. data/gemfiles/resque.gemfile +1 -0
  11. data/lib/appsignal.rb +40 -30
  12. data/lib/appsignal/auth_check.rb +1 -1
  13. data/lib/appsignal/cli/diagnose.rb +4 -3
  14. data/lib/appsignal/cli/install.rb +16 -15
  15. data/lib/appsignal/config.rb +31 -31
  16. data/lib/appsignal/event_formatter.rb +1 -1
  17. data/lib/appsignal/extension.rb +6 -0
  18. data/lib/appsignal/garbage_collection_profiler.rb +47 -0
  19. data/lib/appsignal/hooks.rb +1 -0
  20. data/lib/appsignal/hooks/active_support_notifications.rb +43 -0
  21. data/lib/appsignal/integrations/capistrano/appsignal.cap +1 -1
  22. data/lib/appsignal/integrations/capistrano/capistrano_2_tasks.rb +2 -2
  23. data/lib/appsignal/integrations/mongo_ruby_driver.rb +1 -1
  24. data/lib/appsignal/integrations/object.rb +4 -4
  25. data/lib/appsignal/integrations/padrino.rb +1 -1
  26. data/lib/appsignal/integrations/sinatra.rb +1 -1
  27. data/lib/appsignal/integrations/webmachine.rb +2 -2
  28. data/lib/appsignal/js_exception_transaction.rb +7 -10
  29. data/lib/appsignal/marker.rb +3 -2
  30. data/lib/appsignal/rack/generic_instrumentation.rb +1 -1
  31. data/lib/appsignal/rack/sinatra_instrumentation.rb +13 -6
  32. data/lib/appsignal/rack/streaming_listener.rb +5 -3
  33. data/lib/appsignal/system.rb +36 -0
  34. data/lib/appsignal/transaction.rb +20 -20
  35. data/lib/appsignal/transmitter.rb +11 -7
  36. data/lib/appsignal/utils.rb +76 -2
  37. data/lib/appsignal/version.rb +1 -1
  38. data/spec/lib/appsignal/auth_check_spec.rb +0 -2
  39. data/spec/lib/appsignal/capistrano2_spec.rb +99 -79
  40. data/spec/lib/appsignal/capistrano3_spec.rb +57 -78
  41. data/spec/lib/appsignal/cli/diagnose_spec.rb +17 -15
  42. data/spec/lib/appsignal/cli/install_spec.rb +38 -20
  43. data/spec/lib/appsignal/cli/notify_of_deploy_spec.rb +2 -5
  44. data/spec/lib/appsignal/cli_spec.rb +2 -5
  45. data/spec/lib/appsignal/config_spec.rb +385 -158
  46. data/spec/lib/appsignal/event_formatter/action_view/render_formatter_spec.rb +1 -3
  47. data/spec/lib/appsignal/event_formatter/active_record/instantiation_formatter_spec.rb +0 -2
  48. data/spec/lib/appsignal/event_formatter/active_record/sql_formatter_spec.rb +0 -2
  49. data/spec/lib/appsignal/event_formatter/elastic_search/search_formatter_spec.rb +0 -2
  50. data/spec/lib/appsignal/event_formatter/faraday/request_formatter_spec.rb +0 -2
  51. data/spec/lib/appsignal/event_formatter/mongo_ruby_driver/query_formatter_spec.rb +0 -2
  52. data/spec/lib/appsignal/event_formatter/moped/query_formatter_spec.rb +0 -2
  53. data/spec/lib/appsignal/event_formatter_spec.rb +0 -2
  54. data/spec/lib/appsignal/extension_spec.rb +7 -8
  55. data/spec/lib/appsignal/garbage_collection_profiler_spec.rb +71 -0
  56. data/spec/lib/appsignal/hooks/active_support_notifications_spec.rb +42 -0
  57. data/spec/lib/appsignal/hooks/celluloid_spec.rb +0 -2
  58. data/spec/lib/appsignal/hooks/data_mapper_spec.rb +0 -2
  59. data/spec/lib/appsignal/hooks/delayed_job_spec.rb +0 -2
  60. data/spec/lib/appsignal/hooks/mongo_ruby_driver_spec.rb +0 -2
  61. data/spec/lib/appsignal/hooks/net_http_spec.rb +0 -2
  62. data/spec/lib/appsignal/hooks/passenger_spec.rb +0 -2
  63. data/spec/lib/appsignal/hooks/puma_spec.rb +0 -2
  64. data/spec/lib/appsignal/hooks/rake_spec.rb +1 -2
  65. data/spec/lib/appsignal/hooks/redis_spec.rb +0 -2
  66. data/spec/lib/appsignal/hooks/sequel_spec.rb +19 -21
  67. data/spec/lib/appsignal/hooks/shoryuken_spec.rb +1 -4
  68. data/spec/lib/appsignal/hooks/sidekiq_spec.rb +2 -3
  69. data/spec/lib/appsignal/hooks/unicorn_spec.rb +0 -2
  70. data/spec/lib/appsignal/hooks/webmachine_spec.rb +4 -11
  71. data/spec/lib/appsignal/hooks_spec.rb +0 -2
  72. data/spec/lib/appsignal/integrations/data_mapper_spec.rb +0 -1
  73. data/spec/lib/appsignal/integrations/grape_spec.rb +1 -3
  74. data/spec/lib/appsignal/integrations/mongo_ruby_driver_spec.rb +1 -2
  75. data/spec/lib/appsignal/integrations/object_spec.rb +32 -3
  76. data/spec/lib/appsignal/integrations/padrino_spec.rb +4 -11
  77. data/spec/lib/appsignal/integrations/railtie_spec.rb +1 -3
  78. data/spec/lib/appsignal/integrations/resque_active_job_spec.rb +1 -3
  79. data/spec/lib/appsignal/integrations/resque_spec.rb +2 -4
  80. data/spec/lib/appsignal/integrations/sinatra_spec.rb +33 -8
  81. data/spec/lib/appsignal/integrations/webmachine_spec.rb +6 -15
  82. data/spec/lib/appsignal/js_exception_transaction_spec.rb +3 -5
  83. data/spec/lib/appsignal/marker_spec.rb +35 -48
  84. data/spec/lib/appsignal/minutely_spec.rb +0 -2
  85. data/spec/lib/appsignal/rack/generic_instrumentation_spec.rb +0 -2
  86. data/spec/lib/appsignal/rack/js_exception_catcher_spec.rb +0 -2
  87. data/spec/lib/appsignal/rack/rails_instrumentation_spec.rb +3 -5
  88. data/spec/lib/appsignal/rack/sinatra_instrumentation_spec.rb +47 -11
  89. data/spec/lib/appsignal/rack/streaming_listener_spec.rb +6 -7
  90. data/spec/lib/appsignal/system/container_spec.rb +67 -0
  91. data/spec/lib/appsignal/system_spec.rb +49 -0
  92. data/spec/lib/appsignal/transaction_spec.rb +30 -13
  93. data/spec/lib/appsignal/transmitter_spec.rb +53 -20
  94. data/spec/lib/appsignal/utils/gzip_spec.rb +10 -0
  95. data/spec/lib/appsignal/utils/params_sanitizer_spec.rb +0 -2
  96. data/spec/lib/appsignal/utils/query_params_sanitizer_spec.rb +0 -2
  97. data/spec/lib/appsignal/utils_spec.rb +59 -3
  98. data/spec/lib/appsignal_spec.rb +132 -58
  99. data/spec/spec_helper.rb +24 -116
  100. data/spec/support/fixtures/containers/cgroups/docker +14 -0
  101. data/spec/support/fixtures/containers/cgroups/docker_systemd +8 -0
  102. data/spec/support/fixtures/containers/cgroups/lxc +10 -0
  103. data/spec/support/fixtures/containers/cgroups/no_permission +0 -0
  104. data/spec/support/fixtures/containers/cgroups/none +1 -0
  105. data/spec/support/helpers/api_request_helper.rb +22 -0
  106. data/spec/support/helpers/dependency_helper.rb +61 -0
  107. data/spec/support/helpers/directory_helper.rb +27 -0
  108. data/spec/support/helpers/std_streams_helper.rb +35 -0
  109. data/spec/support/helpers/system_helpers.rb +24 -0
  110. data/spec/support/helpers/transaction_helpers.rb +7 -64
  111. data/spec/support/helpers/very_specific_error.rb +8 -0
  112. data/spec/support/mocks/fake_gc_profiler.rb +19 -0
  113. data/spec/support/project_fixture/config/appsignal.yml +10 -1
  114. metadata +60 -35
  115. data/circle.yml +0 -12
  116. data/lib/appsignal/subscriber.rb +0 -55
  117. data/lib/appsignal/update_active_support.rb +0 -20
  118. data/lib/vendor/active_support/notifications.rb +0 -212
  119. data/lib/vendor/active_support/notifications/fanout.rb +0 -157
  120. data/lib/vendor/active_support/notifications/instrumenter.rb +0 -73
  121. data/lib/vendor/active_support/per_thread_registry.rb +0 -53
  122. data/spec/lib/appsignal/subscriber_spec.rb +0 -160
  123. data/spec/lib/appsignal/update_active_support_spec.rb +0 -17
  124. data/spec/support/helpers/notification_helpers.rb +0 -14
@@ -1,27 +1,29 @@
1
- require 'spec_helper'
2
1
  require 'appsignal/cli'
3
2
 
4
3
  describe Appsignal::CLI::Diagnose do
5
4
  let(:out_stream) { StringIO.new }
6
- let(:cli) { Appsignal::CLI::Diagnose }
7
- before do
8
- @original_stdout = $stdout
9
- $stdout = out_stream
10
- end
11
- after do
12
- $stdout = @original_stdout
5
+ let(:config) { project_fixture_config }
6
+ let(:cli) { described_class }
7
+ around do |example|
8
+ capture_stdout(out_stream) { example.run }
13
9
  end
14
10
 
15
11
  describe ".run" do
12
+ before do
13
+ Appsignal.config = config
14
+ stub_api_request config, 'auth'
15
+ end
16
+
16
17
  it "should output diagnostic information" do
17
18
  cli.run
18
-
19
- out_stream.string.should include('Gem version')
20
- out_stream.string.should include('Agent version')
21
- out_stream.string.should include('Config')
22
- out_stream.string.should include('Checking API key')
23
- out_stream.string.should include('Checking if required paths are writable')
24
- out_stream.string.should include('Showing last lines of extension install log')
19
+ output = out_stream.string
20
+ expect(output).to include('Gem version')
21
+ expect(output).to include('Agent version')
22
+ expect(output).to include('Environment')
23
+ expect(output).to include('Config')
24
+ expect(output).to include('Checking API key')
25
+ expect(output).to include('Checking if required paths are writable')
26
+ expect(output).to include('Showing last lines of extension install log')
25
27
  end
26
28
  end
27
29
  end
@@ -1,11 +1,5 @@
1
- require 'spec_helper'
2
1
  require 'appsignal/cli'
3
2
 
4
- begin
5
- require 'sinatra'
6
- rescue LoadError
7
- end
8
-
9
3
  describe Appsignal::CLI::Install do
10
4
  let(:out_stream) { StringIO.new }
11
5
  let(:cli) { Appsignal::CLI::Install }
@@ -14,15 +8,13 @@ describe Appsignal::CLI::Install do
14
8
 
15
9
  before do
16
10
  Dir.stub(:pwd => project_fixture_path)
17
- @original_stdout = $stdout
18
- $stdout = out_stream
19
11
  Appsignal::AuthCheck.stub(:new => auth_check)
20
12
  auth_check.stub(:perform => '200')
21
13
  cli.stub(:sleep)
22
14
  cli.stub(:press_any_key)
23
15
  end
24
- after do
25
- $stdout = @original_stdout
16
+ around do |example|
17
+ capture_stdout(out_stream) { example.run }
26
18
  end
27
19
 
28
20
  describe ".run" do
@@ -375,24 +367,50 @@ describe Appsignal::CLI::Install do
375
367
  end
376
368
  end
377
369
 
378
- context "when deploy.rb is present" do
379
- let(:config_dir) { File.join(tmp_dir, 'config') }
380
- let(:deploy_rb_file) { File.join(tmp_dir, 'config/deploy.rb') }
370
+ context "with capistrano" do
371
+ let(:capfile) { File.join(tmp_dir, 'Capfile') }
381
372
  before do
382
373
  Dir.stub(:pwd => tmp_dir)
383
- FileUtils.mkdir_p(config_dir)
384
- FileUtils.touch(deploy_rb_file)
374
+ FileUtils.mkdir_p(tmp_dir)
385
375
  cli.should_receive(:gets).once.and_return('2')
386
376
  end
387
377
  after do
388
- FileUtils.rm_rf(config_dir)
378
+ FileUtils.rm_rf(tmp_dir)
389
379
  end
390
380
 
391
- it "should add a require to deploy.rb" do
392
- cli.configure(config, [], false)
381
+ context "without Capfile" do
382
+ before { cli.configure(config, [], false) }
383
+
384
+ it "does nothing" do
385
+ expect(out_stream.string).to_not include 'Adding AppSignal integration to Capfile'
386
+ expect(File.exist?(capfile)).to be_false
387
+ end
388
+ end
389
+
390
+ context "with Capfile" do
391
+ context "when already installed" do
392
+ before do
393
+ File.open(capfile, 'w') { |f| f.write("require 'appsignal/capistrano'") }
394
+ cli.configure(config, [], false)
395
+ end
396
+
397
+ it "does not add another require to Capfile" do
398
+ expect(out_stream.string).to_not include 'Adding AppSignal integration to Capfile'
399
+ expect(File.read(capfile).scan(/appsignal/).count).to eq(1)
400
+ end
401
+ end
393
402
 
394
- out_stream.string.should include 'Adding AppSignal integration to deploy.rb'
395
- File.read(deploy_rb_file).should include "require 'appsignal/capistrano'"
403
+ context "when not installed" do
404
+ before do
405
+ FileUtils.touch(capfile)
406
+ cli.configure(config, [], false)
407
+ end
408
+
409
+ it "adds a require to Capfile" do
410
+ expect(out_stream.string).to include 'Adding AppSignal integration to Capfile'
411
+ expect(File.read(capfile)).to include "require 'appsignal/capistrano'"
412
+ end
413
+ end
396
414
  end
397
415
  end
398
416
  end
@@ -1,4 +1,3 @@
1
- require 'spec_helper'
2
1
  require 'appsignal/cli'
3
2
 
4
3
  describe Appsignal::CLI::NotifyOfDeploy do
@@ -7,12 +6,10 @@ describe Appsignal::CLI::NotifyOfDeploy do
7
6
  let(:config) { Appsignal::Config.new(project_fixture_path, {}) }
8
7
  let(:marker_data) { {:revision => 'aaaaa', :user => 'thijs', :environment => 'production'} }
9
8
  before do
10
- @original_stdout = $stdout
11
- $stdout = out_stream
12
9
  config.stub(:active? => true)
13
10
  end
14
- after do
15
- $stdout = @original_stdout
11
+ around do |example|
12
+ capture_stdout(out_stream) { example.run }
16
13
  end
17
14
 
18
15
  describe ".run" do
@@ -1,17 +1,14 @@
1
- require 'spec_helper'
2
1
  require 'appsignal/cli'
3
2
 
4
3
  describe Appsignal::CLI do
5
4
  let(:out_stream) { StringIO.new }
6
5
  let(:cli) { Appsignal::CLI }
7
6
  before do
8
- @original_stdout = $stdout
9
- $stdout = out_stream
10
7
  Dir.stub(:pwd => project_fixture_path)
11
8
  cli.options = {:environment => 'production'}
12
9
  end
13
- after do
14
- $stdout = @original_stdout
10
+ around do |example|
11
+ capture_stdout(out_stream) { example.run }
15
12
  end
16
13
 
17
14
  describe "#config" do
@@ -1,22 +1,50 @@
1
- require 'spec_helper'
2
-
3
1
  describe Appsignal::Config do
4
- subject { config }
2
+ describe "config based on the system" do
3
+ let(:config) { project_fixture_config(:none) }
5
4
 
6
- describe "with a config file" do
7
- let(:config) { project_fixture_config('production') }
5
+ describe ":running_in_container" do
6
+ subject { config[:running_in_container] }
7
+
8
+ context "when running on Heroku" do
9
+ around { |example| recognize_as_heroku { example.run } }
10
+
11
+ it "is set to true" do
12
+ expect(subject).to be_true
13
+ end
14
+ end
15
+
16
+ context "when running in container" do
17
+ around { |example| recognize_as_container(:docker) { example.run } }
8
18
 
9
- it "should not log an error" do
10
- Logger.any_instance.should_not_receive(:log_error)
11
- subject
19
+ it "is set to true" do
20
+ expect(subject).to be_true
21
+ end
22
+ end
23
+
24
+ context "when not running in container" do
25
+ around { |example| recognize_as_container(:none) { example.run } }
26
+
27
+ it "is set to false" do
28
+ expect(subject).to be_false
29
+ end
30
+ end
12
31
  end
32
+ end
13
33
 
14
- its(:valid?) { should be_true }
15
- its(:active?) { should be_true }
16
- its(:log_file_path) { should end_with('spec/support/project_fixture/appsignal.log') }
34
+ describe "initial config" do
35
+ let(:config) do
36
+ described_class.new(
37
+ "non-existing-path",
38
+ "production",
39
+ :push_api_key => "abc",
40
+ :name => "TestApp",
41
+ :active => true
42
+ )
43
+ end
44
+ around { |example| recognize_as_container(:none) { example.run } }
17
45
 
18
- it "should merge with the default config and fill the config hash" do
19
- subject.config_hash.should eq({
46
+ it "merges with the default config" do
47
+ expect(config.config_hash).to eq(
20
48
  :debug => false,
21
49
  :ignore_errors => [],
22
50
  :ignore_actions => [],
@@ -37,221 +65,420 @@ describe Appsignal::Config do
37
65
  :running_in_container => false,
38
66
  :enable_host_metrics => true,
39
67
  :enable_minutely_probes => false,
40
- :hostname => Socket.gethostname
41
- })
68
+ :hostname => Socket.gethostname,
69
+ :ca_file_path => File.join(resources_dir, 'cacert.pem')
70
+ )
42
71
  end
43
72
 
44
- context "if a log file path is set" do
45
- let(:config) { project_fixture_config('production', :log_path => '/tmp') }
73
+ describe "overriding system detected config" do
74
+ let(:config) do
75
+ described_class.new(
76
+ "non-existing-path",
77
+ "production",
78
+ :running_in_container => true
79
+ )
80
+ end
46
81
 
47
- its(:log_file_path) { should end_with('/tmp/appsignal.log') }
82
+ it "overrides system detected config" do
83
+ expect(config[:running_in_container]).to be_true
84
+ end
85
+ end
86
+ end
48
87
 
49
- context "if it is not writable" do
50
- before { FileUtils.mkdir_p('/tmp/not-writable', :mode => 0555) }
88
+ context "when root path is nil" do
89
+ let(:config) { described_class.new(nil, 'production') }
51
90
 
52
- let(:config) { project_fixture_config('production', :log_path => '/tmp/not-writable') }
91
+ it "is not valid or active" do
92
+ expect(config.valid?).to be_false
93
+ expect(config.active?).to be_false
94
+ end
95
+ end
53
96
 
54
- its(:log_file_path) { should eq '/tmp/appsignal.log' }
55
- end
97
+ context "without config file" do
98
+ let(:config) { described_class.new(tmp_dir, 'production') }
56
99
 
57
- context "if it does not exist" do
58
- let(:config) { project_fixture_config('production', :log_path => '/non-existing') }
100
+ it "is not valid or active" do
101
+ expect(config.valid?).to be_false
102
+ expect(config.active?).to be_false
103
+ end
104
+ end
59
105
 
60
- its(:log_file_path) { should eq '/tmp/appsignal.log' }
61
- end
106
+ context "with a config file" do
107
+ let(:config) { project_fixture_config('production') }
62
108
 
63
- context "if it is nil" do
64
- let(:config) { project_fixture_config('production', :log_path => nil) }
109
+ it "is not valid or active" do
110
+ expect(config.valid?).to be_true
111
+ expect(config.active?).to be_true
112
+ end
113
+
114
+ it "does not log an error" do
115
+ expect_any_instance_of(Logger).to_not receive(:error)
116
+ config
117
+ end
65
118
 
66
- before { config.stub(:root_path => nil) }
119
+ describe "overriding system and defaults config" do
120
+ let(:config) do
121
+ described_class.new(
122
+ "non-existing-path",
123
+ "production",
124
+ :running_in_container => true,
125
+ :debug => true
126
+ )
127
+ end
128
+ around { |example| recognize_as_container(:none) { example.run } }
67
129
 
68
- its(:log_file_path) { should eq '/tmp/appsignal.log' }
130
+ it "overrides system detected and defaults config" do
131
+ expect(config[:running_in_container]).to be_true
132
+ expect(config[:debug]).to be_true
69
133
  end
70
134
  end
71
135
 
72
- context "when there is a pre 0.12 style endpoint" do
73
- let(:config) { project_fixture_config('production', :endpoint => 'https://push.appsignal.com/1') }
136
+ context "with the env name as a symbol" do
137
+ let(:config) { project_fixture_config(:production) }
138
+
139
+ it "loads the config" do
140
+ expect(config.valid?).to be_true
141
+ expect(config.active?).to be_true
74
142
 
75
- it "should strip the path" do
76
- subject[:endpoint].should eq 'https://push.appsignal.com'
143
+ expect(config[:push_api_key]).to eq('abc')
77
144
  end
78
145
  end
79
146
 
80
- context "when there is an endpoint with a non-standard port" do
81
- let(:config) { project_fixture_config('production', :endpoint => 'http://localhost:4567') }
147
+ context "without the selected env" do
148
+ let(:config) { project_fixture_config('nonsense') }
82
149
 
83
- it "should keep the port" do
84
- subject[:endpoint].should eq 'http://localhost:4567'
150
+ it "is not valid or active" do
151
+ expect(config.valid?).to be_false
152
+ expect(config.active?).to be_false
153
+ end
154
+
155
+ it "logs an error" do
156
+ expect_any_instance_of(Logger).to receive(:error).once
157
+ .with("Not loading from config file: config for 'nonsense' not found")
158
+ expect_any_instance_of(Logger).to receive(:error).once
159
+ .with("Push api key not set after loading config")
160
+ config
85
161
  end
86
162
  end
87
163
 
88
- describe "#[]= and #[]" do
89
- it "should get the value for an existing key" do
90
- subject[:push_api_key].should eq 'abc'
164
+ describe "old-style config keys" do
165
+ describe ":api_key" do
166
+ subject { config[:push_api_key] }
167
+
168
+ context "without :push_api_key" do
169
+ let(:config) { project_fixture_config('old_config') }
170
+
171
+ it "sets the :push_api_key with the old :api_key value" do
172
+ expect(subject).to eq 'def'
173
+ end
174
+ end
175
+
176
+ context "with :push_api_key" do
177
+ let(:config) { project_fixture_config('old_config_mixed_with_new_config') }
178
+
179
+ it "ignores the :api_key config" do
180
+ expect(subject).to eq 'ghi'
181
+ end
182
+ end
91
183
  end
92
184
 
93
- it "should change and get the value for an existing key" do
94
- subject[:push_api_key] = 'abcde'
95
- subject[:push_api_key].should eq 'abcde'
185
+ describe ":ignore_exceptions" do
186
+ subject { config[:ignore_errors] }
187
+
188
+ context "without :ignore_errors" do
189
+ let(:config) { project_fixture_config('old_config') }
190
+
191
+ it "sets :ignore_errors with the old :ignore_exceptions value" do
192
+ expect(subject).to eq ['StandardError']
193
+ end
194
+ end
195
+
196
+ context "with :ignore_errors" do
197
+ let(:config) { project_fixture_config('old_config_mixed_with_new_config') }
198
+
199
+ it "ignores the :ignore_exceptions config" do
200
+ expect(subject).to eq ['NoMethodError']
201
+ end
202
+ end
203
+ end
204
+ end
205
+ end
206
+
207
+ context "with config in the environment" do
208
+ let(:config) do
209
+ described_class.new(
210
+ "non-existing-path",
211
+ "production",
212
+ :running_in_container => true,
213
+ :debug => true
214
+ )
215
+ end
216
+ before do
217
+ ENV['APPSIGNAL_RUNNING_IN_CONTAINER'] = 'true'
218
+ ENV['APPSIGNAL_PUSH_API_KEY'] = 'aaa-bbb-ccc'
219
+ ENV['APPSIGNAL_ACTIVE'] = 'true'
220
+ ENV['APPSIGNAL_APP_NAME'] = 'App name'
221
+ ENV['APPSIGNAL_DEBUG'] = 'true'
222
+ ENV['APPSIGNAL_IGNORE_ACTIONS'] = 'action1,action2'
223
+ end
224
+ around { |example| recognize_as_container(:none) { example.run } }
225
+
226
+ it "overrides config with environment values" do
227
+ expect(config.valid?).to be_true
228
+ expect(config.active?).to be_true
229
+
230
+ expect(config[:running_in_container]).to be_true
231
+ expect(config[:push_api_key]).to eq 'aaa-bbb-ccc'
232
+ expect(config[:active]).to be_true
233
+ expect(config[:name]).to eq 'App name'
234
+ expect(config[:debug]).to be_true
235
+ expect(config[:ignore_actions]).to eq ['action1', 'action2']
236
+ end
237
+ end
238
+
239
+ describe "config keys" do
240
+ describe ":endpoint" do
241
+ subject { config[:endpoint] }
242
+
243
+ context "with an pre-0.12-style endpoint" do
244
+ let(:config) do
245
+ project_fixture_config('production', :endpoint => 'https://push.appsignal.com/1')
246
+ end
247
+
248
+ it "strips off the path" do
249
+ expect(subject).to eq 'https://push.appsignal.com'
250
+ end
96
251
  end
97
252
 
98
- it "should return nil for a non-existing key" do
99
- subject[:nonsense].should be_nil
253
+ context "with a non-standard port" do
254
+ let(:config) { project_fixture_config('production', :endpoint => 'http://localhost:4567') }
255
+
256
+ it "keeps the port" do
257
+ expect(subject).to eq 'http://localhost:4567'
258
+ end
100
259
  end
101
260
  end
261
+ end
102
262
 
103
- describe "#write_to_environment" do
104
- before do
105
- subject.config_hash[:http_proxy] = 'http://localhost'
106
- subject.config_hash[:ignore_actions] = ['action1', 'action2']
107
- subject.config_hash[:log_path] = '/tmp'
108
- subject.config_hash[:hostname] = 'app1.local'
109
- subject.config_hash[:filter_parameters] = %w(password confirm_password)
110
- subject.write_to_environment
263
+ describe "#[]" do
264
+ let(:config) { project_fixture_config(:none, :push_api_key => 'foo') }
265
+
266
+ context "with existing key" do
267
+ it "gets the value" do
268
+ expect(config[:push_api_key]).to eq 'foo'
111
269
  end
270
+ end
112
271
 
113
- it "should write the current config to env vars" do
114
- ENV['APPSIGNAL_ACTIVE'].should eq 'true'
115
- ENV['APPSIGNAL_APP_PATH'].should end_with('spec/support/project_fixture')
116
- ENV['APPSIGNAL_AGENT_PATH'].should end_with('/ext')
117
- ENV['APPSIGNAL_DEBUG_LOGGING'].should eq 'false'
118
- ENV['APPSIGNAL_LOG_FILE_PATH'].should end_with('/tmp/appsignal.log')
119
- ENV['APPSIGNAL_PUSH_API_ENDPOINT'].should eq 'https://push.appsignal.com'
120
- ENV['APPSIGNAL_PUSH_API_KEY'].should eq 'abc'
121
- ENV['APPSIGNAL_APP_NAME'].should eq 'TestApp'
122
- ENV['APPSIGNAL_ENVIRONMENT'].should eq 'production'
123
- ENV['APPSIGNAL_AGENT_VERSION'].should eq Appsignal::Extension.agent_version
124
- ENV['APPSIGNAL_LANGUAGE_INTEGRATION_VERSION'].should eq Appsignal::VERSION
125
- ENV['APPSIGNAL_HTTP_PROXY'].should eq 'http://localhost'
126
- ENV['APPSIGNAL_IGNORE_ACTIONS'].should eq 'action1,action2'
127
- ENV['APPSIGNAL_FILTER_PARAMETERS'].should eq 'password,confirm_password'
128
- ENV['APPSIGNAL_SEND_PARAMS'].should eq 'true'
129
- ENV['APPSIGNAL_RUNNING_IN_CONTAINER'].should eq 'false'
130
- ENV['APPSIGNAL_WORKING_DIR_PATH'].should be_nil
131
- ENV['APPSIGNAL_ENABLE_HOST_METRICS'].should eq 'true'
132
- ENV['APPSIGNAL_ENABLE_MINUTELY_PROBES'].should eq 'false'
133
- ENV['APPSIGNAL_HOSTNAME'].should eq 'app1.local'
134
- ENV['APPSIGNAL_PROCESS_NAME'].should include 'rspec'
272
+ context "without existing key" do
273
+ it "returns nil" do
274
+ expect(config[:nonsense]).to be_nil
135
275
  end
276
+ end
277
+ end
136
278
 
137
- context "if working_dir_path is set" do
138
- before do
139
- subject.config_hash[:working_dir_path] = '/tmp/appsignal2'
140
- subject.write_to_environment
141
- end
279
+ describe "#[]=" do
280
+ let(:config) { project_fixture_config(:none) }
142
281
 
143
- it "should write the current config to env vars" do
144
- ENV['APPSIGNAL_WORKING_DIR_PATH'].should eq '/tmp/appsignal2'
145
- end
282
+ context "with existing key" do
283
+ it "changes the value" do
284
+ expect(config[:push_api_key]).to be_nil
285
+ config[:push_api_key] = 'abcde'
286
+ expect(config[:push_api_key]).to eq 'abcde'
146
287
  end
147
288
  end
148
289
 
149
- context "if the env is passed as a symbol" do
150
- let(:config) { project_fixture_config(:production) }
290
+ context "with new key" do
291
+ it "sets the value" do
292
+ expect(config[:foo]).to be_nil
293
+ config[:foo] = 'bar'
294
+ expect(config[:foo]).to eq 'bar'
295
+ end
296
+ end
297
+ end
298
+
299
+ describe "#write_to_environment" do
300
+ let(:config) { project_fixture_config(:production) }
301
+ before do
302
+ config[:http_proxy] = 'http://localhost'
303
+ config[:ignore_actions] = ['action1', 'action2']
304
+ config[:log_path] = '/tmp'
305
+ config[:hostname] = 'app1.local'
306
+ config[:filter_parameters] = %w(password confirm_password)
307
+ config[:running_in_container] = false
308
+ config.write_to_environment
309
+ end
151
310
 
152
- its(:active?) { should be_true }
311
+ it "writes the current config to environment variables" do
312
+ expect(ENV['APPSIGNAL_ACTIVE']).to eq 'true'
313
+ expect(ENV['APPSIGNAL_APP_PATH']).to end_with('spec/support/project_fixture')
314
+ expect(ENV['APPSIGNAL_AGENT_PATH']).to end_with('/ext')
315
+ expect(ENV['APPSIGNAL_DEBUG_LOGGING']).to eq 'false'
316
+ expect(ENV['APPSIGNAL_LOG_FILE_PATH']).to end_with('/tmp/appsignal.log')
317
+ expect(ENV['APPSIGNAL_PUSH_API_ENDPOINT']).to eq 'https://push.appsignal.com'
318
+ expect(ENV['APPSIGNAL_PUSH_API_KEY']).to eq 'abc'
319
+ expect(ENV['APPSIGNAL_APP_NAME']).to eq 'TestApp'
320
+ expect(ENV['APPSIGNAL_ENVIRONMENT']).to eq 'production'
321
+ expect(ENV['APPSIGNAL_AGENT_VERSION']).to eq Appsignal::Extension.agent_version
322
+ expect(ENV['APPSIGNAL_LANGUAGE_INTEGRATION_VERSION']).to eq Appsignal::VERSION
323
+ expect(ENV['APPSIGNAL_HTTP_PROXY']).to eq 'http://localhost'
324
+ expect(ENV['APPSIGNAL_IGNORE_ACTIONS']).to eq 'action1,action2'
325
+ expect(ENV['APPSIGNAL_FILTER_PARAMETERS']).to eq 'password,confirm_password'
326
+ expect(ENV['APPSIGNAL_SEND_PARAMS']).to eq 'true'
327
+ expect(ENV['APPSIGNAL_RUNNING_IN_CONTAINER']).to eq 'false'
328
+ expect(ENV['APPSIGNAL_ENABLE_HOST_METRICS']).to eq 'true'
329
+ expect(ENV['APPSIGNAL_ENABLE_MINUTELY_PROBES']).to eq 'false'
330
+ expect(ENV['APPSIGNAL_HOSTNAME']).to eq 'app1.local'
331
+ expect(ENV['APPSIGNAL_PROCESS_NAME']).to include 'rspec'
332
+ expect(ENV['APPSIGNAL_CA_FILE_PATH']).to eq File.join(resources_dir, "cacert.pem")
333
+ expect(ENV).to_not have_key('APPSIGNAL_WORKING_DIR_PATH')
153
334
  end
154
335
 
155
- context "and there's config in the environment" do
336
+ context "with :working_dir_path" do
156
337
  before do
157
- ENV['APPSIGNAL_PUSH_API_KEY'] = 'push_api_key'
158
- ENV['APPSIGNAL_DEBUG'] = 'true'
338
+ config[:working_dir_path] = '/tmp/appsignal2'
339
+ config.write_to_environment
159
340
  end
160
341
 
161
- it "should ignore env vars that are present in the config file" do
162
- subject[:push_api_key].should eq 'abc'
342
+ it "sets the modified :working_dir_path" do
343
+ expect(ENV['APPSIGNAL_WORKING_DIR_PATH']).to eq '/tmp/appsignal2'
344
+ end
345
+ end
346
+ end
347
+
348
+ describe "#log_file_path" do
349
+ let(:stdout) { StringIO.new }
350
+ let(:config) { project_fixture_config('production', :log_path => log_path) }
351
+ subject { config.log_file_path }
352
+ around { |example| capture_stdout(stdout) { example.run } }
353
+
354
+ context "when path is writable" do
355
+ let(:log_path) { File.join(tmp_dir, 'writable-path') }
356
+ before { FileUtils.mkdir_p(log_path, :mode => 0755) }
357
+ after { FileUtils.rm_rf(log_path) }
358
+
359
+ it "returns log file path" do
360
+ expect(subject).to eq File.join(log_path, 'appsignal.log')
163
361
  end
164
362
 
165
- it "should use env vars that are not present in the config file" do
166
- subject[:debug].should eq true
363
+ it "prints no warning" do
364
+ subject
365
+ expect(stdout.string).to be_empty
167
366
  end
367
+ end
168
368
 
169
- context "running on Heroku" do
170
- before do
171
- ENV['DYNO'] = 'true'
172
- end
173
- after do
174
- ENV.delete('DYNO')
369
+ shared_examples '#log_file_path: tmp path' do
370
+ let(:system_tmp_dir) { described_class::SYSTEM_TMP_DIR }
371
+ before { FileUtils.mkdir_p(system_tmp_dir) }
372
+ after { FileUtils.rm_rf(system_tmp_dir) }
373
+
374
+ context "when the /tmp fallback path is writable" do
375
+ before { FileUtils.chmod(0777, system_tmp_dir) }
376
+
377
+ it "returns returns the tmp location" do
378
+ expect(subject).to eq(File.join(system_tmp_dir, 'appsignal.log'))
175
379
  end
176
380
 
177
- it "should set running in container to true" do
178
- subject[:running_in_container].should be_true
381
+ it "prints a warning" do
382
+ subject
383
+ expect(stdout.string).to include "appsignal: Unable to log to '#{log_path}'. "\
384
+ "Logging to '#{system_tmp_dir}' instead."
179
385
  end
180
386
  end
181
- end
182
387
 
183
- context "and there is an initial config" do
184
- let(:config) { project_fixture_config('production', :name => 'Initial name', :initial_key => 'value') }
388
+ context "when the /tmp fallback path is not writable" do
389
+ before { FileUtils.chmod(0555, system_tmp_dir) }
390
+
391
+ it "returns nil" do
392
+ expect(subject).to be_nil
393
+ end
185
394
 
186
- it "should merge with the config" do
187
- subject[:name].should eq 'TestApp'
188
- subject[:initial_key].should eq 'value'
395
+ it "prints a warning" do
396
+ subject
397
+ expect(stdout.string).to include "appsignal: Unable to log to '#{log_path}' "\
398
+ "or the '#{system_tmp_dir}' fallback."
399
+ end
189
400
  end
190
401
  end
191
402
 
192
- context "and there is an old-style config" do
193
- let(:config) { project_fixture_config('old_api_key') }
403
+ context "when path is nil" do
404
+ let(:log_path) { nil }
405
+
406
+ context "when root_path is nil" do
407
+ before { allow(config).to receive(:root_path).and_return(nil) }
194
408
 
195
- it "should fill the push_api_key with the old style key" do
196
- subject[:push_api_key].should eq 'def'
409
+ include_examples '#log_file_path: tmp path'
197
410
  end
198
411
 
199
- it "should fill ignore_errors with the old style ignore exceptions" do
200
- subject[:ignore_errors].should eq ['StandardError']
412
+ context "when root_path is set" do
413
+ it "returns returns the project log location" do
414
+ expect(subject).to eq File.join(config.root_path, 'appsignal.log')
415
+ end
416
+
417
+ it "prints no warning" do
418
+ subject
419
+ expect(stdout.string).to be_empty
420
+ end
201
421
  end
202
422
  end
203
- end
204
423
 
205
- context "when there is a config file without the current env" do
206
- let(:config) { project_fixture_config('nonsense') }
207
-
208
- it "should log an error" do
209
- Logger.any_instance.should_receive(:error).with(
210
- "Not loading from config file: config for 'nonsense' not found"
211
- ).once
212
- Logger.any_instance.should_receive(:error).with(
213
- "Push api key not set after loading config"
214
- ).once
215
- subject
424
+ context "when path does not exist" do
425
+ let(:log_path) { '/non-existing' }
426
+
427
+ include_examples '#log_file_path: tmp path'
216
428
  end
217
429
 
218
- its(:valid?) { should be_false }
219
- its(:active?) { should be_false }
220
- end
430
+ context "when path is not writable" do
431
+ let(:log_path) { File.join(tmp_dir, 'not-writable-path') }
432
+ before { FileUtils.mkdir_p(log_path, :mode => 0555) }
433
+ after { FileUtils.rm_rf(log_path) }
221
434
 
222
- context "when there is no config file" do
223
- let(:initial_config) { {} }
224
- let(:config) { Appsignal::Config.new('/tmp', 'production', initial_config) }
435
+ include_examples '#log_file_path: tmp path'
436
+ end
225
437
 
226
- its(:valid?) { should be_false }
227
- its(:active?) { should be_false }
228
- its(:log_file_path) { should end_with('/tmp/appsignal.log') }
438
+ context "when path is a symlink" do
439
+ context "when linked path does not exist" do
440
+ let(:real_path) { File.join(tmp_dir, 'real-path') }
441
+ let(:log_path) { File.join(tmp_dir, 'symlink-path') }
442
+ before { File.symlink(real_path, log_path) }
443
+ after { FileUtils.rm(log_path) }
229
444
 
230
- context "with valid config in the environment" do
231
- before do
232
- ENV['APPSIGNAL_PUSH_API_KEY'] = 'aaa-bbb-ccc'
233
- ENV['APPSIGNAL_ACTIVE'] = 'true'
234
- ENV['APPSIGNAL_APP_NAME'] = 'App name'
235
- ENV['APPSIGNAL_DEBUG'] = 'true'
236
- ENV['APPSIGNAL_IGNORE_ACTIONS'] = 'action1,action2'
445
+ include_examples '#log_file_path: tmp path'
237
446
  end
238
447
 
239
- its(:valid?) { should be_true }
240
- its(:active?) { should be_true }
448
+ context "when linked path exists" do
449
+ context "when linked path is not writable" do
450
+ let(:real_path) { File.join(tmp_dir, 'real-path') }
451
+ let(:log_path) { File.join(tmp_dir, 'symlink-path') }
452
+ before do
453
+ FileUtils.mkdir_p(real_path)
454
+ FileUtils.chmod(0444, real_path)
455
+ File.symlink(real_path, log_path)
456
+ end
457
+ after do
458
+ FileUtils.rm_rf(real_path)
459
+ FileUtils.rm(log_path)
460
+ end
461
+
462
+ include_examples '#log_file_path: tmp path'
463
+ end
241
464
 
242
- its([:push_api_key]) { should eq 'aaa-bbb-ccc' }
243
- its([:active]) { should eq true }
244
- its([:name]) { should eq 'App name' }
245
- its([:debug]) { should eq true }
246
- its([:ignore_actions]) { should eq ['action1', 'action2'] }
465
+ context "when linked path is writable" do
466
+ let(:real_path) { File.join(tmp_dir, 'real-path') }
467
+ let(:log_path) { File.join(tmp_dir, 'symlink-path') }
468
+ before do
469
+ FileUtils.mkdir_p(real_path)
470
+ File.symlink(real_path, log_path)
471
+ end
472
+ after do
473
+ FileUtils.rm_rf(real_path)
474
+ FileUtils.rm(log_path)
475
+ end
476
+
477
+ it "returns real path of log path" do
478
+ expect(subject).to eq(File.join(real_path, 'appsignal.log'))
479
+ end
480
+ end
481
+ end
247
482
  end
248
483
  end
249
-
250
- context "when a nil root path is passed" do
251
- let(:initial_config) { {} }
252
- let(:config) { Appsignal::Config.new(nil, 'production', initial_config) }
253
-
254
- its(:valid?) { should be_false }
255
- its(:active?) { should be_false }
256
- end
257
484
  end