From e10757a7be1852de40594fb426271d41e8871e26 Mon Sep 17 00:00:00 2001 From: Kyrylo Silin Date: Thu, 11 Aug 2016 20:23:31 +0300 Subject: [PATCH] backtrace: add support for ExecJS backtraces Fixes #103 ("can't parse '#{stackframe}' (please file an issue so we can fix " \")") --- CHANGELOG.md | 3 +++ lib/airbrake-ruby/backtrace.rb | 44 ++++++++++++++++++++++++++++------ spec/backtrace_spec.rb | 35 +++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d2cbd91..58866ac8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/lib/airbrake-ruby/backtrace.rb b/lib/airbrake-ruby/backtrace.rb index 4ab97dd1..4145cec8 100644 --- a/lib/airbrake-ruby/backtrace.rb +++ b/lib/airbrake-ruby/backtrace.rb @@ -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)' + (?.+)\s\((?.+):(?\d+):\d+\) + | + # Matches 'bootstrap_node.js:467:3' + (?.+):(?\d+):\d+(?) + | + # Matches the Ruby part of the backtrace + #{RUBY_STACKFRAME_REGEXP} + ) + \z/x + ## # Parses an exception's backtrace. # @@ -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) @@ -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]), diff --git a/spec/backtrace_spec.rb b/spec/backtrace_spec.rb index 41497bcc..43a51fbb 100644 --- a/spec/backtrace_spec.rb +++ b/spec/backtrace_spec.rb @@ -190,5 +190,40 @@ ).to eq(parsed_backtrace) end end + + context "given an ExecJS backtrace" do + let(:bt) do + ['compile ((execjs):6692:19)', + 'eval (:1:10)', + '(execjs):6703:8', + 'require../helpers.exports ((execjs):1:102)', + 'Object. ((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: '', line: 1, function: 'eval' }, + { file: '(execjs)', line: 6703, function: '' }, + { file: '(execjs)', line: 1, function: 'require../helpers.exports' }, + { file: '(execjs)', line: 1, function: 'Object.' }, + { 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