appsignal 3.4.13 → 3.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/.semaphore/semaphore.yml +180 -14
  3. data/CHANGELOG.md +164 -0
  4. data/README.md +2 -0
  5. data/Rakefile +3 -1
  6. data/build_matrix.yml +7 -13
  7. data/ext/Rakefile +8 -1
  8. data/ext/agent.rb +27 -27
  9. data/ext/appsignal_extension.c +0 -24
  10. data/ext/base.rb +4 -1
  11. data/gemfiles/redis-4.gemfile +5 -0
  12. data/gemfiles/redis-5.gemfile +6 -0
  13. data/lib/appsignal/cli/diagnose/paths.rb +33 -10
  14. data/lib/appsignal/cli/diagnose.rb +6 -1
  15. data/lib/appsignal/config.rb +19 -5
  16. data/lib/appsignal/demo.rb +1 -1
  17. data/lib/appsignal/environment.rb +24 -13
  18. data/lib/appsignal/event_formatter.rb +1 -1
  19. data/lib/appsignal/extension/jruby.rb +4 -17
  20. data/lib/appsignal/extension.rb +1 -1
  21. data/lib/appsignal/helpers/instrumentation.rb +10 -10
  22. data/lib/appsignal/helpers/metrics.rb +15 -13
  23. data/lib/appsignal/hooks/active_job.rb +9 -1
  24. data/lib/appsignal/hooks/redis.rb +1 -0
  25. data/lib/appsignal/hooks/redis_client.rb +27 -0
  26. data/lib/appsignal/hooks.rb +3 -2
  27. data/lib/appsignal/integrations/hanami.rb +1 -1
  28. data/lib/appsignal/integrations/padrino.rb +1 -1
  29. data/lib/appsignal/integrations/railtie.rb +1 -1
  30. data/lib/appsignal/integrations/redis_client.rb +20 -0
  31. data/lib/appsignal/integrations/sidekiq.rb +2 -2
  32. data/lib/appsignal/integrations/sinatra.rb +1 -1
  33. data/lib/appsignal/logger.rb +2 -0
  34. data/lib/appsignal/minutely.rb +4 -4
  35. data/lib/appsignal/probes/gvl.rb +1 -1
  36. data/lib/appsignal/probes/helpers.rb +1 -1
  37. data/lib/appsignal/probes/mri.rb +1 -1
  38. data/lib/appsignal/probes/sidekiq.rb +10 -8
  39. data/lib/appsignal/rack/body_wrapper.rb +161 -0
  40. data/lib/appsignal/rack/generic_instrumentation.rb +18 -5
  41. data/lib/appsignal/rack/rails_instrumentation.rb +17 -5
  42. data/lib/appsignal/rack/sinatra_instrumentation.rb +17 -5
  43. data/lib/appsignal/rack/streaming_listener.rb +27 -36
  44. data/lib/appsignal/span.rb +2 -2
  45. data/lib/appsignal/transaction.rb +46 -10
  46. data/lib/appsignal/utils/deprecation_message.rb +2 -2
  47. data/lib/appsignal/version.rb +1 -1
  48. data/lib/appsignal.rb +38 -31
  49. data/resources/cacert.pem +321 -159
  50. data/spec/lib/appsignal/cli/diagnose/utils_spec.rb +11 -0
  51. data/spec/lib/appsignal/cli/diagnose_spec.rb +38 -12
  52. data/spec/lib/appsignal/config_spec.rb +3 -2
  53. data/spec/lib/appsignal/hooks/activejob_spec.rb +26 -1
  54. data/spec/lib/appsignal/hooks/redis_client_spec.rb +222 -0
  55. data/spec/lib/appsignal/hooks/redis_spec.rb +98 -76
  56. data/spec/lib/appsignal/hooks_spec.rb +4 -4
  57. data/spec/lib/appsignal/integrations/railtie_spec.rb +2 -2
  58. data/spec/lib/appsignal/integrations/sidekiq_spec.rb +3 -3
  59. data/spec/lib/appsignal/integrations/sinatra_spec.rb +2 -2
  60. data/spec/lib/appsignal/minutely_spec.rb +2 -2
  61. data/spec/lib/appsignal/probes/sidekiq_spec.rb +29 -6
  62. data/spec/lib/appsignal/rack/body_wrapper_spec.rb +220 -0
  63. data/spec/lib/appsignal/rack/generic_instrumentation_spec.rb +3 -2
  64. data/spec/lib/appsignal/rack/rails_instrumentation_spec.rb +5 -3
  65. data/spec/lib/appsignal/rack/sinatra_instrumentation_spec.rb +3 -1
  66. data/spec/lib/appsignal/rack/streaming_listener_spec.rb +9 -53
  67. data/spec/lib/appsignal/transaction_spec.rb +95 -2
  68. data/spec/lib/appsignal_spec.rb +62 -60
  69. data/spec/spec_helper.rb +1 -1
  70. data/spec/support/fixtures/projects/valid/config/appsignal.yml +3 -3
  71. data/spec/support/helpers/config_helpers.rb +6 -2
  72. data/spec/support/helpers/dependency_helper.rb +9 -1
  73. data/spec/support/helpers/log_helpers.rb +2 -2
  74. metadata +9 -2
