Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

backtrace: add support for ExecJS backtraces #107

Merged
merged 1 commit into from
Aug 12, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ Airbrake Ruby Changelog

### master

* Added support for CoffeeScript/ExecJS backtraces
([#107](https://github.com/airbrake/airbrake-ruby/pull/107))

### [v1.4.4][v1.4.4] (July 11, 2016)

* Added support for PL/SQL exceptions raised by
Expand Down
44 changes: 37 additions & 7 deletions lib/airbrake-ruby/backtrace.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,22 @@ module Backtrace
)
\z/x

##
# @return [Regexp] the template that matches CoffeeScript backtraces
# usually coming from Rails & ExecJS
EXECJS_STACKFRAME_REGEXP = /\A
(?:
# Matches 'compile ((execjs):6692:19)'
(?<function>.+)\s\((?<file>.+):(?<line>\d+):\d+\)
|
# Matches 'bootstrap_node.js:467:3'
(?<file>.+):(?<line>\d+):\d+(?<function>)
|
# Matches the Ruby part of the backtrace
#{RUBY_STACKFRAME_REGEXP}
)
\z/x

##
# Parses an exception's backtrace.
#
Expand All @@ -73,13 +89,7 @@ module Backtrace
def self.parse(exception, logger)
return [] if exception.backtrace.nil? || exception.backtrace.none?

regexp = if java_exception?(exception)
JAVA_STACKFRAME_REGEXP
elsif oci_exception?(exception)
OCI_STACKFRAME_REGEXP
else
RUBY_STACKFRAME_REGEXP
end
regexp = best_regexp_for(exception)

exception.backtrace.map do |stackframe|
frame = match_frame(regexp, stackframe)
Expand Down Expand Up @@ -109,10 +119,30 @@ def self.java_exception?(exception)
class << self
private

def best_regexp_for(exception)
if java_exception?(exception)
JAVA_STACKFRAME_REGEXP
elsif oci_exception?(exception)
OCI_STACKFRAME_REGEXP
elsif execjs_exception?(exception)
EXECJS_STACKFRAME_REGEXP
else
RUBY_STACKFRAME_REGEXP
end
end

def oci_exception?(exception)
defined?(OCIError) && exception.is_a?(OCIError)
end

def execjs_exception?(exception)
return false unless defined?(ExecJS::RuntimeError)
return true if exception.is_a?(ExecJS::RuntimeError)
return true if exception.cause && exception.cause.is_a?(ExecJS::RuntimeError)

false
end

def stack_frame(match)
{ file: match[:file],
line: (Integer(match[:line]) if match[:line]),
Expand Down
35 changes: 35 additions & 0 deletions spec/backtrace_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -190,5 +190,40 @@
).to eq(parsed_backtrace)
end
end

context "given an ExecJS backtrace" do
let(:bt) do
['compile ((execjs):6692:19)',
'eval (<anonymous>:1:10)',
'(execjs):6703:8',
'require../helpers.exports ((execjs):1:102)',
'Object.<anonymous> ((execjs):1:120)',
'Object.Module._extensions..js (module.js:550:10)',
'bootstrap_node.js:467:3',
"/opt/rubies/ruby-2.3.1/lib/ruby/2.3.0/benchmark.rb:308:in `realtime'"]
end

let(:ex) { ExecJS::RuntimeError.new.tap { |e| e.set_backtrace(bt) } }

let(:parsed_backtrace) do
[{ file: '(execjs)', line: 6692, function: 'compile' },
{ file: '<anonymous>', line: 1, function: 'eval' },
{ file: '(execjs)', line: 6703, function: '' },
{ file: '(execjs)', line: 1, function: 'require../helpers.exports' },
{ file: '(execjs)', line: 1, function: 'Object.<anonymous>' },
{ file: 'module.js', line: 550, function: 'Object.Module._extensions..js' },
{ file: 'bootstrap_node.js', line: 467, function: '' },
{ file: '/opt/rubies/ruby-2.3.1/lib/ruby/2.3.0/benchmark.rb',
line: 308,
function: 'realtime' }]
end

it "returns a properly formatted array of hashes" do
stub_const('ExecJS::RuntimeError', AirbrakeTestError)
expect(
described_class.parse(ex, Logger.new('/dev/null'))
).to eq(parsed_backtrace)
end
end
end
end