Skip to content

Commit

Permalink
backtrace: add support for generic backtraces
Browse files Browse the repository at this point in the history
Sometimes exceptions can contain manual backtraces that quack similar to
Ruby exceptions, but do not stricly adhere their template. I stumbled
upon this issues, when I was working with SCSS. It sets a couple of
custom first frames such as:

```
Sass::SyntaxError: Undefined mixin 'animation'.
/home/app/assets/stylesheets/error_pages.scss:139:in `animation'
/home/app/app/assets/stylesheets/error_pages.scss:139
```

Previously, we were not able to parse the second frame.

Furthermore, we now raise error if we cannot parse a stack frame to
simplify debugguging and improving our parser.
  • Loading branch information
kyrylo committed Dec 17, 2015
1 parent 6a7a50e commit d7d5dd0
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 3 deletions.
27 changes: 24 additions & 3 deletions lib/airbrake-ruby/backtrace.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ module Backtrace
##
# @return [Regexp] the pattern that matches standard Ruby stack frames,
# such as ./spec/notice_spec.rb:43:in `block (3 levels) in <top (required)>'
STACKFRAME_REGEXP = %r{\A
RUBY_STACKFRAME_REGEXP = %r{\A
(?<file>.+) # Matches './spec/notice_spec.rb'
:
(?<line>\d+) # Matches '43'
Expand All @@ -34,6 +34,16 @@ module Backtrace
\)
\z/x

##
# @return [Regexp] the template that tries to assume what a generic stack
# frame might look like, when exception's backtrace is set manually.
GENERIC_STACKFRAME_REGEXP = %r{\A
(?<file>.+) # Matches '/foo/bar/baz.ext'
:
(?<line>\d+) # Matches '43'
(?<function>) # No-op
\z}x

##
# Parses an exception's backtrace.
#
Expand All @@ -44,11 +54,11 @@ def self.parse(exception)
regexp = if java_exception?(exception)
JAVA_STACKFRAME_REGEXP
else
STACKFRAME_REGEXP
RUBY_STACKFRAME_REGEXP
end

(exception.backtrace || []).map do |stackframe|
stack_frame(regexp.match(stackframe))
stack_frame(match_frame(regexp, stackframe))
end
end

Expand All @@ -71,5 +81,16 @@ def stack_frame(match)
function: match[:function] }
end
end

def self.match_frame(regexp, stackframe)
match = regexp.match(stackframe)
return match if match

unless (matched_frame = GENERIC_STACKFRAME_REGEXP.match(stackframe))
raise Airbrake::Error, "failed parsing '#{stackframe}'"
end

matched_frame
end
end
end
35 changes: 35 additions & 0 deletions spec/backtrace_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,40 @@
to eq(backtrace_array)
end
end

context "generic backtrace" do
# rubocop:disable Metrics/LineLength
let(:generic_bt) do
["/home/bingo/bango/assets/stylesheets/error_pages.scss:139:in `animation'",
"/home/bingo/bango/assets/stylesheets/error_pages.scss:139",
"/home/bingo/.gem/ruby/2.2.2/gems/sass-3.4.20/lib/sass/tree/visitors/perform.rb:349:in `block in visit_mixin'"]
end
# rubocop:enable Metrics/LineLength

let(:ex) { AirbrakeTestError.new.tap { |e| e.set_backtrace(generic_bt) } }

let(:parsed_backtrace) do
# rubocop:disable Metrics/LineLength, Style/HashSyntax, Style/SpaceInsideHashLiteralBraces, Style/SpaceAroundOperators
[{:file=>"/home/bingo/bango/assets/stylesheets/error_pages.scss", :line=>139, :function=>"animation"},
{:file=>"/home/bingo/bango/assets/stylesheets/error_pages.scss", :line=>139, :function=>""},
{:file=>"/home/bingo/.gem/ruby/2.2.2/gems/sass-3.4.20/lib/sass/tree/visitors/perform.rb", :line=>349, :function=>"block in visit_mixin"}]
# rubocop:enable Metrics/LineLength, Style/HashSyntax, Style/SpaceInsideHashLiteralBraces, Style/SpaceAroundOperators
end

it "returns a properly formatted array of hashes" do
expect(described_class.parse(ex)).to eq(parsed_backtrace)
end
end

context "unknown backtrace" do
let(:unknown_bt) { ['a b c 1 23 321 .rb'] }

let(:ex) { AirbrakeTestError.new.tap { |e| e.set_backtrace(unknown_bt) } }

it "raises error" do
expect { described_class.parse(ex) }.
to raise_error(Airbrake::Error, /failed parsing/)
end
end
end
end

0 comments on commit d7d5dd0

Please sign in to comment.