@@ -0,0 +1,220 @@
1
+ describe Appsignal::Rack::BodyWrapper do
2
+ let(:nil_txn) { Appsignal::Transaction::NilTransaction.new }
3
+
4
+ describe "with a body that supports all possible features" do
5
+ it "reduces the supported methods to just each()" do
6
+ # which is the safest thing to do, since the body is likely broken
7
+ fake_body = double(:each => nil, :call => nil, :to_ary => [], :to_path => "/tmp/foo.bin",
8
+ :close => nil)
9
+ wrapped = described_class.wrap(fake_body, nil_txn)
10
+ expect(wrapped).to respond_to(:each)
11
+ expect(wrapped).not_to respond_to(:to_ary)
12
+ expect(wrapped).not_to respond_to(:call)
13
+ expect(wrapped).to respond_to(:close)
14
+ end
15
+ end
16
+
17
+ describe "with a body only supporting each()" do
18
+ it "wraps with appropriate class" do
19
+ fake_body = double
20
+ allow(fake_body).to receive(:each)
21
+
22
+ wrapped = described_class.wrap(fake_body, nil_txn)
23
+ expect(wrapped).to respond_to(:each)
24
+ expect(wrapped).not_to respond_to(:to_ary)
25
+ expect(wrapped).not_to respond_to(:call)
26
+ expect(wrapped).to respond_to(:close)
27
+ end
28
+
29
+ it "reads out the body in full using each" do
30
+ fake_body = double
31
+ expect(fake_body).to receive(:each).once.and_yield("a").and_yield("b").and_yield("c")
32
+ wrapped = described_class.wrap(fake_body, nil_txn)
33
+ expect { |b| wrapped.each(&b) }.to yield_successive_args("a", "b", "c")
34
+ end
35
+
36
+ it "returns an Enumerator if each() gets called without a block" do
37
+ fake_body = double
38
+ expect(fake_body).to receive(:each).once.and_yield("a").and_yield("b").and_yield("c")
39
+
40
+ wrapped = described_class.wrap(fake_body, nil_txn)
41
+ enum = wrapped.each
42
+ expect(enum).to be_kind_of(Enumerator)
43
+ expect { |b| enum.each(&b) }.to yield_successive_args("a", "b", "c")
44
+ end
45
+
46
+ it "sets the exception raised inside each() into the Appsignal transaction" do
47
+ fake_body = double
48
+ expect(fake_body).to receive(:each).once.and_raise(Exception.new("Oops"))
49
+
50
+ txn = double("Appsignal transaction", "nil_transaction?" => false)
51
+ expect(txn).to receive(:set_error).once.with(instance_of(Exception))
52
+
53
+ wrapped = described_class.wrap(fake_body, txn)
54
+ expect do
55
+ expect { |b| wrapped.each(&b) }.to yield_control
56
+ end.to raise_error(/Oops/)
57
+ end
58
+
59
+ it "closes the body and the transaction when it gets closed" do
60
+ fake_body = double
61
+ expect(fake_body).to receive(:each).once.and_yield("a").and_yield("b").and_yield("c")
62
+
63
+ txn = double("Appsignal transaction", "nil_transaction?" => false)
64
+ expect(Appsignal::Transaction).to receive(:complete_current!).once
65
+
66
+ wrapped = described_class.wrap(fake_body, txn)
67
+ expect { |b| wrapped.each(&b) }.to yield_successive_args("a", "b", "c")
68
+ expect { wrapped.close }.not_to raise_error
69
+ end
70
+ end
71
+
72
+ describe "with a body supporting both each() and call" do
73
+ it "wraps with the wrapper that conceals call() and exposes each" do
74
+ fake_body = double
75
+ allow(fake_body).to receive(:each)
76
+ allow(fake_body).to receive(:call)
77
+
78
+ wrapped = described_class.wrap(fake_body, nil_txn)
79
+ expect(wrapped).to respond_to(:each)
80
+ expect(wrapped).not_to respond_to(:to_ary)
81
+ expect(wrapped).not_to respond_to(:call)
82
+ expect(wrapped).not_to respond_to(:to_path)
83
+ expect(wrapped).to respond_to(:close)
84
+ end
85
+ end
86
+
87
+ describe "with a body supporting both to_ary and each" do
88
+ let(:fake_body) { double(:each => nil, :to_ary => []) }
89
+ it "wraps with appropriate class" do
90
+ wrapped = described_class.wrap(fake_body, nil_txn)
91
+ expect(wrapped).to respond_to(:each)
92
+ expect(wrapped).to respond_to(:to_ary)
93
+ expect(wrapped).not_to respond_to(:call)
94
+ expect(wrapped).not_to respond_to(:to_path)
95
+ expect(wrapped).to respond_to(:close)
96
+ end
97
+
98
+ it "reads out the body in full using each" do
99
+ expect(fake_body).to receive(:each).once.and_yield("a").and_yield("b").and_yield("c")
100
+
101
+ wrapped = described_class.wrap(fake_body, nil_txn)
102
+ expect { |b| wrapped.each(&b) }.to yield_successive_args("a", "b", "c")
103
+ end
104
+
105
+ it "sets the exception raised inside each() into the Appsignal transaction" do
106
+ expect(fake_body).to receive(:each).once.and_raise(Exception.new("Oops"))
107
+
108
+ txn = double("Appsignal transaction", "nil_transaction?" => false)
109
+ expect(txn).to receive(:set_error).once.with(instance_of(Exception))
110
+
111
+ wrapped = described_class.wrap(fake_body, txn)
112
+ expect do
113
+ expect { |b| wrapped.each(&b) }.to yield_control
114
+ end.to raise_error(/Oops/)
115
+ end
116
+
117
+ it "reads out the body in full using to_ary" do
118
+ expect(fake_body).to receive(:to_ary).and_return(["one", "two", "three"])
119
+
120
+ wrapped = described_class.wrap(fake_body, nil_txn)
121
+ expect(wrapped.to_ary).to eq(["one", "two", "three"])
122
+ end
123
+
124
+ it "sends the exception raised inside to_ary() into the Appsignal and closes txn" do
125
+ fake_body = double
126
+ allow(fake_body).to receive(:each)
127
+ expect(fake_body).to receive(:to_ary).once.and_raise(Exception.new("Oops"))
128
+ expect(fake_body).not_to receive(:close) # Per spec we expect the body has closed itself
129
+
130
+ txn = double("Appsignal transaction", "nil_transaction?" => false)
131
+ expect(txn).to receive(:set_error).once.with(instance_of(Exception))
132
+ expect(Appsignal::Transaction).to receive(:complete_current!).once
133
+
134
+ wrapped = described_class.wrap(fake_body, txn)
135
+ expect { wrapped.to_ary }.to raise_error(/Oops/)
136
+ end
137
+ end
138
+
139
+ describe "with a body supporting both to_path and each" do
140
+ let(:fake_body) { double(:each => nil, :to_path => nil) }
141
+
142
+ it "wraps with appropriate class" do
143
+ wrapped = described_class.wrap(fake_body, nil_txn)
144
+ expect(wrapped).to respond_to(:each)
145
+ expect(wrapped).not_to respond_to(:to_ary)
146
+ expect(wrapped).not_to respond_to(:call)
147
+ expect(wrapped).to respond_to(:to_path)
148
+ expect(wrapped).to respond_to(:close)
149
+ end
150
+
151
+ it "reads out the body in full using each()" do
152
+ expect(fake_body).to receive(:each).once.and_yield("a").and_yield("b").and_yield("c")
153
+
154
+ wrapped = described_class.wrap(fake_body, nil_txn)
155
+ expect { |b| wrapped.each(&b) }.to yield_successive_args("a", "b", "c")
156
+ end
157
+
158
+ it "sets the exception raised inside each() into the Appsignal transaction" do
159
+ expect(fake_body).to receive(:each).once.and_raise(Exception.new("Oops"))
160
+
161
+ txn = double("Appsignal transaction", "nil_transaction?" => false)
162
+ expect(txn).to receive(:set_error).once.with(instance_of(Exception))
163
+
164
+ wrapped = described_class.wrap(fake_body, txn)
165
+ expect do
166
+ expect { |b| wrapped.each(&b) }.to yield_control
167
+ end.to raise_error(/Oops/)
168
+ end
169
+
170
+ it "sets the exception raised inside to_path() into the Appsignal transaction" do
171
+ allow(fake_body).to receive(:to_path).once.and_raise(Exception.new("Oops"))
172
+
173
+ txn = double("Appsignal transaction", "nil_transaction?" => false)
174
+ expect(txn).to receive(:set_error).once.with(instance_of(Exception))
175
+ expect(txn).not_to receive(:complete) # gets called by the caller via close()
176
+
177
+ wrapped = described_class.wrap(fake_body, txn)
178
+ expect { wrapped.to_path }.to raise_error(/Oops/)
179
+ end
180
+
181
+ it "exposes to_path to the sender" do
182
+ allow(fake_body).to receive(:to_path).and_return("/tmp/file.bin")
183
+
184
+ wrapped = described_class.wrap(fake_body, nil_txn)
185
+ expect(wrapped.to_path).to eq("/tmp/file.bin")
186
+ end
187
+ end
188
+
189
+ describe "with a body only supporting call()" do
190
+ let(:fake_body) { double(:call => nil) }
191
+ it "wraps with appropriate class" do
192
+ wrapped = described_class.wrap(fake_body, nil_txn)
193
+ expect(wrapped).not_to respond_to(:each)
194
+ expect(wrapped).not_to respond_to(:to_ary)
195
+ expect(wrapped).to respond_to(:call)
196
+ expect(wrapped).not_to respond_to(:to_path)
197
+ expect(wrapped).to respond_to(:close)
198
+ end
199
+
200
+ it "passes the stream into the call() of the body" do
201
+ fake_rack_stream = double("stream")
202
+ expect(fake_body).to receive(:call).with(fake_rack_stream)
203
+
204
+ wrapped = described_class.wrap(fake_body, nil_txn)
205
+ expect { wrapped.call(fake_rack_stream) }.not_to raise_error
206
+ end
207
+
208
+ it "sets the exception raised inside call() into the Appsignal transaction" do
209
+ fake_rack_stream = double
210
+ allow(fake_body).to receive(:call).with(fake_rack_stream).and_raise(Exception.new("Oopsie"))
211
+
212
+ txn = double("Appsignal transaction", "nil_transaction?" => false)
213
+ expect(txn).to receive(:set_error).once.with(instance_of(Exception))
214
+ expect(txn).not_to receive(:complete) # gets called by the caller via close()
215
+ wrapped = described_class.wrap(fake_body, txn)
216
+
217
+ expect { wrapped.call(fake_rack_stream) }.to raise_error(/Oopsie/)
218
+ end
219
+ end
220
+ end
@@ -50,7 +50,7 @@ describe Appsignal::Rack::GenericInstrumentation do
50
50
  expect(app).to receive(:call).with(env)
