Skip to content

Commit

Permalink
Run error cause backtrace lines through filters
Browse files Browse the repository at this point in the history
If the Rails backtrace cleaner filter is present also run the error
cause backtrace through the filter as well.

Replicate our processor behavior for the error causes by stripping of
the app/root path in the Ruby gem.
  • Loading branch information
tombruijn committed Sep 25, 2024
1 parent 496b035 commit 097bc32
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 6 deletions.
13 changes: 11 additions & 2 deletions lib/appsignal/transaction.rb
Original file line number Diff line number Diff line change
Expand Up @@ -644,7 +644,8 @@ def _set_error(error)
%r{(?<gem>[\w-]+ \(.+\) )?(?<path>:?/?\w+?.+?):(?<line>:?\d+)(?<group>:in `(?<method>.+)')?$}.freeze # rubocop:disable Layout/LineLength

def first_formatted_backtrace_line(error)
first_line = error.backtrace&.first
backtrace = cleaned_backtrace(error.backtrace)
first_line = backtrace&.first
return unless first_line

captures = BACKTRACE_REGEX.match(first_line)
Expand All @@ -653,11 +654,19 @@ def first_formatted_backtrace_line(error)
captures.named_captures
.merge("original" => first_line)
.tap do |c|
config = Appsignal.config
c.delete("group") # Unused key, only for easier matching
# Strip of whitespace at the end of the gem name
c["gem"] = c["gem"]&.strip
# Strip the app path from the path if present
root_path = config.root_path
if c["path"].start_with?(root_path)
c["path"].delete_prefix!(root_path)
# Relative paths shouldn't start with a slash
c["path"].delete_prefix!("/")
end
# Add revision for linking to the repository from the UI
c["revision"] = Appsignal.config[:revision]
c["revision"] = config[:revision]
end
end

Expand Down
88 changes: 86 additions & 2 deletions spec/lib/appsignal/transaction_spec.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
describe Appsignal::Transaction do
let(:options) { {} }
let(:time) { Time.at(fixed_time) }
let(:root_path) { nil }

before do
start_agent(:options => options)
start_agent(:options => options, :root_path => root_path)
Timecop.freeze(time)
end
after { Timecop.return }
Expand Down Expand Up @@ -1865,13 +1866,16 @@ def to_s
allow(e3).to receive(:cause).and_return(e4)
e
end

let(:error_without_cause) do
ExampleStandardError.new("error without cause")
end
let(:options) { { :revision => "my_revision" } }

it "sends the causes information as sample data" do
# Hide Rails so we can test the normal Ruby behavior. The Rails
# behavior is tested in another spec.
hide_const("Rails")

transaction.send(:_set_error, error)

expect(transaction).to have_error(
Expand Down Expand Up @@ -1931,6 +1935,86 @@ def to_s
expect(transaction).to include_error_causes([])
end

describe "with app paths" do
let(:root_path) { project_fixture_path }
let(:error) do
e = ExampleStandardError.new("test message")
e2 = RuntimeError.new("cause message")
e2.set_backtrace(["#{root_path}/src/example.rb:123:in `my_method'"])
allow(e).to receive(:cause).and_return(e2)
e
end

it "sends the causes information as sample data" do
# Hide Rails so we can test the normal Ruby behavior. The Rails
# behavior is tested in another spec.
hide_const("Rails")

transaction.send(:_set_error, error)

path = "src/example.rb"
original_path = "#{root_path}/#{path}"

expect(transaction).to include_error_causes([
{
"name" => "RuntimeError",
"message" => "cause message",
"first_line" => {
"original" => "#{original_path}:123:in `my_method'",
"gem" => nil,
"path" => path,
"line" => "123",
"method" => "my_method",
"revision" => "my_revision"
}
}
])
end
end

if rails_present?
describe "with Rails" do
let(:root_path) { project_fixture_path }
let(:error) do
e = ExampleStandardError.new("test message")
e2 = RuntimeError.new("cause message")
e2.set_backtrace([
"#{root_path}/src/example.rb:123:in `my_method'"
])
allow(e).to receive(:cause).and_return(e2)
e
end

it "sends the causes information as sample data" do
transaction.send(:_set_error, error)

path = "src/example.rb"
original_path = "#{root_path}/#{path}"
# When Rails is present we run it through the Rails backtrace cleaner
# that removes the app path from the backtrace lines, so update our
# assertion to match.
original_path.delete_prefix!(DirectoryHelper.project_dir)
original_path.delete_prefix!("/")
path = original_path

expect(transaction).to include_error_causes([
{
"name" => "RuntimeError",
"message" => "cause message",
"first_line" => {
"original" => "#{original_path}:123:in `my_method'",
"gem" => nil,
"path" => path,
"line" => "123",
"method" => "my_method",
"revision" => "my_revision"
}
}
])
end
end
end

describe "HAML backtrace lines" do
let(:error) do
e = ExampleStandardError.new("test message")
Expand Down
9 changes: 7 additions & 2 deletions spec/support/helpers/config_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,15 @@ def build_config(
end
module_function :build_config

def start_agent(env: "production", options: {}, internal_logger: nil)
def start_agent(
env: "production",
root_path: nil,
options: {},
internal_logger: nil
)
env = "production" if env == :default
env ||= "production"
Appsignal.configure(env, :root_path => project_fixture_path) do |config|
Appsignal.configure(env, :root_path => root_path || project_fixture_path) do |config|
options.each do |option, value|
config.send("#{option}=", value)
end
Expand Down

0 comments on commit 097bc32

Please sign in to comment.