Skip to content

Commit

Permalink
Merge pull request #1010 from appsignal/nested-errors
Browse files Browse the repository at this point in the history
Add error causes tree as transaction metadata
  • Loading branch information
unflxw authored Dec 11, 2023
2 parents 987a02a + cee1676 commit d81f9b5
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .changesets/store-error-causes-as-transaction-sample-data.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
bump: "minor"
type: "add"
---

Nested errors are now supported. The error causes are stored as sample data on the transaction so they can be displayed in the UI.
36 changes: 36 additions & 0 deletions lib/appsignal/transaction.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class Transaction
ALLOWED_TAG_KEY_TYPES = [Symbol, String].freeze
ALLOWED_TAG_VALUE_TYPES = [Symbol, String, Integer].freeze
BREADCRUMB_LIMIT = 20
ERROR_CAUSES_LIMIT = 10

class << self
def create(id, namespace, request, options = {})
Expand Down Expand Up @@ -374,6 +375,41 @@ def set_error(error)
cleaned_error_message(error),
backtrace ? Appsignal::Utils::Data.generate(backtrace) : Appsignal::Extension.data_array_new
)

root_cause_missing = false

causes = []
while error
error = error.cause

break unless error

if causes.length >= ERROR_CAUSES_LIMIT
Appsignal.logger.debug "Appsignal::Transaction#set_error: Error has more " \
"than #{ERROR_CAUSES_LIMIT} error causes. Only the first #{ERROR_CAUSES_LIMIT} " \
"will be reported."
root_cause_missing = true
break
end

causes << error
end

return if causes.empty?

causes_sample_data = causes.map do |e|
{
:name => e.class.name,
:message => cleaned_error_message(e)
}
end

causes_sample_data.last[:is_root_cause] = false if root_cause_missing

set_sample_data(
"error_causes",
causes_sample_data
)
end
alias_method :add_exception, :set_error

Expand Down
93 changes: 93 additions & 0 deletions spec/lib/appsignal/transaction_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,99 @@ def to_s
end
end

context "when the error has no causes" do
it "should not send the causes information as sample data" do
expect(transaction.ext).to_not receive(:set_sample_data)

transaction.set_error(error)
end
end

context "when the error has multiple causes" do
let(:error) do
e = ExampleStandardError.new("test message")
e2 = RuntimeError.new("cause message")
e3 = StandardError.new("cause message 2")
allow(e).to receive(:backtrace).and_return(["line 1"])
allow(e).to receive(:cause).and_return(e2)
allow(e2).to receive(:cause).and_return(e3)
e
end

it "sends the causes information as sample data" do
expect(transaction.ext).to receive(:set_error).with(
"ExampleStandardError",
"test message",
Appsignal::Utils::Data.generate(["line 1"])
)

expect(transaction.ext).to receive(:set_sample_data).with(
"error_causes",
Appsignal::Utils::Data.generate(
[
{
:name => "RuntimeError",
:message => "cause message"
},
{
:name => "StandardError",
:message => "cause message 2"
}
]
)
)

expect(Appsignal.logger).to_not receive(:debug)

transaction.set_error(error)
end
end

context "when the error has too many causes" do
let(:error) do
e = ExampleStandardError.new("root cause error")

11.times do |i|
next_e = ExampleStandardError.new("wrapper error #{i}")
allow(next_e).to receive(:cause).and_return(e)
e = next_e
end

allow(e).to receive(:backtrace).and_return(["line 1"])
e
end

it "sends only the first causes as sample data" do
expect(transaction.ext).to receive(:set_error).with(
"ExampleStandardError",
"wrapper error 10",
Appsignal::Utils::Data.generate(["line 1"])
)

expected_error_causes = Array.new(10) do |i|
{
:name => "ExampleStandardError",
:message => "wrapper error #{9 - i}"
}
end

expected_error_causes.last[:is_root_cause] = false

expect(transaction.ext).to receive(:set_sample_data).with(
"error_causes",
Appsignal::Utils::Data.generate(expected_error_causes)
)

expect(Appsignal.logger).to receive(:debug).with(
"Appsignal::Transaction#set_error: Error has more " \
"than 10 error causes. Only the first 10 " \
"will be reported."
)

transaction.set_error(error)
end
end

context "when error message is nil" do
let(:error) do
e = ExampleStandardError.new
Expand Down

0 comments on commit d81f9b5

Please sign in to comment.