51
51
  end
52
52
 
53
- context "with an exception", :error => true do
53
+ context "with an exception raised from call()", :error => true do
54
54
  let(:error) { ExampleException }
55
55
  let(:app) do
56
56
  double.tap do |d|
@@ -58,8 +58,9 @@ describe Appsignal::Rack::GenericInstrumentation do
58
58
  end
59
59
  end
60
60
 
61
- it "records the exception" do
61
+ it "records the exception and completes the transaction" do
62
62
  expect_any_instance_of(Appsignal::Transaction).to receive(:set_error).with(error)
63
+ expect(Appsignal::Transaction).to receive(:complete_current!)
63
64
  end
64
65
  end
65
66
 
@@ -6,7 +6,7 @@ if DependencyHelper.rails_present?
6
6
  let(:log) { StringIO.new }
7
7
  before do
8
8
  start_agent
9
- Appsignal.logger = test_logger(log)
9
+ Appsignal.internal_logger = test_logger(log)
10
10
  end
11
11
 
12
12
  let(:params) do
@@ -65,7 +65,8 @@ if DependencyHelper.rails_present?
65
65
 
66
66
  describe "#call_with_appsignal_monitoring" do
67
67
  def run
68
- middleware.call(env)
68
+ _status, _headers, body = middleware.call(env)
69
+ body.close # Rack will always call close() on the body
69
70
  end
