Skip to content

Commit

Permalink
Merge pull request #107 from airbrake/103-coffee-backtrace
Browse files Browse the repository at this point in the history
backtrace: add support for ExecJS backtraces
  • Loading branch information
kyrylo authored Aug 12, 2016
2 parents 0656003 + e10757a commit 4c3ffef
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 7 deletions.
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

0 comments on commit 4c3ffef

Please sign in to comment.