Class: Sentiero::Analytics::ErrorCollector

Inherits:
Object
  • Object
show all
Includes:
Bounded, Events, Stats
Defined in:
lib/sentiero/analytics/collectors/error_collector.rb

Overview

Per-URL error grouping across page segments. Groups JS errors by a normalized key (see group_key) so messages differing only by an id/count/line number collapse into one row. Each occurrence records offset_ms from the window's first event so the UI can deep-link via ?t=. The three helpers are class methods so ErrorDiscovery can reuse them with its own group shape without instantiating an accumulator.

Constant Summary collapse

ERROR_TAG =
"error"
MAX_KEY_LENGTH =
200

Constants included from Events

Sentiero::Analytics::Events::CUSTOM, Sentiero::Analytics::Events::INCREMENTAL, Sentiero::Analytics::Events::META, Sentiero::Analytics::Events::SOURCE_INPUT, Sentiero::Analytics::Events::SOURCE_MOUSE_INTERACTION, Sentiero::Analytics::Events::SOURCE_SCROLL

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Bounded

#bounded_fetch, #bounded_increment

Methods included from Stats

#mean, #offset_ms, #percentile, #top_counts

Constructor Details

#initialize(max_groups: nil, max_occurrences: nil) ⇒ ErrorCollector

Integer caps distinct groups (flips #capped) / occurrences per group; nil unbounded.



26
27
28
29
30
31
# File 'lib/sentiero/analytics/collectors/error_collector.rb', line 26

def initialize(max_groups: nil, max_occurrences: nil)
  @max_groups = max_groups
  @max_occurrences = max_occurrences
  @groups = {}
  @capped = false
end

Instance Attribute Details

#cappedObject (readonly)

Returns the value of attribute capped.



23
24
25
# File 'lib/sentiero/analytics/collectors/error_collector.rb', line 23

def capped
  @capped
end

#groupsObject (readonly)

Returns the value of attribute groups.



23
24
25
# File 'lib/sentiero/analytics/collectors/error_collector.rb', line 23

def groups
  @groups
end

Class Method Details

.error_event?(event) ⇒ Boolean

Returns:

  • (Boolean)


69
70
71
72
73
74
# File 'lib/sentiero/analytics/collectors/error_collector.rb', line 69

def self.error_event?(event)
  return false unless event["type"] == CUSTOM

  data = event["data"]
  data.is_a?(Hash) && data["tag"] == ERROR_TAG
end

.extract_message(event) ⇒ Object



80
81
82
83
84
85
86
# File 'lib/sentiero/analytics/collectors/error_collector.rb', line 80

def self.extract_message(event)
  payload = event.dig("data", "payload")
  message = payload.is_a?(Hash) ? payload["message"] : nil
  return "Unknown error" if message.nil? || message.to_s.strip.empty?

  message.to_s
end

.group_key(message) ⇒ Object



76
77
78
# File 'lib/sentiero/analytics/collectors/error_collector.rb', line 76

def self.group_key(message)
  message.lines.first.to_s.strip.gsub(/\d+/, "#")[0, MAX_KEY_LENGTH]
end

Instance Method Details

#collect(segment, session_id:, window_id:, anchor:) ⇒ Object

anchor is the window's first-event timestamp; offset_ms is relative to it so replay ?t= deep-links stay consistent across segments of the window.



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/sentiero/analytics/collectors/error_collector.rb', line 35

def collect(segment, session_id:, window_id:, anchor:)
  segment.each do |event|
    next unless self.class.error_event?(event)

    message = self.class.extract_message(event)
    key = self.class.group_key(message)

    group = bounded_fetch(@groups, key, @max_groups) { {message: message, count: 0, occurrences: []} }
    if group.nil?
      @capped = true
      next
    end

    group[:count] += 1

    if @max_occurrences.nil? || group[:occurrences].size < @max_occurrences
      group[:occurrences] << {
        session_id: session_id,
        window_id: window_id,
        offset_ms: offset_ms(anchor, event["timestamp"])
      }
    end
  end
end

#summarizeObject



60
61
62
63
64
65
66
67
# File 'lib/sentiero/analytics/collectors/error_collector.rb', line 60

def summarize
  {
    groups: @groups.values
      .sort_by { |g| -g[:count] }
      .map { |g| {message: g[:message], count: g[:count], occurrences: g[:occurrences]} },
    total: @groups.values.sum { |g| g[:count] }
  }
end