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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. checksums.yaml +5 -5
  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/config.rb +5 -2
  36. data/lib/appsignal/event_formatter/action_view/render_formatter.rb +10 -8
  37. data/lib/appsignal/helpers/instrumentation.rb +18 -9
  38. data/lib/appsignal/helpers/metrics.rb +0 -1
  39. data/lib/appsignal/hooks.rb +3 -1
  40. data/lib/appsignal/hooks/active_support_notifications.rb +2 -5
  41. data/lib/appsignal/hooks/puma.rb +15 -13
  42. data/lib/appsignal/hooks/sequel.rb +1 -1
  43. data/lib/appsignal/hooks/sidekiq.rb +33 -8
  44. data/lib/appsignal/integrations/que.rb +9 -8
  45. data/lib/appsignal/minutely.rb +38 -19
  46. data/lib/appsignal/transaction.rb +5 -0
  47. data/lib/appsignal/utils/rails_helper.rb +4 -0
  48. data/lib/appsignal/version.rb +1 -1
  49. data/lib/puma/plugin/appsignal.rb +26 -0
  50. data/spec/lib/appsignal/cli/diagnose/utils_spec.rb +40 -0
  51. data/spec/lib/appsignal/cli/install_spec.rb +51 -7
  52. data/spec/lib/appsignal/cli/notify_of_deploy_spec.rb +10 -0
  53. data/spec/lib/appsignal/config_spec.rb +13 -8
  54. data/spec/lib/appsignal/event_formatter/action_view/render_formatter_spec.rb +38 -28
  55. data/spec/lib/appsignal/hooks/active_support_notifications_spec.rb +104 -25
  56. data/spec/lib/appsignal/hooks/puma_spec.rb +69 -39
  57. data/spec/lib/appsignal/hooks/sidekiq_spec.rb +65 -3
  58. data/spec/lib/appsignal/hooks_spec.rb +4 -0
  59. data/spec/lib/appsignal/minutely_spec.rb +150 -88
  60. data/spec/lib/appsignal/transaction_spec.rb +27 -4
  61. data/spec/lib/appsignal_spec.rb +62 -11
  62. data/spec/lib/puma/appsignal_spec.rb +91 -0
  63. data/spec/support/{project_fixture → fixtures/projects/valid}/config/application.rb +0 -0
  64. data/spec/support/{project_fixture → fixtures/projects/valid}/config/appsignal.yml +0 -0
  65. data/spec/support/{project_fixture → fixtures/projects/valid}/config/environments/development.rb +0 -0
  66. data/spec/support/{project_fixture → fixtures/projects/valid}/config/environments/production.rb +0 -0
  67. data/spec/support/{project_fixture → fixtures/projects/valid}/config/environments/test.rb +0 -0
  68. data/spec/support/{project_fixture → fixtures/projects/valid}/log/.gitkeep +0 -0
  69. data/spec/support/helpers/config_helpers.rb +1 -1
  70. data/spec/support/helpers/wait_for_helper.rb +28 -0
  71. data/spec/support/mocks/mock_probe.rb +11 -0
  72. metadata +38 -30
  73. data/spec/support/fixtures/containers/cgroups/docker +0 -14
  74. data/spec/support/fixtures/containers/cgroups/docker_systemd +0 -8
  75. data/spec/support/fixtures/containers/cgroups/lxc +0 -10
  76. data/spec/support/fixtures/containers/cgroups/no_permission +0 -0
  77. data/spec/support/fixtures/containers/cgroups/none +0 -1
@@ -11,6 +11,10 @@ module Appsignal
11
11
  rails_class.parent_name
12
12
  end
13
13
  end
14
+
15
+ def self.application_config_path
16
+ File.expand_path(File.join(Dir.pwd, "config/application.rb"))
17
+ end
14
18
  end
15
19
  end
16
20
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Appsignal
4
- VERSION = "2.9.2.alpha.1".freeze
4
+ VERSION = "2.9.18.beta.2".freeze
5
5
  end