70
71
 
71
72
  it "calls the wrapped app" do
@@ -126,7 +127,8 @@ if DependencyHelper.rails_present?
126
127
  end
127
128
  end
128
129
 
129
- it "records the exception" do
130
+ it "records the exception and completes the transaction" do
131
+ expect(Appsignal::Transaction).to receive(:complete_current!)
130
132
  expect { run }.to raise_error(error)
131
133
 
132
134
  transaction_hash = last_transaction.to_h
@@ -3,7 +3,9 @@ if DependencyHelper.sinatra_present?
3
3
 
4
4
  module SinatraRequestHelpers
5
5
  def make_request(env)
6
- middleware.call(env)
6
+ _status, _headers, body = middleware.call(env)
7
+ # Close the body so that the transaction gets completed
8
+ body&.close
7
9
  end
8
10
 
9
11
  def make_request_with_error(env, error)
@@ -90,10 +90,11 @@ describe Appsignal::Rack::StreamingListener do
90
90
  context "with an exception in the instrumentation call" do
91
91
  let(:error) { ExampleException }
92
92
 
93
- it "should add the exception to the transaction" do
93
+ it "should add the exception to the transaction and complete the transaction" do
94
94
  allow(app).to receive(:call).and_raise(error)
95
95
 
96
96
  expect(transaction).to receive(:set_error).with(error)
