Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions sentry-ruby/lib/sentry/backtrace.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@ 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)
# @deprecated project_root, in_app_pattern passed from outside
# @deprecated app_dirs_pattern, in_app_pattern passed from outside
def self.parse(backtrace, project_root, app_dirs_pattern, in_app_pattern: nil, &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
# in_app_pattern is now passed in from StacktraceBuilder, so this regex won't be triggered
# only here for backwards compat and will be deleted
in_app_pattern ||= Regexp.new("^(#{project_root}/)?#{app_dirs_pattern}")

lines = ruby_lines.to_a.map do |unparsed_line|
Line.parse(unparsed_line, in_app_pattern)
Expand Down
25 changes: 16 additions & 9 deletions sentry-ruby/lib/sentry/backtrace/line.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ class Backtrace
# Handles backtrace parsing line by line
class Line
RB_EXTENSION = ".rb"
CLASS_EXTENSION = ".class"
# regexp (optional leading X: on windows, or JRuby9000 class-prefix)
RUBY_INPUT_FORMAT = /
^ \s* (?: [a-zA-Z]: | uri:classloader: )? ([^:]+ | <.*>):
Expand Down Expand Up @@ -37,12 +38,21 @@ def self.parse(unparsed_line, in_app_pattern = nil)
ruby_match = unparsed_line.match(RUBY_INPUT_FORMAT)

if ruby_match
_, file, number, _, module_name, method = ruby_match.to_a
file.sub!(/\.class$/, RB_EXTENSION)
module_name = module_name
file = ruby_match[1]
number = ruby_match[2]
module_name = ruby_match[4]
method = ruby_match[5]
if file.end_with?(CLASS_EXTENSION)
file.sub!(/\.class$/, RB_EXTENSION)
end
else
java_match = unparsed_line.match(JAVA_INPUT_FORMAT)
_, module_name, method, file, number = java_match.to_a
if java_match
module_name = java_match[1]
method = java_match[2]
file = java_match[3]
number = java_match[4]
end
end
new(file, number, method, module_name, in_app_pattern)
end
Expand Down Expand Up @@ -74,12 +84,9 @@ def initialize(file, number, method, module_name, in_app_pattern)

def in_app
return false unless in_app_pattern
return false unless file

if file =~ in_app_pattern
true
else
false
end
file.match?(in_app_pattern)
end

# Reconstructs the line in a readable fashion
Expand Down
18 changes: 12 additions & 6 deletions sentry-ruby/lib/sentry/interfaces/request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ class RequestInterface < Interface
"HTTP_X_FORWARDED_FOR"
].freeze

# Regex to detect lowercase chars — match? is allocation-free (no MatchData/String)
LOWERCASE_PATTERN = /[a-z]/.freeze

# See Sentry server default limits at
# https://github.com/getsentry/sentry/blob/master/src/sentry/conf/server.py
MAX_BODY_LIMIT = 4096 * 4
Expand Down Expand Up @@ -93,7 +96,7 @@ def filter_and_format_headers(env, send_default_pii)
next if key == "HTTP_AUTHORIZATION" && !send_default_pii

# Rack stores headers as HTTP_WHAT_EVER, we need What-Ever
key = key.sub(/^HTTP_/, "")
key = key.delete_prefix("HTTP_")
key = key.split("_").map(&:capitalize).join("-")

memo[key] = Utils::EncodingHelper.encode_to_utf_8(value.to_s)
Expand All @@ -107,9 +110,6 @@ def filter_and_format_headers(env, send_default_pii)
end
end

# Regex to detect lowercase chars — match? is allocation-free (no MatchData/String)
LOWERCASE_PATTERN = /[a-z]/.freeze

def is_skippable_header?(key)
key.match?(LOWERCASE_PATTERN) || # lower-case envs aren't real http headers
key == "HTTP_COOKIE" || # Cookies don't go here, they go somewhere else
Expand All @@ -122,12 +122,18 @@ def is_skippable_header?(key)
# if the request has legitimately sent a Version header themselves.
# See: https://github.com/rack/rack/blob/028438f/lib/rack/handler/cgi.rb#L29
def is_server_protocol?(key, value, protocol_version)
rack_version = Gem::Version.new(::Rack.release)
return false if rack_version >= Gem::Version.new("3.0")
return false if self.class.rack_3_or_above?

