appsignal 3.4.13 → 3.6.1

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 (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