97
+ expect(Appsignal::Transaction).to receive(:complete_current!).and_call_original
97
98
 
98
99
  expect do
99
100
  listener.call_with_appsignal_monitoring(env)
@@ -101,64 +102,19 @@ describe Appsignal::Rack::StreamingListener do
101
102
  end
102
103
  end
103
104
 
104
- it "should wrap the body in a wrapper" do
105
- expect(Appsignal::StreamWrapper).to receive(:new)
106
- .with("body", transaction)
107
- .and_return(wrapper)
108
-
105
+ it "should wrap the response body in a wrapper" do
109
106
  body = listener.call_with_appsignal_monitoring(env)[2]
110
107
 
111
- expect(body).to be_a(Appsignal::StreamWrapper)
108
+ expect(body).to be_kind_of(Appsignal::Rack::BodyWrapper)
112
109
  end
113
110
  end
114
111
  end
115
112
 
116
113
  describe Appsignal::StreamWrapper do
117
- let(:stream) { double }
118
- let(:transaction) do
119
- Appsignal::Transaction.create(SecureRandom.uuid, Appsignal::Transaction::HTTP_REQUEST, {})
120
- end
121
- let(:wrapper) { Appsignal::StreamWrapper.new(stream, transaction) }
122
-
123
- describe "#each" do
124
- it "calls the original stream" do
125
- expect(stream).to receive(:each)
126
-
127
- wrapper.each
128
- end
129
-
130
- context "when #each raises an error" do
131
- let(:error) { ExampleException }
132
-
133
- it "records the exception" do
134
- allow(stream).to receive(:each).and_raise(error)
135
-
136
- expect(transaction).to receive(:set_error).with(error)
137
-
138
- expect { wrapper.send(:each) }.to raise_error(error)
139
- end
140
- end
141
- end
142
-
143
- describe "#close" do
144
- it "closes the original stream and completes the transaction" do
145
- expect(stream).to receive(:close)
146
- expect(Appsignal::Transaction).to receive(:complete_current!)
147
-
148
- wrapper.close
149
- end
150
-
151
- context "when #close raises an error" do
152
- let(:error) { ExampleException }
153
-
154
- it "records the exception and completes the transaction" do
155
- allow(stream).to receive(:close).and_raise(error)
156
-
157
- expect(transaction).to receive(:set_error).with(error)
158
- expect(transaction).to receive(:complete)
159
-
160
- expect { wrapper.send(:close) }.to raise_error(error)
161
- end
162
- end
114
+ it ".new returns an EnumerableWrapper" do
115
+ fake_body = double(:each => nil)
116
+ fake_txn = double
117
+ stream_wrapper = Appsignal::StreamWrapper.new(fake_body, fake_txn)
118
+ expect(stream_wrapper).to be_kind_of(Appsignal::Rack::EnumerableBodyWrapper)
163
119
  end
164
120
  end
