appsignal 2.9.2.alpha.1 → 2.9.18.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +31 -0
  3. data/.github/ISSUE_TEMPLATE/chore.md +14 -0
  4. data/.gitignore +1 -2
  5. data/.rubocop.yml +3 -0
  6. data/.travis.yml +25 -27
  7. data/CHANGELOG.md +632 -535
  8. data/README.md +8 -3
  9. data/Rakefile +118 -122
  10. data/SUPPORT.md +16 -0
  11. data/appsignal.gemspec +14 -4
  12. data/build_matrix.yml +16 -8
  13. data/ext/Rakefile +2 -3
  14. data/ext/agent.yml +40 -37
  15. data/ext/base.rb +37 -14
  16. data/ext/extconf.rb +3 -4
  17. data/gemfiles/capistrano2.gemfile +5 -0
  18. data/gemfiles/capistrano3.gemfile +5 -0
  19. data/gemfiles/grape.gemfile +5 -0
  20. data/gemfiles/no_dependencies.gemfile +5 -0
  21. data/gemfiles/padrino.gemfile +5 -0
  22. data/gemfiles/que.gemfile +5 -0
  23. data/gemfiles/que_beta.gemfile +10 -0
  24. data/gemfiles/rails-3.2.gemfile +5 -0
  25. data/gemfiles/rails-4.0.gemfile +5 -0
  26. data/gemfiles/rails-4.1.gemfile +5 -0
  27. data/gemfiles/rails-4.2.gemfile +5 -0
  28. data/gemfiles/rails-6.0.gemfile +1 -1
  29. data/gemfiles/resque.gemfile +5 -0
  30. data/lib/appsignal.rb +1 -4
  31. data/lib/appsignal/cli/demo.rb +5 -2
  32. data/lib/appsignal/cli/diagnose/utils.rb +2 -0
  33. data/lib/appsignal/cli/install.rb +34 -10
  34. data/lib/appsignal/cli/notify_of_deploy.rb +10 -0
  35. data/lib/appsignal/event_formatter/action_view/render_formatter.rb +10 -8
  36. data/lib/appsignal/helpers/instrumentation.rb +18 -9
  37. data/lib/appsignal/helpers/metrics.rb +0 -1
  38. data/lib/appsignal/hooks.rb +3 -1
  39. data/lib/appsignal/hooks/active_support_notifications.rb +2 -5
  40. data/lib/appsignal/hooks/puma.rb +15 -13
  41. data/lib/appsignal/hooks/sequel.rb +1 -1
  42. data/lib/appsignal/hooks/sidekiq.rb +33 -8
  43. data/lib/appsignal/integrations/que.rb +9 -8
  44. data/lib/appsignal/minutely.rb +38 -19
  45. data/lib/appsignal/transaction.rb +5 -0
  46. data/lib/appsignal/utils/rails_helper.rb +4 -0
  47. data/lib/appsignal/version.rb +1 -1
  48. data/lib/puma/plugin/appsignal.rb +26 -0
  49. data/spec/lib/appsignal/cli/diagnose/utils_spec.rb +40 -0
  50. data/spec/lib/appsignal/cli/install_spec.rb +51 -7
  51. data/spec/lib/appsignal/cli/notify_of_deploy_spec.rb +10 -0
  52. data/spec/lib/appsignal/config_spec.rb +10 -8
  53. data/spec/lib/appsignal/event_formatter/action_view/render_formatter_spec.rb +38 -28
  54. data/spec/lib/appsignal/hooks/active_support_notifications_spec.rb +104 -25
  55. data/spec/lib/appsignal/hooks/puma_spec.rb +69 -39
  56. data/spec/lib/appsignal/hooks/sidekiq_spec.rb +65 -3
  57. data/spec/lib/appsignal/hooks_spec.rb +4 -0
  58. data/spec/lib/appsignal/minutely_spec.rb +150 -88
  59. data/spec/lib/appsignal/transaction_spec.rb +27 -4
  60. data/spec/lib/appsignal_spec.rb +62 -11
  61. data/spec/lib/puma/appsignal_spec.rb +91 -0
  62. data/spec/support/{project_fixture → fixtures/projects/valid}/config/application.rb +0 -0
  63. data/spec/support/{project_fixture → fixtures/projects/valid}/config/appsignal.yml +0 -0
  64. data/spec/support/{project_fixture → fixtures/projects/valid}/config/environments/development.rb +0 -0
  65. data/spec/support/{project_fixture → fixtures/projects/valid}/config/environments/production.rb +0 -0
  66. data/spec/support/{project_fixture → fixtures/projects/valid}/config/environments/test.rb +0 -0
  67. data/spec/support/{project_fixture → fixtures/projects/valid}/log/.gitkeep +0 -0
  68. data/spec/support/helpers/config_helpers.rb +1 -1
  69. data/spec/support/helpers/wait_for_helper.rb +28 -0
  70. data/spec/support/mocks/mock_probe.rb +11 -0
  71. metadata +37 -30
  72. data/spec/support/fixtures/containers/cgroups/docker +0 -14
  73. data/spec/support/fixtures/containers/cgroups/docker_systemd +0 -8
  74. data/spec/support/fixtures/containers/cgroups/lxc +0 -10
  75. data/spec/support/fixtures/containers/cgroups/no_permission +0 -0
  76. data/spec/support/fixtures/containers/cgroups/none +0 -1
