module Airbrake::Backtrace

Represents a cross-Ruby backtrace from exceptions (including JRuby Java exceptions). Provides information about stack frames (such as line number, file and method) in convenient for Airbrake format.

@example

begin
  raise 'Oops!'
rescue
  Backtrace.parse($!)
end

@api private @since v1.0.0

Constants

CODE_FRAME_LIMIT

@return [Integer] how many first frames should include code hunks

Public Class Methods

java_exception?(exception) click to toggle source

Checks whether the given exception was generated by JRuby's VM.

@param [Exception] exception @return [Boolean]

# File lib/airbrake-ruby/backtrace.rb, line 106
def self.java_exception?(exception)
  if defined?(Java::JavaLang::Throwable) &&
     exception.is_a?(Java::JavaLang::Throwable)
    return true
  end

  return false unless exception.respond_to?(:backtrace)

  (Patterns::JAVA =~ exception.backtrace.first) != nil
end
parse(exception) click to toggle source

Parses an exception's backtrace.

@param [Exception] exception The exception, which contains a backtrace to

parse

@return [Array<Hash{Symbol=>String,Integer}>] the parsed backtrace

# File lib/airbrake-ruby/backtrace.rb, line 96
def self.parse(exception)
  return [] if exception.backtrace.nil? || exception.backtrace.none?

  parse_backtrace(exception)
end

Private Class Methods

best_regexp_for(exception) click to toggle source
# File lib/airbrake-ruby/backtrace.rb, line 122
def best_regexp_for(exception)
  if java_exception?(exception)
    Patterns::JAVA
  elsif oci_exception?(exception)
    Patterns::OCI
  elsif execjs_exception?(exception)
    Patterns::EXECJS
  else
    Patterns::RUBY
  end
end
execjs_exception?(exception) click to toggle source
# File lib/airbrake-ruby/backtrace.rb, line 138
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
frame_in_root?(frame, root_directory) click to toggle source
# File lib/airbrake-ruby/backtrace.rb, line 192
def frame_in_root?(frame, root_directory)
  frame[:file].start_with?(root_directory) && frame[:file] !~ %r{vendor/bundle}
end
match_frame(regexp, stackframe) click to toggle source
# File lib/airbrake-ruby/backtrace.rb, line 162
def match_frame(regexp, stackframe)
  match = regexp.match(stackframe)
  return match if match

  Patterns::GENERIC.match(stackframe)
end
oci_exception?(exception) click to toggle source
# File lib/airbrake-ruby/backtrace.rb, line 134
def oci_exception?(exception)
  defined?(OCIError) && exception.is_a?(OCIError)
end
parse_backtrace(exception) click to toggle source
# File lib/airbrake-ruby/backtrace.rb, line 169
def parse_backtrace(exception)
  regexp = best_regexp_for(exception)
  root_directory = Airbrake::Config.instance.root_directory.to_s

  exception.backtrace.map.with_index do |stackframe, i|
    frame = stack_frame(regexp, stackframe)
    next(frame) if !Airbrake::Config.instance.code_hunks || frame[:file].nil?

    if !root_directory.empty?
      populate_code(frame) if frame_in_root?(frame, root_directory)
    elsif i < CODE_FRAME_LIMIT
      populate_code(frame)
    end

    frame
  end
end
populate_code(frame) click to toggle source
# File lib/airbrake-ruby/backtrace.rb, line 187
def populate_code(frame)
  code = Airbrake::CodeHunk.new.get(frame[:file], frame[:line])
  frame[:code] = code if code
end
stack_frame(regexp, stackframe) click to toggle source
# File lib/airbrake-ruby/backtrace.rb, line 146
def stack_frame(regexp, stackframe)
  if (match = match_frame(regexp, stackframe))
    return {
      file: match[:file],
      line: (Integer(match[:line]) if match[:line]),
      function: match[:function],
    }
  end

  logger.error(
    "can't parse '#{stackframe}' (please file an issue so we can fix " \
    "it: https://github.com/airbrake/airbrake-ruby/issues/new)",
  )
  { file: nil, line: nil, function: stackframe }
end