1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
|
# frozen_string_literal: true
require "rubygems"
module Sentry
# @api private
class Backtrace
# Handles backtrace parsing line by line
class Line
RB_EXTENSION = ".rb"
# regexp (optional leading X: on windows, or JRuby9000 class-prefix)
RUBY_INPUT_FORMAT = /
^ \s* (?: [a-zA-Z]: | uri:classloader: )? ([^:]+ | <.*>):
(\d+)
(?: :in\s('|`)([^']+)')?$
/x.freeze
# org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:170)
JAVA_INPUT_FORMAT = /^(.+)\.([^\.]+)\(([^\:]+)\:(\d+)\)$/.freeze
# The file portion of the line (such as app/models/user.rb)
attr_reader :file
# The line number portion of the line
attr_reader :number
# The method of the line (such as index)
attr_reader :method
# The module name (JRuby)
attr_reader :module_name
attr_reader :in_app_pattern
# Parses a single line of a given backtrace
# @param [String] unparsed_line The raw line from +caller+ or some backtrace
# @return [Line] The parsed backtrace line
def self.parse(unparsed_line, in_app_pattern = nil)
ruby_match = unparsed_line.match(RUBY_INPUT_FORMAT)
if ruby_match
_, file, number, _, method = ruby_match.to_a
file.sub!(/\.class$/, RB_EXTENSION)
module_name = nil
else
java_match = unparsed_line.match(JAVA_INPUT_FORMAT)
_, module_name, method, file, number = java_match.to_a
end
new(file, number, method, module_name, in_app_pattern)
end
def initialize(file, number, method, module_name, in_app_pattern)
@file = file
@module_name = module_name
@number = number.to_i
@method = method
@in_app_pattern = in_app_pattern
end
def in_app
return false unless in_app_pattern
if file =~ in_app_pattern
true
else
false
end
end
# Reconstructs the line in a readable fashion
def to_s
"#{file}:#{number}:in `#{method}'"
end
def ==(other)
to_s == other.to_s
end
def inspect
"<Line:#{self}>"
end
end
APP_DIRS_PATTERN = /(bin|exe|app|config|lib|test|spec)/.freeze
# holder for an Array of Backtrace::Line instances
attr_reader :lines
def self.parse(backtrace, project_root, app_dirs_pattern, &backtrace_cleanup_callback)
ruby_lines = backtrace.is_a?(Array) ? backtrace : backtrace.split(/\n\s*/)
ruby_lines = backtrace_cleanup_callback.call(ruby_lines) if backtrace_cleanup_callback
in_app_pattern ||= begin
Regexp.new("^(#{project_root}/)?#{app_dirs_pattern || APP_DIRS_PATTERN}")
end
lines = ruby_lines.to_a.map do |unparsed_line|
Line.parse(unparsed_line, in_app_pattern)
end
new(lines)
end
def initialize(lines)
@lines = lines
end
def inspect
"<Backtrace: " + lines.map(&:inspect).join(", ") + ">"
end
def to_s
content = []
lines.each do |line|
content << line
end
content.join("\n")
end
def ==(other)
if other.respond_to?(:lines)
lines == other.lines
else
false
end
end
end
end
|