@@ -1,6 +1,46 @@
1
1
  require "appsignal/cli/diagnose/utils"
2
2
 
3
3
  describe Appsignal::CLI::Diagnose::Utils do
4
+ describe ".username_for_uid" do
5
+ subject { described_class.username_for_uid(uid) }
6
+
7
+ context "when user with id exists" do
8
+ let(:uid) { 0 }
9
+
10
+ it "returns username" do
11
+ is_expected.to be_kind_of(String)
12
+ end
13
+ end
14
+
15
+ context "when user with id does not exist" do
16
+ let(:uid) { -1 }
17
+
18
+ it "returns nil" do
19
+ is_expected.to be_nil
20
+ end
21
+ end
22
+ end
23
+
24
+ describe ".group_for_gid" do
25
+ subject { described_class.group_for_gid(uid) }
26
+
27
+ context "when group with id exists" do
28
+ let(:uid) { 0 }
29
+
30
+ it "returns group name" do
31
+ is_expected.to be_kind_of(String)
32
+ end
33
+ end
34
+
35
+ context "when group with id does not exist" do
36
+ let(:uid) { -3 }
37
+
38
+ it "returns nil" do
39
+ is_expected.to be_nil
40
+ end
41
+ end
42
+ end
43
+
4
44
  describe ".read_file_content" do
5
45
  let(:path) { File.join(spec_system_tmp_dir, "test_file.txt") }
6
46
  let(:bytes_to_read) { 100 }
@@ -16,6 +16,10 @@ describe Appsignal::CLI::Install do
16
16
  allow(described_class).to receive(:press_any_key)
17
17
  allow(Appsignal::Demo).to receive(:transmit).and_return(true)
18
18
  end
19
+ after do
20
+ FileUtils.rm_rf(tmp_dir)
21
+ FileUtils.mkdir_p(tmp_dir)
22
+ end
19
23
  around do |example|
20
24
  original_stdin = $stdin
21
25
  $stdin = StringIO.new
@@ -157,16 +161,10 @@ describe Appsignal::CLI::Install do
157
161
  shared_examples "capistrano install" do
158
162
  let(:capfile) { File.join(tmp_dir, "Capfile") }
159
163
  before do
160
- FileUtils.mkdir_p(tmp_dir)
161
-
162
164
  enter_app_name "foo"
163
165
  add_cli_input "n"
164
166
  choose_environment_config
165
167
  end
166
- after do
167
- FileUtils.rm_rf(tmp_dir)
168
- FileUtils.mkdir_p(tmp_dir)
169
- end
170
168
 
171
169
  context "without Capfile" do
172
170
  it "does nothing" do
@@ -260,7 +258,6 @@ describe Appsignal::CLI::Install do
260
258
  FileUtils.touch(File.join(environments_dir, "development.rb"))
261
259
  FileUtils.touch(File.join(environments_dir, "staging.rb"))