@@ -580,7 +580,7 @@ describe Appsignal::Transaction do
580
580
  it "does not raise an error when the queue start is too big" do
581
581
  expect(transaction.ext).to receive(:set_queue_start).and_raise(RangeError)
582
582
 
583
- expect(Appsignal.logger).to receive(:warn).with("Queue start value 10 is too big")
583
+ expect(Appsignal.internal_logger).to receive(:warn).with("Queue start value 10 is too big")
584
584
 
585
585
  expect do
586
586
  transaction.set_queue_start(10)
@@ -766,7 +766,7 @@ describe Appsignal::Transaction do
766
766
  let(:error) { Object.new }
767
767
 
768
768
  it "does not add the error" do
769
- expect(Appsignal.logger).to receive(:error).with(
769
+ expect(Appsignal.internal_logger).to receive(:error).with(
770
770
  "Appsignal::Transaction#set_error: Cannot set error. " \
771
771
  "The given value is not an exception: #{error.inspect}"
772
772
  )
@@ -788,6 +788,99 @@ describe Appsignal::Transaction do
788
788
  end
789
789
  end
790
790
 
791
+ context "when the error has no causes" do
792
+ it "should not send the causes information as sample data" do
793
+ expect(transaction.ext).to_not receive(:set_sample_data)
794
+
795
+ transaction.set_error(error)
796
+ end
797
+ end
798
+
799
+ context "when the error has multiple causes" do
800
+ let(:error) do
801
+ e = ExampleStandardError.new("test message")
802
+ e2 = RuntimeError.new("cause message")
803
+ e3 = StandardError.new("cause message 2")
804
+ allow(e).to receive(:backtrace).and_return(["line 1"])
805
+ allow(e).to receive(:cause).and_return(e2)
806
+ allow(e2).to receive(:cause).and_return(e3)
807
+ e
808
+ end
809
+
810
+ it "sends the causes information as sample data" do
811
+ expect(transaction.ext).to receive(:set_error).with(
812
+ "ExampleStandardError",
813
+ "test message",
814
+ Appsignal::Utils::Data.generate(["line 1"])
815
+ )
816
+
817
+ expect(transaction.ext).to receive(:set_sample_data).with(
818
+ "error_causes",
819
+ Appsignal::Utils::Data.generate(
820
+ [
821
+ {
822
+ :name => "RuntimeError",
823
+ :message => "cause message"
824
+ },
825
+ {
826
+ :name => "StandardError",
827
+ :message => "cause message 2"
828
+ }
829
+ ]
830
+ )
831
+ )
832
+
833
+ expect(Appsignal.internal_logger).to_not receive(:debug)
834
+
835
+ transaction.set_error(error)
836
+ end
837
+ end
838
+
839
+ context "when the error has too many causes" do
840
+ let(:error) do
841
+ e = ExampleStandardError.new("root cause error")
842
+
843
+ 11.times do |i|
844
+ next_e = ExampleStandardError.new("wrapper error #{i}")
845
+ allow(next_e).to receive(:cause).and_return(e)
846
+ e = next_e
847
+ end
848
+
849
+ allow(e).to receive(:backtrace).and_return(["line 1"])
850
+ e
851
+ end
852
+
853
+ it "sends only the first causes as sample data" do
854
+ expect(transaction.ext).to receive(:set_error).with(
855
+ "ExampleStandardError",
856
+ "wrapper error 10",
857
+ Appsignal::Utils::Data.generate(["line 1"])
858
+ )
859
+
860
+ expected_error_causes = Array.new(10) do |i|
861
+ {
862
+ :name => "ExampleStandardError",
863
+ :message => "wrapper error #{9 - i}"
864
+ }
865
+ end
866
+
867
+ expected_error_causes.last[:is_root_cause] = false
868
+
869
+ expect(transaction.ext).to receive(:set_sample_data).with(
870
+ "error_causes",
871
+ Appsignal::Utils::Data.generate(expected_error_causes)
872
+ )
873
+
874
+ expect(Appsignal.internal_logger).to receive(:debug).with(
875
+ "Appsignal::Transaction#set_error: Error has more " \
876
+ "than 10 error causes. Only the first 10 " \
877
+ "will be reported."
878
+ )
879
+
880
+ transaction.set_error(error)
881
+ end
882
+ end
883
+
791
884
  context "when error message is nil" do
792
885
  let(:error) do
793
886
  e = ExampleStandardError.new