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
|
# frozen_string_literal: true
require "rubygems"
require "concurrent/map"
require "sentry/backtrace/line"
module Sentry
# @api private
class Backtrace
# 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}")
end
lines = ruby_lines.to_a.map do |unparsed_line|
Line.parse(unparsed_line, in_app_pattern)
end
new(lines)
end
# Thread.each_caller_location is an API added in Ruby 3.2 that doesn't always collect
# the entire stack like Kernel#caller or #caller_locations do.
#
# @see https://github.com/rails/rails/pull/49095 for more context.
if Thread.respond_to?(:each_caller_location)
def self.source_location(&backtrace_cleaner)
Thread.each_caller_location do |location|
frame_key = [location.absolute_path, location.lineno]
cached_value = line_cache[frame_key]
next if cached_value == :skip
if cached_value
return cached_value
else
if cleaned_frame = backtrace_cleaner.(location)
line = Line.from_source_location(location)
line_cache[frame_key] = line
return line
else
line_cache[frame_key] = :skip
next
end
end
end
end
def self.line_cache
@line_cache ||= Concurrent::Map.new
end
else
# Since Sentry is mostly used in production, we don't want to fallback
# to the slower implementation and adds potentially big overhead to the
# application.
def self.source_location(*)
nil
end
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
|