key == "HTTP_VERSION" && value == protocol_version
end

def self.rack_3_or_above?
return @rack_3_or_above if defined?(@rack_3_or_above)

@rack_3_or_above = defined?(::Rack) &&
Gem::Version.new(::Rack.release) >= Gem::Version.new("3.0")
end

def filter_and_format_env(env, rack_env_whitelist)
return env if rack_env_whitelist.empty?

Expand Down
34 changes: 19 additions & 15 deletions sentry-ruby/lib/sentry/interfaces/stacktrace.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,35 +28,43 @@ class Frame < Interface
:lineno, :module, :pre_context, :post_context, :vars

def initialize(project_root, line, strip_backtrace_load_path = true)
@project_root = project_root
@strip_backtrace_load_path = strip_backtrace_load_path

@abs_path = line.file
@function = line.method if line.method
@lineno = line.number
@in_app = line.in_app
@module = line.module_name if line.module_name
@filename = compute_filename
@filename = compute_filename(project_root, strip_backtrace_load_path)
end

def to_s
"#{@filename}:#{@lineno}"
end

def compute_filename
def compute_filename(project_root, strip_backtrace_load_path)
return if abs_path.nil?
return abs_path unless @strip_backtrace_load_path
return abs_path unless strip_backtrace_load_path

under_root = project_root && abs_path.start_with?(project_root)
prefix =
if under_project_root? && in_app
@project_root
elsif under_project_root?
longest_load_path || @project_root
if under_root && in_app
project_root
elsif under_root
longest_load_path || project_root
else
longest_load_path
end

prefix ? abs_path[prefix.to_s.chomp(File::SEPARATOR).length + 1..-1] : abs_path
if prefix
prefix_str = prefix.to_s
offset = if prefix_str.end_with?(File::SEPARATOR)
prefix_str.bytesize
else
prefix_str.bytesize + 1
end
abs_path.byteslice(offset, abs_path.bytesize - offset)
else
abs_path
end
end

def set_context(linecache, context_lines)
Expand All @@ -77,10 +85,6 @@ def to_h(*args)

private

def under_project_root?
@project_root && abs_path.start_with?(@project_root)
end

def longest_load_path
$LOAD_PATH.select { |path| abs_path.start_with?(path.to_s) }.max_by(&:size)
end
Expand Down
20 changes: 15 additions & 5 deletions sentry-ruby/lib/sentry/interfaces/stacktrace_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def initialize(
@context_lines = context_lines
@backtrace_cleanup_callback = backtrace_cleanup_callback
@strip_backtrace_load_path = strip_backtrace_load_path
@in_app_pattern = Regexp.new("^(#{project_root}/)?#{app_dirs_pattern}") if app_dirs_pattern
end

# Generates a StacktraceInterface with the given backtrace.
Expand All @@ -64,13 +65,21 @@ def initialize(
# @yieldparam frame [StacktraceInterface::Frame]
# @return [StacktraceInterface]
def build(backtrace:, &frame_callback)
parsed_lines = parse_backtrace_lines(backtrace).select(&:file)
parsed_lines = parse_backtrace_lines(backtrace)

# Build frames in reverse order, skipping lines without files
# Single pass instead of select + reverse + map + compact
frames = []
i = parsed_lines.size - 1
while i >= 0
line = parsed_lines[i]
i -= 1
next unless line.file

frames = parsed_lines.reverse.map do |line|
frame = convert_parsed_line_into_frame(line)
frame = frame_callback.call(frame) if frame_callback
frame
end.compact
frames << frame if frame
end

StacktraceInterface.new(frames: frames)
end
Expand All @@ -85,7 +94,8 @@ def convert_parsed_line_into_frame(line)

def parse_backtrace_lines(backtrace)
Backtrace.parse(
backtrace, project_root, app_dirs_pattern, &backtrace_cleanup_callback
backtrace, project_root, app_dirs_pattern,
in_app_pattern: @in_app_pattern, &backtrace_cleanup_callback
).lines
end
end
Expand Down
Loading