Module: Flare::SourceLocation

Defined in:
lib/flare/source_location.rb

Overview

Utility for finding the source location (file, line, method) of app code that triggered a database query or other instrumented operation.

Constant Summary collapse

MAX_TRACE_LINES =

How many app code lines to capture for the backtrace

8
IGNORE_PATTERNS =

Patterns to filter out from backtraces (gems, framework code)

[
  /\/gems\//,
  /\/ruby\//,
  /\/rubygems\//,
  /lib\/active_record/,
  /lib\/active_support/,
  /lib\/action_/,
  /opentelemetry/,
  /flare/,
  /<internal:/,
  /\/bin\//,
].freeze

Class Method Summary collapse

Class Method Details

.add_to_attributes(attrs) ⇒ Object

Add source location attributes to a hash (for span attributes)



54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/flare/source_location.rb', line 54

def add_to_attributes(attrs)
  location = find
  return attrs unless location

  attrs["code.filepath"] = location[:filepath]
  attrs["code.lineno"] = location[:lineno]
  attrs["code.function"] = location[:function] if location[:function]

  # Add full trace as a single string attribute
  trace = find_trace
  attrs["code.stacktrace"] = trace.join("\n") if trace.any?

  attrs
end

.app_code?(line) ⇒ Boolean

Returns:

  • (Boolean)


69
70
71
72
73
74
# File 'lib/flare/source_location.rb', line 69

def app_code?(line)
  # Must contain /app/ (Rails convention) and not match ignore patterns
  return false unless line.include?("/app/")

  IGNORE_PATTERNS.none? { |pattern| line.match?(pattern) }
end

.clean_backtrace_line(line) ⇒ Object



93
94
95
96
97
98
99
100
101
102
# File 'lib/flare/source_location.rb', line 93

def clean_backtrace_line(line)
  # Parse and reformat: "app/models/user.rb:42:in `find_by_email'"
  if line =~ /\A(.+):(\d+):in [`'](.+)'\z/
    "#{clean_path($1)}:#{$2} in `#{$3}'"
  elsif line =~ /\A(.+):(\d+)\z/
    "#{clean_path($1)}:#{$2}"
  else
    clean_path(line)
  end
end

.clean_path(path) ⇒ Object



104
105
106
107
108
109
110
111
# File 'lib/flare/source_location.rb', line 104

def clean_path(path)
  # Remove Rails.root prefix if present
  if defined?(Rails) && Rails.respond_to?(:root) && Rails.root
    path.sub(/\A#{Regexp.escape(Rails.root.to_s)}\//, "")
  else
    path
  end
end

.findObject

Find the first app source location from the current backtrace Returns a hash with :filepath, :lineno, :function or nil



28
29
30
31
32
33
34
35
36
37
# File 'lib/flare/source_location.rb', line 28

def find
  backtrace = caller(2, 50)
  return nil unless backtrace

  # Find the first line that's app code (not gems/framework)
  app_line = backtrace.find { |line| app_code?(line) }
  return nil unless app_line

  parse_backtrace_line(app_line)
end

.find_traceObject

Find multiple app source locations from the current backtrace Returns an array of cleaned backtrace lines (up to MAX_TRACE_LINES)



41
42
43
44
45
46
47
48
49
50
51
# File 'lib/flare/source_location.rb', line 41

def find_trace
  backtrace = caller(2, 100)
  return [] unless backtrace

  # Filter to only app code lines
  app_lines = backtrace.select { |line| app_code?(line) }
  return [] if app_lines.empty?

  # Clean and format each line, limit to MAX_TRACE_LINES
  app_lines.first(MAX_TRACE_LINES).map { |line| clean_backtrace_line(line) }
end

.parse_backtrace_line(line) ⇒ Object



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/flare/source_location.rb', line 76

def parse_backtrace_line(line)
  # Parse: /path/to/file.rb:123:in `method_name'
  if line =~ /\A(.+):(\d+):in [`'](.+)'\z/
    {
      filepath: clean_path($1),
      lineno: $2.to_i,
      function: $3
    }
  elsif line =~ /\A(.+):(\d+)\z/
    {
      filepath: clean_path($1),
      lineno: $2.to_i,
      function: nil
    }
  end
end