require"json"moduleHoneybadger# @api private# Front end to parsing the backtrace for each notice.classBacktrace# Handles backtrace parsing line by line.classLine# Backtrace line regexp (optionally allowing leading X: for windows support).# Capture quoted strings either with leading backtick (pre Ruby 3.4) or single quote.INPUT_FORMAT=%r{^((?:[a-zA-Z]:)?[^:]+):(\d+)(?::in (?:`|')([^']+)')?$}# 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# Filtered representationsattr_reader:filtered_file,:filtered_number,:filtered_method# Parses a single line of a given backtrace## @param [String] unparsed_line The raw line from +caller+ or some backtrace.## @return The parsed backtrace line.defself.parse(unparsed_line,opts={})filters=opts[:filters]||[]filtered_line=filters.reduce(unparsed_line)do|line,proc|# TODO: Break if nilifproc.arity==2proc.call(line,opts[:config])elseproc.call(line)endendiffiltered_linematch=unparsed_line.match(INPUT_FORMAT)||[].freezefmatch=filtered_line.match(INPUT_FORMAT)||[].freezefile,number,method=match[1],match[2],match[3]filtered_args=[fmatch[1],fmatch[2],fmatch[3]]new(file,number,method,*filtered_args,opts.fetch(:source_radius,2))endenddefinitialize(file,number,method,filtered_file=file,filtered_number=number,filtered_method=method,source_radius=2)self.filtered_file=filtered_fileself.filtered_number=filtered_numberself.filtered_method=filtered_methodself.file=fileself.number=numberself.method=methodself.source_radius=source_radiusend# Reconstructs the line in a readable fashion.defto_s"#{filtered_file}:#{filtered_number}:in `#{filtered_method}'"enddef==(other)to_s==other.to_senddefinspect"<Line:#{self}>"end# Determines if this line is part of the application trace or not.defapplication?(filtered_file=~/^\[PROJECT_ROOT\]/i)&&!(filtered_file=~/^\[PROJECT_ROOT\]\/vendor/i)enddefsource@source||=get_source(file,number,source_radius)endprivateattr_writer:file,:number,:method,:filtered_file,:filtered_number,:filtered_methodattr_accessor:source_radius# Open source file and read line(s).## Returns an array of line(s) from source file.defget_source(file,number,radius=2)iffile&&File.exist?(file)before=after=radiusstart=(number.to_i-1)-beforestart=0andbefore=1ifstart<=0duration=before+1+afterl=0File.open(file)do|f|start.times{f.getsl+=1}returnduration.times.map{(line=f.gets)?[(l+=1),line]:nil}.compact.to_hendelse{}endendend# Holder for an Array of Backtrace::Line instances.attr_reader:lines,:application_linesdefself.parse(ruby_backtrace,opts={})ruby_lines=split_multiline_backtrace(ruby_backtrace.to_a)lines=ruby_lines.collectdo|unparsed_line|Line.parse(unparsed_line.to_s,opts)end.compactnew(lines)enddefinitialize(lines)self.lines=linesself.application_lines=lines.select(&:application?)end# Convert Backtrace to arry.## Returns array containing backtrace lines.defto_arylines.take(1000).map{|l|{number: l.filtered_number,file: l.filtered_file,method: l.filtered_method,source: l.source}}endalias_method:to_a,:to_ary# JSON support.## Returns JSON representation of backtrace.defas_json(options={})to_aryend# Creates JSON.## Returns valid JSON representation of backtrace.defto_json(*a)as_json.to_json(*a)enddefto_slines.map(&:to_s).join("\n")enddefinspect"<Backtrace: "+lines.collect{|line|line.inspect}.join(", ")+">"enddef==(other)ifother.respond_to?(:to_json)to_json==other.to_jsonelsefalseendendprivateattr_writer:lines,:application_linesclass<<selfprivatedefsplit_multiline_backtrace(backtrace)ifbacktrace.size==1backtrace.first.to_s.split(/\n\s*/)elsebacktraceendendendendend