Module: PostHog::ExceptionCapture

Defined in:
lib/posthog/exception_capture.rb

Constant Summary collapse

RUBY_INPUT_FORMAT =
/
  ^ \s* (?: [a-zA-Z]: | uri:classloader: )? ([^:]+ | <.*>):
  (\d+)
  (?: :in\s('|`)(?:([\w:]+)\#)?([^']+)')?$
/x

Class Method Summary collapse

Class Method Details

.add_context_lines(frame, file_path, lineno, context_size = 5) ⇒ Object



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/posthog/exception_capture.rb', line 82

def self.add_context_lines(frame, file_path, lineno, context_size = 5)
  lines = File.readlines(file_path)
  return if lines.empty?

  return unless lineno.positive? && lineno <= lines.length

  pre_context_start = [lineno - context_size, 1].max
  post_context_end = [lineno + context_size, lines.length].min

  frame['context_line'] = lines[lineno - 1].chomp

  frame['pre_context'] = lines[(pre_context_start - 1)...(lineno - 1)].map(&:chomp) if pre_context_start < lineno

  frame['post_context'] = lines[lineno...(post_context_end)].map(&:chomp) if post_context_end > lineno
rescue StandardError
  # Silently ignore file read errors
end

.build_parsed_exception(value) ⇒ Object



21
22
23
24
25
26
# File 'lib/posthog/exception_capture.rb', line 21

def self.build_parsed_exception(value)
  title, message, backtrace = coerce_exception_input(value)
  return nil if title.nil?

  build_single_exception_from_data(title, message, backtrace)
end

.build_single_exception_from_data(title, message, backtrace) ⇒ Object



28
29
30
31
32
33
34
35
36
37
38
# File 'lib/posthog/exception_capture.rb', line 28

def self.build_single_exception_from_data(title, message, backtrace)
  {
    'type' => title,
    'value' => message || '',
    'mechanism' => {
      'type' => 'generic',
      'handled' => true
    },
    'stacktrace' => build_stacktrace(backtrace)
  }
end

.build_stacktrace(backtrace) ⇒ Object



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

def self.build_stacktrace(backtrace)
  return nil unless backtrace && !backtrace.empty?

  frames = backtrace.first(50).map do |line|
    parse_backtrace_line(line)
  end.compact.reverse

  {
    'type' => 'raw',
    'frames' => frames
  }
end

.coerce_exception_input(value) ⇒ Object



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/posthog/exception_capture.rb', line 100

def self.coerce_exception_input(value)
  if value.is_a?(String)
    title = 'Error'
    message = value
    backtrace = nil
  elsif value.respond_to?(:backtrace) && value.respond_to?(:message)
    title = value.class.to_s
    message = value.message || ''
    backtrace = value.backtrace
  else
    return [nil, nil, nil]
  end

  [title, message, backtrace]
end

.gem_path?(path) ⇒ Boolean



75
76
77
78
79
80
# File 'lib/posthog/exception_capture.rb', line 75

def self.gem_path?(path)
  path.include?('/gems/') ||
    path.include?('/ruby/') ||
    path.include?('/.rbenv/') ||
    path.include?('/.rvm/')
end

.parse_backtrace_line(line) ⇒ Object



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/posthog/exception_capture.rb', line 53

def self.parse_backtrace_line(line)
  match = line.match(RUBY_INPUT_FORMAT)
  return nil unless match

  file = match[1]
  lineno = match[2].to_i
  method_name = match[5]

  frame = {
    'filename' => File.basename(file),
    'abs_path' => file,
    'lineno' => lineno,
    'function' => method_name,
    'in_app' => !gem_path?(file),
    'platform' => 'ruby'
  }

  add_context_lines(frame, file, lineno) if File.exist?(file)

  frame
end