appsignal 3.1.1 → 3.1.4

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.
@@ -35,10 +35,24 @@ module Appsignal
35
35
  end
36
36
  end
37
37
 
38
+ # Returns currently active transaction or a {NilTransaction} if none is
39
+ # active.
40
+ #
41
+ # @see .current?
42
+ # @return [Boolean]
38
43
  def current
39
44
  Thread.current[:appsignal_transaction] || NilTransaction.new
40
45
  end
41
46
 
47
+ # Returns if any transaction is currently active or not. A
48
+ # {NilTransaction} is not considered an active transaction.
49
+ #
50
+ # @see .current
51
+ # @return [Boolean]
52
+ def current?
53
+ current && !current.nil_transaction?
54
+ end
55
+
42
56
  def complete_current!
43
57
  current.complete
44
58
  rescue => e
@@ -52,11 +66,6 @@ module Appsignal
52
66
  def clear_current_transaction!
53
67
  Thread.current[:appsignal_transaction] = nil
54
68
  end
55
-
56
- def garbage_collection_profiler
57
- @garbage_collection_profiler ||=
58
- Appsignal.config[:enable_gc_instrumentation] ? Appsignal::GarbageCollectionProfiler.new : NilGarbageCollectionProfiler.new
59
- end
60
69
  end
61
70
 
62
71
  attr_reader :ext, :transaction_id, :action, :namespace, :request, :paused, :tags, :options, :discarded, :breadcrumbs
@@ -89,7 +98,7 @@ module Appsignal
89
98
  @ext = Appsignal::Extension.start_transaction(
90
99
  @transaction_id,
91
100
  @namespace,
92
- self.class.garbage_collection_profiler.total_time
101
+ 0
93
102
  ) || Appsignal::Extension::MockTransaction.new
94
103
  end
95
104
 
@@ -103,9 +112,7 @@ module Appsignal
103
112
  "because it was manually discarded."
104
113
  return
105
114
  end
106
- if @ext.finish(self.class.garbage_collection_profiler.total_time)
107
- sample_data
108
- end
115
+ sample_data if @ext.finish(0)
109
116
  @ext.complete
110
117
  end
111
118
 
@@ -341,7 +348,7 @@ module Appsignal
341
348
 
342
349
  def start_event
343
350
  return if paused?
344
- @ext.start_event(self.class.garbage_collection_profiler.total_time)
351
+ @ext.start_event(0)
345
352
  end
346
353
 
347
354
  def finish_event(name, title, body, body_format = Appsignal::EventFormatter::DEFAULT)
@@ -351,7 +358,7 @@ module Appsignal
351
358
  title || BLANK,
352
359
  body || BLANK,
353
360
  body_format || Appsignal::EventFormatter::DEFAULT,
354
- self.class.garbage_collection_profiler.total_time
361
+ 0
355
362
  )
356
363
  end
357
364
 
@@ -363,7 +370,7 @@ module Appsignal
363
370
  body || BLANK,
364
371
  body_format || Appsignal::EventFormatter::DEFAULT,
365
372
  duration,
366
- self.class.garbage_collection_profiler.total_time
373
+ 0
367
374
  )
368
375
  end
369
376
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Appsignal
4
- VERSION = "3.1.1".freeze
4
+ VERSION = "3.1.4".freeze
5
5
  end
data/lib/appsignal.rb CHANGED
@@ -115,11 +115,6 @@ module Appsignal
115
115
  Appsignal::Environment.report_enabled("allocation_tracking")
116
116
  end
117
117
 
118
- if config[:enable_gc_instrumentation]
119
- GC::Profiler.enable
120
- Appsignal::Environment.report_enabled("gc_instrumentation")
121
- end
122
-
123
118
  Appsignal::Minutely.start if config[:enable_minutely_probes]
124
119
 
125
120
  collect_environment_metadata
@@ -298,7 +293,7 @@ require "appsignal/hooks"
298
293
  require "appsignal/probes"
299
294
  require "appsignal/marker"
300
295
  require "appsignal/minutely"
301
- require "appsignal/garbage_collection_profiler"
296
+ require "appsignal/garbage_collection"
302
297
  require "appsignal/integrations/railtie" if defined?(::Rails)
303
298
  require "appsignal/transaction"
304
299
  require "appsignal/version"
@@ -156,7 +156,6 @@ describe Appsignal::Config do
156
156
  :debug => false,
157
157
  :dns_servers => [],
158
158
  :enable_allocation_tracking => true,