@@ -0,0 +1,26 @@
1
+ APPSIGNAL_PUMA_PLUGIN_LOADED = true
2
+
3
+ # AppSignal Puma plugin
4
+ #
5
+ # This plugin ensures the minutely probe thread is started with the Puma
6
+ # minutely probe in the Puma master process.
7
+ #
8
+ # The constant {APPSIGNAL_PUMA_PLUGIN_LOADED} is here to mark the Plugin as
9
+ # loaded by the rest of the AppSignal gem. This ensures that the Puma minutely
10
+ # probe is not also started in every Puma workers, which was the old behavior.
11
+ # See {Appsignal::Hooks::PumaHook#install} for more information.
12
+ #
13
+ # For even more information:
14
+ # https://docs.appsignal.com/ruby/integrations/puma.html
15
+ Puma::Plugin.create do
16
+ def start(launcher = nil)
17
+ launcher.events.on_booted do
18
+ require "appsignal"
19
+ if ::Puma.respond_to?(:stats)
20
+ Appsignal::Minutely.probes.register :puma, Appsignal::Hooks::PumaProbe
21
+ end
22
+ Appsignal.start
23
+ Appsignal.start_logger
24
+ end
25
+ end
26
+ end
@@ -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
 
@@ -134,6 +134,7 @@ describe Appsignal::Config do
134
134
  :ca_file_path => File.join(resources_dir, "cacert.pem"),
135
135
  :dns_servers => [],
136
136
  :files_world_accessible => true,
137
+ :transaction_debug_mode => false,
137
138
  :revision => "v2.5.1",
138
139
  :request_headers => []
139
140
  )
@@ -202,14 +203,16 @@ describe Appsignal::Config do
202
203
  context "with a config file" do
203
204
  let(:config) { project_fixture_config("production") }
204
205
 
205
- it "is not valid or active" do
206
- expect(config.valid?).to be_truthy
207
- expect(config.active?).to be_truthy
208
- end
206
+ context "with valid config" do
207
+ it "is valid and active" do
208
+ expect(config.valid?).to be_truthy
209
+ expect(config.active?).to be_truthy
210
+ end
209
211
 
210
- it "does not log an error" do
211
- expect_any_instance_of(Logger).to_not receive(:error)
212
- config
212
+ it "does not log an error" do
213
+ log = capture_logs { config }
214
+ expect(log).to_not contains_log(:error)
215
+ end
213
216
  end
214
217
 
215
218
  it "sets the file_config" do
@@ -471,13 +474,14 @@ describe Appsignal::Config do
471
474
  config[:filter_parameters] = %w[password confirm_password]
472
475
  config[:running_in_container] = false
473
476
  config[:dns_servers] = ["8.8.8.8", "8.8.4.4"]
477
+ config[:transaction_debug_mode] = true
474
478
  config[:revision] = "v2.5.1"
475
479
  config.write_to_environment
476
480
  end
477
481
 
478
482
  it "writes the current config to environment variables" do
479
483
  expect(ENV["_APPSIGNAL_ACTIVE"]).to eq "true"
480
- expect(ENV["_APPSIGNAL_APP_PATH"]).to end_with("spec/support/project_fixture")
484
+ expect(ENV["_APPSIGNAL_APP_PATH"]).to end_with("spec/support/fixtures/projects/valid")
481
485
  expect(ENV["_APPSIGNAL_AGENT_PATH"]).to end_with("/ext")
482
486
  expect(ENV["_APPSIGNAL_DEBUG_LOGGING"]).to eq "false"
483
487
  expect(ENV["_APPSIGNAL_LOG"]).to eq "stdout"
@@ -498,6 +502,7 @@ describe Appsignal::Config do
498
502
  expect(ENV["_APPSIGNAL_CA_FILE_PATH"]).to eq File.join(resources_dir, "cacert.pem")
499
503
  expect(ENV["_APPSIGNAL_DNS_SERVERS"]).to eq "8.8.8.8,8.8.4.4"
500
504
  expect(ENV["_APPSIGNAL_FILES_WORLD_ACCESSIBLE"]).to eq "true"
505
+ expect(ENV["_APPSIGNAL_TRANSACTION_DEBUG_MODE"]).to eq "true"
501
506
  expect(ENV["_APP_REVISION"]).to eq "v2.5.1"
502
507
  expect(ENV).to_not have_key("_APPSIGNAL_WORKING_DIR_PATH")
503
508
  expect(ENV).to_not have_key("_APPSIGNAL_WORKING_DIRECTORY_PATH")
@@ -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