262
260
  FileUtils.touch(File.join(environments_dir, "production.rb"))
263
- enter_app_name app_name
264
261
  end
265
262
 
266
263
  describe "environments" do
@@ -410,6 +407,53 @@ describe Appsignal::CLI::Install do
410
407
  end
411
408
  end
412
409
  end
410
+
411
+ context "when there is no Rails application.rb file" do
412
+ before do
413
+ # Do not detect it as another framework for testing
414
+ allow(described_class).to receive(:framework_available?).and_call_original
415
+ allow(described_class).to receive(:framework_available?).with("sinatra").and_return(false)
416
+
417
+ File.delete(File.join(config_dir, "application.rb"))
418
+ expect(File.exist?(File.join(config_dir, "application.rb"))).to eql(false)
419
+ end
420
+
421
+ it "fails the installation" do
422
+ run
423
+
424
+ expect(output).to include("We could not detect which framework you are using.")
425
+ expect(output).to_not include("Installing for Ruby on Rails")
426
+ expect(output).to include_complete_install
427
+
428
+ expect(File.exist?(config_file_path)).to be(false)
429
+ end
430
+ end
431
+
432
+ context "when failed to load the Rails application.rb file" do
433
+ before do
434
+ File.open(File.join(config_dir, "application.rb"), "w") do |file|
435
+ file.write("I am invalid code")
436
+ end
437
+ end
438
+
439
+ it "prompts the user to fill in an app name" do
440
+ enter_app_name app_name
441
+ choose_config_file
442
+ run
443
+
444
+ expect(output).to include("Installing for Ruby on Rails")
445
+ expect(output).to include("Unable to automatically detect your Rails app's name.")
446
+ expect(output).to include("Choose your app's display name for AppSignal.com:")
447
+ expect(output).to include_file_config
448
+ expect(output).to include_complete_install
449
+
450
+ expect(config_file).to configure_app_name(app_name)
451
+ expect(config_file).to configure_push_api_key(push_api_key)
452
+ expect(config_file).to configure_environment("development")
453
+ expect(config_file).to configure_environment("staging")
454
+ expect(config_file).to configure_environment("production")
455
+ end
456
+ end
413
457
  end
414
458
  end
415
459
 
@@ -116,6 +116,9 @@ describe Appsignal::CLI::NotifyOfDeploy do
116
116
 
117
117
  context "with required options" do
118
118
  let(:options) { { :environment => "production", :revision => "aaaaa", :user => "thijs" } }
119
+ let(:log_stream) { std_stream }
120
+ let(:log) { log_contents(log_stream) }
121
+ before { Appsignal.logger = test_logger(log_stream) }
119
122
 
120
123
  it "notifies of a deploy" do
121
124
  run
@@ -124,6 +127,13 @@ describe Appsignal::CLI::NotifyOfDeploy do
124
127
  expect(output).to include_deploy_notification_with(options)
125
128
  end
126
129
 
130
+ it "prints a deprecation message" do
131
+ run
132
+ deprecation_message = "This command (appsignal notify_of_deploy) has been deprecated"
133
+ expect(output).to include("appsignal WARNING: #{deprecation_message}")
134
+ expect(log).to contains_log :warn, deprecation_message
135
+ end
136
+
127
137
  context "with no app name configured" do
128
138
  before { ENV["APPSIGNAL_APP_NAME"] = "" }
129
139
 
@@ -202,14 +202,16 @@ describe Appsignal::Config do
202
202
  context "with a config file" do
203
203
  let(:config) { project_fixture_config("production") }
204
204
 
205
- it "is not valid or active" do
206
- expect(config.valid?).to be_truthy
207
- expect(config.active?).to be_truthy
208
- end
205
+ context "with valid config" do
206
+ it "is valid and active" do
207
+ expect(config.valid?).to be_truthy
208
+ expect(config.active?).to be_truthy
209
+ end
209
210
 
210
- it "does not log an error" do
211
- expect_any_instance_of(Logger).to_not receive(:error)
212
- config
211
+ it "does not log an error" do
212
+ log = capture_logs { config }
213
+ expect(log).to_not contains_log(:error)
214
+ end
213
215
  end
