Module: Tinymon::Stacktrace

Defined in:
lib/tinymon/stacktrace.rb

Overview

Convert a Ruby Exception’s backtrace into wire-format stack frames.

Wire format wants frames ordered deepest-LAST. Ruby’s #backtrace returns them deepest-FIRST (raise site first, then callers), so we reverse.

Constant Summary collapse

GEM_MARKER =

Falsy heuristic: a frame is NOT in_app if its path lives inside a gem directory, the Ruby stdlib, or is a built-in (‘<internal:…>`).

"/gems/"
INTERNAL_PREFIX =
"<"
STDLIB_PATH =
RbConfig::CONFIG["rubylibdir"].to_s
BT_LINE =

Match strings like:

"path/to/file.rb:123:in `method_name'"
"path/to/file.rb:123:in 'method_name'"
/\A(.+?):(\d+)(?::in [`'](.+?)')?\z/.freeze

Class Method Summary collapse

Class Method Details

.from_location(loc) ⇒ Object



40
41
42
43
44
45
46
47
48
49
# File 'lib/tinymon/stacktrace.rb', line 40

def from_location(loc)
  path = loc.path.to_s
  {
    "filename" => path,
    "function" => (loc.base_label || loc.label || "<anonymous>").to_s,
    "lineno"   => loc.lineno.to_i,
    "colno"    => 0,
    "in_app"   => in_app?(path),
  }
end

.in_app?(path) ⇒ Boolean

Returns:

  • (Boolean)


69
70
71
72
73
74
75
# File 'lib/tinymon/stacktrace.rb', line 69

def in_app?(path)
  return false if path.nil? || path.empty?
  return false if path.start_with?(INTERNAL_PREFIX)
  return false if path.include?(GEM_MARKER)
  return false if !STDLIB_PATH.empty? && path.start_with?(STDLIB_PATH)
  true
end

.parse(exception) ⇒ Object



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/tinymon/stacktrace.rb', line 19

def parse(exception)
  return [] unless exception

  locations = nil
  begin
    locations = exception.backtrace_locations
  rescue StandardError
    locations = nil
  end

  if locations && !locations.empty?
    frames = locations.map { |loc| from_location(loc) }
  else
    bt = exception.backtrace || []
    frames = bt.map { |line| parse_string(line) }.compact
  end

  # Ruby gives deepest-first; wire format wants deepest-LAST.
  frames.reverse
end

.parse_string(line) ⇒ Object



56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/tinymon/stacktrace.rb', line 56

def parse_string(line)
  m = BT_LINE.match(line.to_s)
  return nil unless m
  filename = m[1]
  {
    "filename" => filename,
    "function" => (m[3] || "<anonymous>"),
    "lineno"   => m[2].to_i,
    "colno"    => 0,
    "in_app"   => in_app?(filename),
  }
end