159
- :enable_gc_instrumentation => false,
160
159
  :enable_host_metrics => true,
161
160
  :enable_minutely_probes => true,
162
161
  :enable_statsd => true,
@@ -1,11 +1,33 @@
1
- describe Appsignal::GarbageCollectionProfiler do
1
+ describe Appsignal::GarbageCollection do
2
+ describe ".profiler" do
3
+ before do
4
+ # Unset the internal memoized variable to avoid state leaking
5
+ described_class.clear_profiler!
6
+ end
7
+
8
+ context "when GC instrumentation is disabled" do
9
+ it "returns the NilProfiler" do
10
+ expect(described_class.profiler).to be_a(Appsignal::GarbageCollection::NilProfiler)
11
+ end
12
+ end
13
+
14
+ context "when GC profiling is enabled" do
15
+ before { GC::Profiler.enable }
16
+ after { GC::Profiler.disable }
17
+
18
+ it "returns the Profiler" do
19
+ expect(described_class.profiler).to be_a(Appsignal::GarbageCollection::Profiler)
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ describe Appsignal::GarbageCollection::Profiler do
2
26
  let(:internal_profiler) { FakeGCProfiler.new }
3
27
  let(:profiler) { described_class.new }
4
28
 
5
29
  before do
6
- allow_any_instance_of(described_class)
7
- .to receive(:internal_profiler)
8
- .and_return(internal_profiler)
30
+ stub_const("GC::Profiler", internal_profiler)
9
31
  end
10
32
 
11
33
  context "on initialization" do
@@ -54,7 +76,7 @@ describe Appsignal::GarbageCollectionProfiler do
54
76
 
55
77
  2.times do
56
78
  threads << Thread.new do
57
- profiler = Appsignal::GarbageCollectionProfiler.new
79
+ profiler = Appsignal::GarbageCollection::Profiler.new
58
80
  results << profiler.total_time
59
81
  end
60
82
  end
@@ -65,7 +87,7 @@ describe Appsignal::GarbageCollectionProfiler do
65
87
  end
66
88
  end
67
89
 
68
- describe Appsignal::NilGarbageCollectionProfiler do
90
+ describe Appsignal::GarbageCollection::NilProfiler do
69
91
  let(:profiler) { described_class.new }
70
92
 
71
93
  describe "#total_time" do
@@ -19,7 +19,8 @@ end
19
19
 
20
20
  describe Appsignal::Probes::MriProbe do
21
21
  let(:appsignal_mock) { AppsignalMock.new(:hostname => hostname) }
22
- let(:probe) { described_class.new(appsignal_mock) }
22
+ let(:gc_profiler_mock) { instance_double("Appsignal::GarbageCollectionProfiler") }
23
+ let(:probe) { described_class.new(:appsignal => appsignal_mock, :gc_profiler => gc_profiler_mock) }
23
24
 
24
25
  describe ".dependencies_present?" do
25
26
  if DependencyHelper.running_jruby? || DependencyHelper.running_ruby_2_0?
@@ -36,6 +37,10 @@ describe Appsignal::Probes::MriProbe do
36
37
  unless DependencyHelper.running_jruby? || DependencyHelper.running_ruby_2_0?
37
38
  describe "#call" do
38
39
  let(:hostname) { nil }
40
+ before do
41
+ allow(gc_profiler_mock).to receive(:total_time)
42
+ allow(GC::Profiler).to receive(:enabled?).and_return(true)
43
+ end
39
44
 
40
45
  it "should track vm metrics" do
41
46
  probe.call
@@ -48,9 +53,56 @@ describe Appsignal::Probes::MriProbe do
48
53
  expect_gauge_value("thread_count")
49
54
  end
50
55
 
51
- it "tracks GC total time" do
56
+ it "tracks GC time between measurements" do
57
+ expect(gc_profiler_mock).to receive(:total_time).and_return(10, 15)
58
+ probe.call
52
59
  probe.call