214
216
 
215
217
  it "sets the file_config" do
@@ -477,7 +479,7 @@ describe Appsignal::Config do
477
479
 
478
480
  it "writes the current config to environment variables" do
479
481
  expect(ENV["_APPSIGNAL_ACTIVE"]).to eq "true"
480
- expect(ENV["_APPSIGNAL_APP_PATH"]).to end_with("spec/support/project_fixture")
482
+ expect(ENV["_APPSIGNAL_APP_PATH"]).to end_with("spec/support/fixtures/projects/valid")
481
483
  expect(ENV["_APPSIGNAL_AGENT_PATH"]).to end_with("/ext")
482
484
  expect(ENV["_APPSIGNAL_DEBUG_LOGGING"]).to eq "false"
483
485
  expect(ENV["_APPSIGNAL_LOG"]).to eq "stdout"
@@ -1,43 +1,53 @@
1
- if DependencyHelper.rails_present?
2
- require "action_view"
1
+ describe Appsignal::EventFormatter::ActionView::RenderFormatter do
2
+ let(:klass) { Appsignal::EventFormatter::ActionView::RenderFormatter }
3
3
 
4
- describe Appsignal::EventFormatter::ActionView::RenderFormatter do
5
- before { allow(Rails.root).to receive(:to_s).and_return("/var/www/app/20130101") }
6
- let(:klass) { Appsignal::EventFormatter::ActionView::RenderFormatter }
7
- let(:formatter) { klass.new }
4
+ if DependencyHelper.rails_present?
5
+ require "action_view"
8
6
 
9
- it "should register render_partial.action_view and render_template.action_view" do
10
- expect(Appsignal::EventFormatter.registered?("render_partial.action_view", klass)).to be_truthy
11
- expect(Appsignal::EventFormatter.registered?("render_template.action_view", klass)).to be_truthy
12
- end
7
+ context "when in a Rails app" do
8
+ let(:formatter) { klass.new }
9
+ before { allow(Rails.root).to receive(:to_s).and_return("/var/www/app/20130101") }
10
+
11
+ it "registers render_partial.action_view and render_template.action_view" do
12
+ expect(Appsignal::EventFormatter.registered?("render_partial.action_view", klass)).to be_truthy
13
+ expect(Appsignal::EventFormatter.registered?("render_template.action_view", klass)).to be_truthy
14
+ end
13
15
 
14
- describe "#root_path" do
15
- subject { formatter.root_path }
16
+ describe "#root_path" do
17
+ subject { formatter.root_path }
16
18
 
17
- it "returns Rails root path" do
18
- is_expected.to eq "/var/www/app/20130101/"
19
+ it "returns Rails root path" do
20
+ is_expected.to eq "/var/www/app/20130101/"
21
+ end
19
22
  end
20
- end
21
23
 
22
- describe "#format" do
23
- subject { formatter.format(payload) }
24
+ describe "#format" do
25
+ subject { formatter.format(payload) }
24
26
 
25
- context "with an identifier" do
26
- let(:payload) { { :identifier => "/var/www/app/20130101/app/views/home/index/html.erb" } }
27
+ context "with an identifier" do
28
+ let(:payload) { { :identifier => "/var/www/app/20130101/app/views/home/index/html.erb" } }
27
29
 
28
- it { is_expected.to eq ["app/views/home/index/html.erb", nil] }
29
- end
30
+ it { is_expected.to eq ["app/views/home/index/html.erb", nil] }
31
+ end
30
32
 
31
- context "with a frozen identifier" do
32
- let(:payload) { { :identifier => "/var/www/app/20130101/app/views/home/index/html.erb".freeze } }
33
+ context "with a frozen identifier" do
34
+ let(:payload) { { :identifier => "/var/www/app/20130101/app/views/home/index/html.erb".freeze } }
33
35
 
34
- it { is_expected.to eq ["app/views/home/index/html.erb", nil] }
35
- end
36
+ it { is_expected.to eq ["app/views/home/index/html.erb", nil] }
37
+ end
36
38
 
37
- context "without an identifier" do
38
- let(:payload) { {} }
39
+ context "without an identifier" do
40
+ let(:payload) { {} }
39
41
 
