Class: RailsErrorDashboard::Queries::CoOccurringErrors

Inherits:
Object
  • Object
show all
Defined in:
lib/rails_error_dashboard/queries/co_occurring_errors.rb

Overview

Find errors that occur together in time (co-occurring errors)

This query analyzes error occurrences to find patterns of errors that happen within the same time window, which can indicate:

  • Cascading failures (one error causes another)

  • Related errors from the same underlying issue

  • Correlated errors from the same feature/endpoint

Examples:

Find errors that occur with NoMethodError

co_occurring = CoOccurringErrors.call(error_log_id: 123, window_minutes: 5)
co_occurring.each do |result|
  puts "#{result[:error].error_type} occurred #{result[:frequency]} times together"
end

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(error_log_id, window_minutes: 5, min_frequency: 2, limit: 10) ⇒ CoOccurringErrors

Returns a new instance of CoOccurringErrors.



30
31
32
33
34
35
# File 'lib/rails_error_dashboard/queries/co_occurring_errors.rb', line 30

def initialize(error_log_id, window_minutes: 5, min_frequency: 2, limit: 10)
  @error_log_id = error_log_id
  @window_minutes = window_minutes.to_i
  @min_frequency = min_frequency.to_i
  @limit = limit.to_i
end

Class Method Details

.call(error_log_id:, window_minutes: 5, min_frequency: 2, limit: 10) ⇒ Array<Hash>

Find co-occurring errors

Parameters:

  • error_log_id (Integer)

    ID of target error

  • window_minutes (Integer) (defaults to: 5)

    Time window in minutes (default: 5)

  • min_frequency (Integer) (defaults to: 2)

    Minimum co-occurrence count (default: 2)

  • limit (Integer) (defaults to: 10)

    Maximum number of results (default: 10)

Returns:

  • (Array<Hash>)

    Array of ErrorLog, frequency: Integer, avg_delay_seconds: Float



26
27
28
# File 'lib/rails_error_dashboard/queries/co_occurring_errors.rb', line 26

def self.call(error_log_id:, window_minutes: 5, min_frequency: 2, limit: 10)
  new(error_log_id, window_minutes: window_minutes, min_frequency: min_frequency, limit: limit).find_co_occurring
end

Instance Method Details

#find_co_occurringObject



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/rails_error_dashboard/queries/co_occurring_errors.rb', line 37

def find_co_occurring
  target_error = ErrorLog.find_by(id: @error_log_id)
  return [] unless target_error

  # Get all occurrences of the target error
  target_occurrences = ErrorOccurrence.where(error_log_id: @error_log_id)
  return [] if target_occurrences.empty?

  # For each occurrence, find other errors in the time window
  co_occurrence_data = Hash.new { |h, k| h[k] = { count: 0, delays: [] } }

  target_occurrences.find_each do |occurrence|
    window = @window_minutes.minutes
    start_time = occurrence.occurred_at - window
    end_time = occurrence.occurred_at + window

    # Find other error occurrences in this time window
    nearby_occurrences = ErrorOccurrence
                          .in_time_window(start_time, end_time)
                          .where.not(error_log_id: @error_log_id)
                          .includes(:error_log)

    nearby_occurrences.each do |nearby|
      error_log_id = nearby.error_log_id
      co_occurrence_data[error_log_id][:count] += 1

      # Calculate delay (negative = before, positive = after target error)
      delay = (nearby.occurred_at - occurrence.occurred_at).to_f
      co_occurrence_data[error_log_id][:delays] << delay
    end
  end

  # Filter by minimum frequency and build results
  results = co_occurrence_data.select { |_id, data| data[:count] >= @min_frequency }.map do |error_log_id, data|
    error = ErrorLog.find(error_log_id)
    avg_delay = data[:delays].sum / data[:delays].size

    {
      error: error,
      frequency: data[:count],
      avg_delay_seconds: avg_delay.round(2)
    }
  end

  # Sort by frequency (most common first) and limit results
  results.sort_by { |r| -r[:frequency] }.first(@limit)
end