53
- expect_gauge_value("gc_total_time")
60
+ expect_gauge_value("gc_time", 5)
61
+ end
62
+
63
+ context "when GC total time overflows" do
64
+ it "skips one report" do
65
+ expect(gc_profiler_mock).to receive(:total_time).and_return(10, 15, 0, 10)
66
+ probe.call # Normal call, create a cache
67
+ probe.call # Report delta value based on cached value
68
+ probe.call # The value overflows and reports no value. Then stores 0 in the cache
69
+ probe.call # Report new value based on cache of 0
70
+ expect_gauges([["gc_time", 5], ["gc_time", 10]])
71
+ end
72
+ end
73
+
74
+ context "when GC profiling is disabled" do
75
+ it "does not report a gc_time metric" do
76
+ allow(GC::Profiler).to receive(:enabled?).and_return(false)
77
+ expect(gc_profiler_mock).to_not receive(:total_time)
78
+ probe.call # Normal call, create a cache
79
+ probe.call # Report delta value based on cached value
80
+ metrics = appsignal_mock.gauges.map { |(key)| key }
81
+ expect(metrics).to_not include("gc_time")
82
+ end
83
+
84
+ it "does not report a gc_time metric while temporarily disabled" do
85
+ # While enabled
86
+ allow(GC::Profiler).to receive(:enabled?).and_return(true)
87
+ expect(gc_profiler_mock).to receive(:total_time).and_return(10, 15)
88
+ probe.call # Normal call, create a cache
89
+ probe.call # Report delta value based on cached value
90
+ expect_gauges([["gc_time", 5]])
91
+
92
+ # While disabled
93
+ allow(GC::Profiler).to receive(:enabled?).and_return(false)
94
+ probe.call # Call twice to make sure any caches resets wouldn't mess up the assertion
95
+ probe.call
96
+ # Does not include any newly reported metrics
97
+ expect_gauges([["gc_time", 5]])
98
+
99
+ # When enabled after being disabled for a while, it only reports the
100
+ # newly reported time since it was renabled
101
+ allow(GC::Profiler).to receive(:enabled?).and_return(true)
102
+ expect(gc_profiler_mock).to receive(:total_time).and_return(25)
103
+ probe.call
104
+ expect_gauges([["gc_time", 5], ["gc_time", 10]])
105
+ end
54
106
  end
55
107
 
56
108
  it "tracks GC run count" do
@@ -107,4 +159,15 @@ describe Appsignal::Probes::MriProbe do
107
159
  end
108
160
  end
109
161
  end
162
+
163
+ def expect_gauges(expected_metrics)
164
+ default_tags = { :hostname => Socket.gethostname }
165
+ keys = expected_metrics.map { |(key)| key }
166
+ metrics = expected_metrics.map do |metric|
167
+ key, value, tags = metric
168
+ [key, value, default_tags.merge(tags || {})]
169
+ end
170
+ found_gauges = appsignal_mock.gauges.select { |(key)| keys.include? key }
171
+ expect(found_gauges).to eq(metrics)
172
+ end
110
173
  end
@@ -3,20 +3,37 @@ if DependencyHelper.rails_present?
3
3
  end
4
4
 
5
5
  describe Appsignal::Rack::RailsInstrumentation do
6
- before :context do
6
+ let(:log) { StringIO.new }
7
+ before do
7
8
  start_agent
9
+ Appsignal.logger = test_logger(log)
8
10
  end
9
11
 
12
+ let(:params) do
13
+ {
14
+ "controller" => "blog_posts",
15
+ "action" => "show",
16
+ "id" => "1",
17
+ "my_custom_param" => "my custom secret",
18
+ "password" => "super secret"
19
+ }
20
+ end
21
+ let(:env_extra) { {} }
10
22
  let(:app) { double(:call => true) }
11
23
  let(:env) do