40
- it { is_expected.to be_nil }
42
+ it { is_expected.to be_nil }
43
+ end
44
+ end
45
+ end
46
+ else
47
+ context "when not in a Rails app" do
48
+ it "does not register the event formatter" do
49
+ expect(Appsignal::EventFormatter.registered?("render_partial.action_view", klass)).to be_falsy
50
+ expect(Appsignal::EventFormatter.registered?("render_template.action_view", klass)).to be_falsy
41
51
  end
42
52
  end
43
53
  end
@@ -2,12 +2,14 @@ describe Appsignal::Hooks::ActiveSupportNotificationsHook do
2
2
  if active_support_present?
3
3
  let(:notifier) { ActiveSupport::Notifications::Fanout.new }
4
4
  let(:as) { ActiveSupport::Notifications }
5
+ let!(:transaction) do
6
+ Appsignal::Transaction.create("uuid", Appsignal::Transaction::HTTP_REQUEST, "test")
7
+ end
5
8
  before :context do
6
9
  start_agent
7
10
  end
8
11
  before do
9
12
  as.notifier = notifier
10
- Appsignal::Transaction.create("uuid", Appsignal::Transaction::HTTP_REQUEST, "test")
11
13
  end
12
14
 
13
15
  describe "#dependencies_present?" do
@@ -17,27 +19,71 @@ describe Appsignal::Hooks::ActiveSupportNotificationsHook do
17
19
  end
18
20
 
19
21
  it "instruments an ActiveSupport::Notifications.instrument event" do
20
- expect(Appsignal::Transaction.current).to receive(:start_event)
21
- .at_least(:once)
22
- expect(Appsignal::Transaction.current).to receive(:finish_event)
23
- .at_least(:once)
24
- .with("sql.active_record", nil, "SQL", 1)
25
-
26
22
  return_value = as.instrument("sql.active_record", :sql => "SQL") do
27
23
  "value"
28
24
  end
29
25
 
30
26
  expect(return_value).to eq "value"
27
+ expect(transaction.to_h["events"]).to match([
28
+ {
29
+ "allocation_count" => kind_of(Integer),
30
+ "body" => "SQL",
31
+ "body_format" => Appsignal::EventFormatter::SQL_BODY_FORMAT,
32
+ "child_allocation_count" => kind_of(Integer),
33
+ "child_duration" => kind_of(Float),
34
+ "child_gc_duration" => kind_of(Float),
35
+ "count" => 1,
36
+ "duration" => kind_of(Float),
37
+ "gc_duration" => kind_of(Float),
38
+ "name" => "sql.active_record",
39
+ "start" => kind_of(Float),
40
+ "title" => ""
41
+ }
42
+ ])
31
43
  end
32
44
 
33
- it "should convert non-string names to strings" do
34
- expect(Appsignal::Transaction.current).to receive(:start_event)
35
- .at_least(:once)
36
- expect(Appsignal::Transaction.current).to receive(:finish_event)
37
- .at_least(:once)
38
- .with("not_a_string", nil, nil, nil)
45
+ it "instruments an ActiveSupport::Notifications.instrument event with no registered formatter" do
46
+ return_value = as.instrument("no-registered.formatter", :key => "something") do
47
+ "value"
48
+ end
49
+
50
+ expect(return_value).to eq "value"
51
+ expect(transaction.to_h["events"]).to match([
52
+ {
53
+ "allocation_count" => kind_of(Integer),
54
+ "body" => "",
55
+ "body_format" => Appsignal::EventFormatter::DEFAULT,
56
+ "child_allocation_count" => kind_of(Integer),
57
+ "child_duration" => kind_of(Float),
58
+ "child_gc_duration" => kind_of(Float),
59
+ "count" => 1,
60
+ "duration" => kind_of(Float),
61
+ "gc_duration" => kind_of(Float),
62
+ "name" => "no-registered.formatter",
63
+ "start" => kind_of(Float),
64
+ "title" => ""
65
+ }
66
+ ])
67
+ end
39
68
 
69
+ it "converts non-string names to strings" do
40
70
  as.instrument(:not_a_string) {}
