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($!, Logger.new(STDOUT))
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 119
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(config, 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 109
def self.parse(config, exception)
  return [] if exception.backtrace.nil? || exception.backtrace.none?
  parse_backtrace(config, exception)
end

Private Class Methods

best_regexp_for(exception) click to toggle source
# File lib/airbrake-ruby/backtrace.rb, line 133
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

rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity

# File lib/airbrake-ruby/backtrace.rb, line 150
def execjs_exception?(exception)
  return false unless defined?(ExecJS::RuntimeError)
  return true if exception.is_a?(ExecJS::RuntimeError)

  if Airbrake::RUBY_20
    # Ruby <2.1 doesn't support Exception#cause. We work around this by
    # parsing backtraces. It's slow, so we check only a few first frames.
    exception.backtrace[0..2].each do |frame|
      return true if frame =~ Patterns::EXECJS_SIMPLIFIED
    end
  elsif exception.cause && exception.cause.is_a?(ExecJS::RuntimeError)
    return true
  end

  false
end
match_frame(regexp, stackframe) click to toggle source
# File lib/airbrake-ruby/backtrace.rb, line 184
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 145
def oci_exception?(exception)
  defined?(OCIError) && exception.is_a?(OCIError)
end
parse_backtrace(config, exception) click to toggle source
# File lib/airbrake-ruby/backtrace.rb, line 191
def parse_backtrace(config, exception)
  regexp = best_regexp_for(exception)
  root_directory = config.root_directory.to_s

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

    if !root_directory.empty?
      populate_code(config, frame) if frame[:file].start_with?(root_directory)
    elsif i < CODE_FRAME_LIMIT
      populate_code(config, frame)
    end

    frame
  end
end
populate_code(config, frame) click to toggle source
# File lib/airbrake-ruby/backtrace.rb, line 209
def populate_code(config, frame)
  code = Airbrake::CodeHunk.new(config).get(frame[:file], frame[:line])
  frame[:code] = code if code
end
stack_frame(config, regexp, stackframe) click to toggle source

rubocop:enable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity

# File lib/airbrake-ruby/backtrace.rb, line 168
def stack_frame(config, regexp, stackframe)
  if (match = match_frame(regexp, stackframe))
    return {
      file: match[:file],
      line: (Integer(match[:line]) if match[:line]),
      function: match[:function]
    }
  end

  config.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