Module: PostHog::ExceptionCapture Private
- Defined in:
- lib/posthog/exception_capture.rb
Overview
This module is part of a private API. You should avoid using this module if possible, as it may be removed or be changed in the future.
Builds PostHog exception payloads from Ruby exception objects.
Constant Summary collapse
- RUBY_INPUT_FORMAT =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
/ ^ \s* (?: [a-zA-Z]: | uri:classloader: )? ([^:]+ | <.*>): (\d+) (?: :in\s('|`)(?:([\w:]+)\#)?([^']+)')?$ /x- MAX_CHAINED_EXCEPTIONS =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
Maximum number of exceptions extracted from a single
causechain. 50- DEFAULT_MECHANISM =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
{ 'type' => 'generic', 'handled' => true }.freeze
Class Method Summary collapse
- .add_context_lines(frame, file_path, lineno, context_size = 5) ⇒ void private
-
.build_exception_list(value, mechanism: nil) ⇒ Array<Hash>?
private
Builds the
$exception_listpayload for an exception, walking itscausechain outermost-first (wrapper first, root cause last). -
.build_parsed_exception(value, mechanism: nil) ⇒ Hash?
private
Parsed exception payload, or nil when the input is unsupported.
- .build_single_exception_from_data(title, message, backtrace, mechanism: nil) ⇒ Hash private
- .build_stacktrace(backtrace) ⇒ Hash? private
- .chain_mechanism(root_mechanism, exception_id) ⇒ Hash private
-
.coerce_exception_input(value) ⇒ Array
private
Three-item array of title, message, and backtrace.
-
.dependency_roots(project_root = self.project_root) ⇒ Array<String>
private
Directories containing installed gems and the Ruby stdlib.
-
.frame_filename(path, project_root) ⇒ String
private
Stable filename used for fingerprinting: the project-relative path when the file lives inside the project root, its basename otherwise.
-
.gem_path?(path, dependency_roots = self.dependency_roots) ⇒ Boolean
private
Whether the path belongs to an installed gem or the Ruby standard library, based on gem install locations rather than path substrings.
- .parse_backtrace_line(line, project_root: self.project_root, dependency_roots: nil) ⇒ Hash? private
- .path_within?(path, root) ⇒ Boolean private
-
.project_root ⇒ String
private
Root directory the application runs from, used to derive stable project-relative filenames from per-host deploy paths (e.g.
/app/releases/20240101/...).
Class Method Details
.add_context_lines(frame, file_path, lineno, context_size = 5) ⇒ void
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
This method returns an undefined value.
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 |
# File 'lib/posthog/exception_capture.rb', line 210 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_exception_list(value, mechanism: nil) ⇒ Array<Hash>?
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Builds the $exception_list payload for an exception, walking its
cause chain outermost-first (wrapper first, root cause last).
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/posthog/exception_capture.rb', line 38 def self.build_exception_list(value, mechanism: nil) root_mechanism = DEFAULT_MECHANISM.merge(mechanism || {}) exceptions = [] seen = {}.compare_by_identity current = value while current && exceptions.length < MAX_CHAINED_EXCEPTIONS && !seen.key?(current) parsed = build_parsed_exception(current, mechanism: chain_mechanism(root_mechanism, exceptions.length)) break if parsed.nil? exceptions << parsed seen[current] = true current = current.respond_to?(:cause) ? current.cause : nil end exceptions.empty? ? nil : exceptions end |
.build_parsed_exception(value, mechanism: nil) ⇒ Hash?
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns Parsed exception payload, or nil when the input is unsupported.
74 75 76 77 78 79 |
# File 'lib/posthog/exception_capture.rb', line 74 def self.build_parsed_exception(value, mechanism: nil) title, , backtrace = coerce_exception_input(value) return nil if title.nil? build_single_exception_from_data(title, , backtrace, mechanism: mechanism) end |
.build_single_exception_from_data(title, message, backtrace, mechanism: nil) ⇒ Hash
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
86 87 88 89 90 91 92 93 |
# File 'lib/posthog/exception_capture.rb', line 86 def self.build_single_exception_from_data(title, , backtrace, mechanism: nil) { 'type' => title, 'value' => || '', 'mechanism' => DEFAULT_MECHANISM.merge(mechanism || {}), 'stacktrace' => build_stacktrace(backtrace) } end |
.build_stacktrace(backtrace) ⇒ Hash?
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/posthog/exception_capture.rb', line 97 def self.build_stacktrace(backtrace) return nil unless backtrace && !backtrace.empty? root = project_root roots = dependency_roots(root) frames = backtrace.first(50).map do |line| parse_backtrace_line(line, project_root: root, dependency_roots: roots) end.compact.reverse { 'type' => 'raw', 'frames' => frames } end |
.chain_mechanism(root_mechanism, exception_id) ⇒ Hash
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
60 61 62 63 64 65 66 67 68 69 |
# File 'lib/posthog/exception_capture.rb', line 60 def self.chain_mechanism(root_mechanism, exception_id) mechanism = root_mechanism.merge('exception_id' => exception_id) return mechanism if exception_id.zero? mechanism.merge( 'type' => 'chained', 'source' => 'cause', 'parent_id' => exception_id - 1 ) end |
.coerce_exception_input(value) ⇒ Array
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns Three-item array of title, message, and backtrace.
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 |
# File 'lib/posthog/exception_capture.rb', line 230 def self.coerce_exception_input(value) if value.is_a?(String) title = 'Error' = value backtrace = nil elsif value.respond_to?(:backtrace) && value.respond_to?(:message) title = value.class.to_s = value. || '' backtrace = value.backtrace else return [nil, nil, nil] end [title, , backtrace] end |
.dependency_roots(project_root = self.project_root) ⇒ Array<String>
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns Directories containing installed gems and the Ruby stdlib.
182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/posthog/exception_capture.rb', line 182 def self.dependency_roots(project_root = self.project_root) roots = [] if defined?(Gem) roots.concat(Gem.path) if Gem.respond_to?(:path) roots << Gem.default_dir if Gem.respond_to?(:default_dir) roots.concat(Gem.loaded_specs.each_value.map(&:full_gem_path)) if Gem.respond_to?(:loaded_specs) end roots << RbConfig::CONFIG['rubylibprefix'] if defined?(RbConfig) # The project itself can be a loaded spec (e.g. a gem developed in place, # or a Rails engine); its frames are still application code. roots.compact.reject(&:empty?).uniq - [project_root] rescue StandardError [] end |
.frame_filename(path, project_root) ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Stable filename used for fingerprinting: the project-relative path when
the file lives inside the project root, its basename otherwise. The raw
absolute path is kept separately in abs_path.
160 161 162 163 164 165 166 167 168 |
# File 'lib/posthog/exception_capture.rb', line 160 def self.frame_filename(path, project_root) if project_root && !project_root.empty? && path_within?(path, project_root) relative = path[project_root.length..] relative = relative[1..] while relative.start_with?(File::SEPARATOR) return relative unless relative.empty? end File.basename(path) end |
.gem_path?(path, dependency_roots = self.dependency_roots) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Whether the path belongs to an installed gem or the Ruby standard library, based on gem install locations rather than path substrings.
176 177 178 |
# File 'lib/posthog/exception_capture.rb', line 176 def self.gem_path?(path, dependency_roots = self.dependency_roots) dependency_roots.any? { |root| path_within?(path, root) } end |
.parse_backtrace_line(line, project_root: self.project_root, dependency_roots: nil) ⇒ Hash?
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/posthog/exception_capture.rb', line 116 def self.parse_backtrace_line(line, project_root: self.project_root, dependency_roots: nil) match = line.match(RUBY_INPUT_FORMAT) return nil unless match file = match[1] lineno = match[2].to_i method_name = match[5] frame = { 'filename' => frame_filename(file, project_root), 'abs_path' => file, 'lineno' => lineno, 'function' => method_name, 'in_app' => !gem_path?(file, dependency_roots || self.dependency_roots(project_root)), 'platform' => 'ruby' } add_context_lines(frame, file, lineno) if frame['in_app'] && File.exist?(file) frame end |
.path_within?(path, root) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
200 201 202 203 |
# File 'lib/posthog/exception_capture.rb', line 200 def self.path_within?(path, root) root = root.chomp(File::SEPARATOR) path == root || path.start_with?("#{root}#{File::SEPARATOR}") end |
.project_root ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Root directory the application runs from, used to derive stable
project-relative filenames from per-host deploy paths
(e.g. /app/releases/20240101/...).
143 144 145 146 147 148 149 150 151 |
# File 'lib/posthog/exception_capture.rb', line 143 def self.project_root if defined?(::Rails) && ::Rails.respond_to?(:root) && ::Rails.root ::Rails.root.to_s else Dir.pwd end rescue StandardError Dir.pwd end |