71
+ expect(transaction.to_h["events"]).to match([
72
+ {
73
+ "allocation_count" => kind_of(Integer),
74
+ "body" => "",
75
+ "body_format" => Appsignal::EventFormatter::DEFAULT,
76
+ "child_allocation_count" => kind_of(Integer),
77
+ "child_duration" => kind_of(Float),
78
+ "child_gc_duration" => kind_of(Float),
79
+ "count" => 1,
80
+ "duration" => kind_of(Float),
81
+ "gc_duration" => kind_of(Float),
82
+ "name" => "not_a_string",
83
+ "start" => kind_of(Float),
84
+ "title" => ""
85
+ }
86
+ ])
41
87
  end
42
88
 
43
89
  it "does not instrument events whose name starts with a bang" do
@@ -53,33 +99,66 @@ describe Appsignal::Hooks::ActiveSupportNotificationsHook do
53
99
 
54
100
  context "when an error is raised in an instrumented block" do
55
101
  it "instruments an ActiveSupport::Notifications.instrument event" do
56
- expect(Appsignal::Transaction.current).to receive(:start_event)
57
- .at_least(:once)
58
- expect(Appsignal::Transaction.current).to receive(:finish_event)
59
- .at_least(:once)
60
- .with("sql.active_record", nil, "SQL", 1)
61
-
62
102
  expect do
63
103
  as.instrument("sql.active_record", :sql => "SQL") do
64
104
  raise ExampleException, "foo"
65
105
  end
66
106
  end.to raise_error(ExampleException, "foo")
107
+
108
+ expect(transaction.to_h["events"]).to match([
109
+ {
110
+ "allocation_count" => kind_of(Integer),
111
+ "body" => "SQL",
112
+ "body_format" => Appsignal::EventFormatter::SQL_BODY_FORMAT,
113
+ "child_allocation_count" => kind_of(Integer),
114
+ "child_duration" => kind_of(Float),
115
+ "child_gc_duration" => kind_of(Float),
116
+ "count" => 1,
117
+ "duration" => kind_of(Float),
118
+ "gc_duration" => kind_of(Float),
119
+ "name" => "sql.active_record",
120
+ "start" => kind_of(Float),
121
+ "title" => ""
122
+ }
123
+ ])
67
124
  end
68
125
  end
69
126
 
70
127
  context "when a message is thrown in an instrumented block" do
71
128
  it "instruments an ActiveSupport::Notifications.instrument event" do
72
- expect(Appsignal::Transaction.current).to receive(:start_event)
73
- .at_least(:once)
74
- expect(Appsignal::Transaction.current).to receive(:finish_event)
75
- .at_least(:once)
76
- .with("sql.active_record", nil, "SQL", 1)
77
-
78
129
  expect do
79
130
  as.instrument("sql.active_record", :sql => "SQL") do
80
131
  throw :foo
81
132
  end
82
133
  end.to throw_symbol(:foo)
134
+
135
+ expect(transaction.to_h["events"]).to match([
136
+ {
137
+ "allocation_count" => kind_of(Integer),
138
+ "body" => "SQL",
139
+ "body_format" => Appsignal::EventFormatter::SQL_BODY_FORMAT,
140
+ "child_allocation_count" => kind_of(Integer),
141
+ "child_duration" => kind_of(Float),
142
+ "child_gc_duration" => kind_of(Float),
143
+ "count" => 1,
144
+ "duration" => kind_of(Float),
145
+ "gc_duration" => kind_of(Float),
146
+ "name" => "sql.active_record",
147
+ "start" => kind_of(Float),
148
+ "title" => ""
149
+ }
150
+ ])
151
+ end
152
+ end
153
+
154
+ context "when a transaction is completed in an instrumented block" do
155
+ it "does not complete the ActiveSupport::Notifications.instrument event" do
156
+ expect(transaction).to receive(:complete)
157
+ as.instrument("sql.active_record", :sql => "SQL") do
158
+ Appsignal::Transaction.complete_current!
159
+ end
160
+
161
+ expect(transaction.to_h["events"]).to match([])
83
162
  end
84
163
  end
85
164
  else