12
- http_request_env_with_data("action_dispatch.request_id" => "1").tap do |request|
13
- request["action_controller.instance"] = double(
24
+ http_request_env_with_data({
25
+ :params => params,
26
+ :with_queue_start => true,
27
+ "action_dispatch.request_id" => "1",
28
+ "action_dispatch.parameter_filter" => [:my_custom_param, :password],
29
+ "action_controller.instance" => double(
14
30
  :class => MockController,
15
31
  :action_name => "index"
16
32
  )
17
- end
33
+ }.merge(env_extra))
18
34
  end
19
35
  let(:middleware) { Appsignal::Rack::RailsInstrumentation.new(app, {}) }
36
+ around { |example| keep_transactions { example.run } }
20
37
 
21
38
  describe "#call" do
22
39
  before do
@@ -46,30 +63,62 @@ if DependencyHelper.rails_present?
46
63
  after { middleware.call(env) }
47
64
  end
48
65
 
49
- describe "#call_with_appsignal_monitoring", :error => false do
50
- it "should create a transaction" do
51
- expect(Appsignal::Transaction).to receive(:create).with(
52
- "1",
53
- Appsignal::Transaction::HTTP_REQUEST,
54
- kind_of(ActionDispatch::Request),
55
- :params_method => :filtered_parameters
56
- ).and_return(
57
- instance_double(
58
- "Appsignal::Transaction",
59
- :set_action => nil,
60
- :set_action_if_nil => nil,
61
- :set_http_or_background_queue_start => nil,
62
- :set_metadata => nil
66
+ describe "#call_with_appsignal_monitoring" do
67
+ def run
68
+ middleware.call(env)
69
+ end
70
+
71
+ it "calls the wrapped app" do
72
+ run
73
+ expect(app).to have_received(:call).with(env)
74
+ end
75
+
76
+ it "creates one transaction with metadata" do
77
+ run
78
+
79
+ expect(created_transactions.length).to eq(1)
80
+ transaction_hash = last_transaction.to_h
81
+ expect(transaction_hash).to include(
82
+ "namespace" => Appsignal::Transaction::HTTP_REQUEST,
83
+ "action" => "MockController#index",
84
+ "metadata" => hash_including(
85
+ "method" => "GET",
86
+ "path" => "/blog"
87
+ )
88
+ )
89
+ expect(last_transaction.ext.queue_start).to eq(
90
+ fixed_time * 1_000.0
91
+ )
92
+ end
93
+
94
+ it "filter parameters in Rails" do
95
+ run
96
+
97
+ transaction_hash = last_transaction.to_h
98
+ expect(transaction_hash).to include(
99
+ "sample_data" => hash_including(
100
+ "params" => params.merge(
101
+ "my_custom_param" => "[FILTERED]",
102
+ "password" => "[FILTERED]"
103
+ )
63
104
  )
64
105
  )
65
106
  end
66
107
 
67
- it "should call the app" do
68
- expect(app).to receive(:call).with(env)
108
+ context "with an invalid HTTP request method" do
109
+ let(:env_extra) { { :request_method => "FOO", "REQUEST_METHOD" => "FOO" } }
110
+
111
+ it "does not store the HTTP request method" do
112
+ run
113
+
114
+ transaction_hash = last_transaction.to_h
115
+ expect(transaction_hash["metadata"]).to_not have_key("method")
116
+ expect(log_contents(log)).to contains_log(:error, "Unable to report HTTP request method: '")
117
+ end
69
118
  end
70
119
 
71
- context "with an exception", :error => true do
72
- let(:error) { ExampleException }
120
+ context "with an exception" do
121
+ let(:error) { ExampleException.new("ExampleException message") }
73
122
  let(:app) do
74
123
  double.tap do |d|
75
124
  allow(d).to receive(:call).and_raise(error)
@@ -77,21 +126,16 @@ if DependencyHelper.rails_present?
77
126
  end
78
127
 
79
128
  it "records the exception" do
80
- expect_any_instance_of(Appsignal::Transaction).to receive(:set_error).with(error)
81
- end
82
- end
129
+ expect { run }.to raise_error(error)
83
130
 
84
- it "should set metadata" do
85
- expect_any_instance_of(Appsignal::Transaction).to receive(:set_metadata).twice
86
- end
87
-
88
- it "should set the action and queue start" do
89
- expect_any_instance_of(Appsignal::Transaction).to receive(:set_action_if_nil).with("MockController#index")
90
- expect_any_instance_of(Appsignal::Transaction).to receive(:set_http_or_background_queue_start)
131
+ transaction_hash = last_transaction.to_h
132
+ expect(transaction_hash["error"]).to include(
133
+ "name" => "ExampleException",
134
+ "message" => "ExampleException message",
135
+ "backtrace" => kind_of(String)
136
+ )
137
+ end
91
138
  end
92
-
93
- after(:error => false) { middleware.call(env) }
94
- after(:error => true) { expect { middleware.call(env) }.to raise_error(error) }
95
139
  end
96
140
 
97
141
  describe "#request_id" do
@@ -85,7 +85,9 @@ describe Appsignal::Transaction do
85
85
  end
86
86
 
87
87
  describe ".current" do
88
- subject { Appsignal::Transaction.current }
88
+ def current_transaction
89
+ Appsignal::Transaction.current
90
+ end
89
91
 
90
92
  context "when there is a current transaction" do
91
93
  let!(:transaction) do
@@ -93,13 +95,17 @@ describe Appsignal::Transaction do
93
95
  end
94
96
 
95
97
  it "reads :appsignal_transaction from the current Thread" do
96
- expect(subject).to eq Thread.current[:appsignal_transaction]
97
- expect(subject).to eq transaction
98
+ expect(current_transaction).to eq Thread.current[:appsignal_transaction]
99
+ expect(current_transaction).to eq transaction
98
100
  end
99
101
 
100
102
  it "is not a NilTransaction" do
101
- expect(subject.nil_transaction?).to eq false
102
- expect(subject).to be_a Appsignal::Transaction
103
+ expect(current_transaction.nil_transaction?).to eq false
104
+ expect(current_transaction).to be_a Appsignal::Transaction
105
+ end
106
+
107
+ it "returns true for current?" do
108
+ expect(Appsignal::Transaction.current?).to be(true)
103
109
  end
104
110
  end
105
111
 
@@ -109,8 +115,12 @@ describe Appsignal::Transaction do
109
115
  end
110
116
 
111
117
  it "returns a NilTransaction stub" do
112
- expect(subject.nil_transaction?).to eq true
113
- expect(subject).to be_a Appsignal::Transaction::NilTransaction
118
+ expect(current_transaction.nil_transaction?).to eq true
119
+ expect(current_transaction).to be_a Appsignal::Transaction::NilTransaction
120
+ end
121
+
122
+ it "returns false for current?" do
123
+ expect(Appsignal::Transaction.current?).to be(false)
114
124
  end
115
125
  end
116
126
  end
@@ -782,23 +792,6 @@ describe Appsignal::Transaction do
782
792
  end
783
793
  end
784
794
 
785
- describe "#garbage_collection_profiler" do
786
- before { Appsignal::Transaction.instance_variable_set(:@garbage_collection_profiler, nil) }
787
-
788
- it "returns the NilGarbageCollectionProfiler" do
789
- expect(Appsignal::Transaction.garbage_collection_profiler).to be_a(Appsignal::NilGarbageCollectionProfiler)
790
- end
791
-
792
- context "when gc profiling is enabled" do
793
- before { Appsignal.config.config_hash[:enable_gc_instrumentation] = true }
794
- after { Appsignal.config.config_hash[:enable_gc_instrumentation] = false }
795
-
796
- it "returns the GarbageCollectionProfiler" do
797
- expect(Appsignal::Transaction.garbage_collection_profiler).to be_a(Appsignal::GarbageCollectionProfiler)
798
- end
799
- end
800
- end
801
-
802
795
  describe "#start_event" do
803
796
  it "starts the event in the extension" do
804
797
  expect(transaction.ext).to receive(:start_event).with(0).and_call_original
@@ -817,11 +810,7 @@ describe Appsignal::Transaction do
817
810
  end
818
811
 
819
812
  describe "#finish_event" do
820
- let(:fake_gc_time) { 123 }
821
- before do
822
- expect(described_class.garbage_collection_profiler)
823
- .to receive(:total_time).at_least(:once).and_return(fake_gc_time)
824
- end
813
+ let(:fake_gc_time) { 0 }
825
814
 
826
815
  it "should finish the event in the extension" do
827
816
  expect(transaction.ext).to receive(:finish_event).with(
@@ -868,11 +857,7 @@ describe Appsignal::Transaction do
868
857
  end
869
858
 
870
859
  describe "#record_event" do
871
- let(:fake_gc_time) { 123 }
872
- before do
873
- expect(described_class.garbage_collection_profiler)
874
- .to receive(:total_time).at_least(:once).and_return(fake_gc_time)
875
- end
860
+ let(:fake_gc_time) { 0 }
876
861
 
877
862
  it "should record the event in the extension" do
878
863
  expect(transaction.ext).to receive(:record_event).with(
@@ -54,20 +54,12 @@ describe Appsignal do
54
54
  Appsignal.start
55
55
  end
56
56
 
57
- context "when allocation tracking and gc instrumentation have been enabled" do
57
+ context "when allocation tracking has been enabled" do
58
58
  before do
59
- allow(GC::Profiler).to receive(:enable)
60
59
  Appsignal.config.config_hash[:enable_allocation_tracking] = true
61
- Appsignal.config.config_hash[:enable_gc_instrumentation] = true
62
60
  capture_environment_metadata_report_calls
63
61
  end
64
62
 
65
- it "should enable Ruby's GC::Profiler" do
66
- expect(GC::Profiler).to receive(:enable)
67
- Appsignal.start
68
- expect_environment_metadata("ruby_gc_instrumentation_enabled", "true")
69
- end
70
-
71
63
  unless DependencyHelper.running_jruby?
72
64
  it "installs the allocation event hook" do
73
65
  expect(Appsignal::Extension).to receive(:install_allocation_event_hook)
@@ -78,29 +70,17 @@ describe Appsignal do
78
70
  end
79
71
  end
80
72
 
81
- context "when allocation tracking and gc instrumentation have been disabled" do
73
+ context "when allocation tracking has been disabled" do
82
74
  before do
83
75
  Appsignal.config.config_hash[:enable_allocation_tracking] = false
84
- Appsignal.config.config_hash[:enable_gc_instrumentation] = false
85
76
  capture_environment_metadata_report_calls
86
77
  end
87
78
 
88
- it "should not enable Ruby's GC::Profiler" do
89
- expect(GC::Profiler).not_to receive(:enable)
90
- Appsignal.start
91
- end
92
-
93
79
  it "should not install the allocation event hook" do
94
80
  expect(Appsignal::Minutely).not_to receive(:install_allocation_event_hook)
95
81
  Appsignal.start
96
82
  expect_not_environment_metadata("ruby_allocation_tracking_enabled")
97
83
  end
98
-
99
- it "should not add the gc probe to minutely" do
100
- expect(Appsignal::Minutely).not_to receive(:register_garbage_collection_probe)
101
- Appsignal.start
102
- expect_not_environment_metadata("ruby_gc_instrumentation_enabled")
103
- end
104
84
  end
105
85
 
106
86
  context "when minutely metrics has been enabled" do
@@ -511,7 +491,14 @@ describe Appsignal do
511
491
  before { allow(Appsignal::Transaction).to receive(:current).and_return(transaction) }
512
492
 
513
493
  context "with transaction" do
514
- let(:transaction) { double }
494
+ let(:transaction) { http_request_transaction }
495
+ around do |example|
496
+ Appsignal.config = project_fixture_config
497
+ set_current_transaction transaction do
498
+ example.run
499
+ end
500
+ end
501
+
515
502
  it "should call add_breadcrumb on transaction" do
516
503
  expect(transaction).to receive(:add_breadcrumb)
517
504
  .with("Network", "http", "User made network request", { :response => 200 }, fixed_time)
@@ -1,7 +1,8 @@
1
1
  module EnvHelpers
2
2
  def http_request_env_with_data(args = {})
3
+ with_queue_start = args.delete(:with_queue_start)
3
4
  path = args.delete(:path) || "/blog"
4
- Rack::MockRequest.env_for(
5
+ request = Rack::MockRequest.env_for(
5
6
  path,
6
7
  :params => args[:params] || {
7
8
  "controller" => "blog_posts",
@@ -18,6 +19,13 @@ module EnvHelpers
18
19
  :db_runtime => 500,
19
20
  :metadata => { :key => "value" }
20
21
  ).merge(args)
22
+
23
+ # Set default queue value
24
+ if with_queue_start
25
+ request["HTTP_X_QUEUE_START"] = "t=#{(fixed_time * 1_000).to_i}" # in milliseconds
26
+ end
27
+
28
+ request
21
29
  end
22
30
 
23
31
  def background_env_with_data(args = {})
@@ -46,8 +46,14 @@ module TransactionHelpers
46
46
 
47
47
  # Set current transaction manually.
48
48
  # Cleared by {clear_current_transaction!}
49
+ #
50
+ # When a block is given, the current transaction is automatically unset after
51
+ # the block.
49
52
  def set_current_transaction(transaction) # rubocop:disable Naming/AccessorMethodName
50
53
  Thread.current[:appsignal_transaction] = transaction
54
+ yield if block_given?
55
+ ensure
56
+ clear_current_transaction! if block_given?
51
57
  end
52
58
 
53
59
  # Use when {Appsignal::Transaction.clear_current_transaction!} is stubbed to
@@ -50,6 +50,17 @@ module Appsignal
50
50
 
51
51
  class Extension
52
52
  class Transaction
53
+ if Appsignal.extension_loaded?
54
+ attr_reader :queue_start
55
+ alias original_set_queue_start set_queue_start
56
+ # Temporary helper until the extension returns this information
57
+ # https://github.com/appsignal/appsignal-agent/issues/293
58
+ def set_queue_start(start) # rubocop:disable Naming/AccessorMethodName
59
+ @queue_start = start
60
+ original_set_queue_start(start)
61
+ end
62
+ end
63
+
53
64
  alias original_finish finish if method_defined? :finish
54
65
 
55
66
  # Override default {Extension::Transaction#finish} behavior to always