Class: RailsErrorDashboard::Services::ErrorHashGenerator

Inherits:
Object
  • Object
show all
Defined in:
lib/rails_error_dashboard/services/error_hash_generator.rb

Overview

Pure algorithm: Generate consistent hash for error deduplication

No database access — accepts exception data, returns a hash string. Same hash = same error type for grouping purposes.

Two entry points:

  • ‘.call(exception, …)` — used by LogError command (exception-based)

  • ‘.from_attributes(…)` — used by ErrorLog model callback (attribute-based)

Examples:

ErrorHashGenerator.call(exception, controller_name: "users", action_name: "show", application_id: 1)
# => "a1b2c3d4e5f6g7h8"

Class Method Summary collapse

Class Method Details

.call(exception, controller_name: nil, action_name: nil, application_id: nil) ⇒ String

Generate hash from an exception object (used by LogError command)

Parameters:

  • exception (Exception)

    The exception to hash

  • controller_name (String, nil) (defaults to: nil)

    Controller context

  • action_name (String, nil) (defaults to: nil)

    Action context

  • application_id (Integer, nil) (defaults to: nil)

    Application for per-app deduplication

Returns:

  • (String)

    16-character hex hash



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/rails_error_dashboard/services/error_hash_generator.rb', line 24

def self.call(exception, controller_name: nil, action_name: nil, application_id: nil)
  normalized_message = normalize_message(exception.message)
  file_path = extract_app_frame(exception.backtrace)

  digest_input = [
    exception.class.name,
    normalized_message,
    file_path,
    controller_name,
    action_name,
    application_id.to_s
  ].compact.join("|")

  Digest::SHA256.hexdigest(digest_input)[0..15]
end

.extract_app_frame(backtrace) ⇒ String?

Extract first meaningful app code frame from backtrace

Parameters:

  • backtrace (Array<String>, nil)

    Exception backtrace

Returns:

  • (String, nil)

    File path of first app code frame



80
81
82
83
84
85
86
87
88
# File 'lib/rails_error_dashboard/services/error_hash_generator.rb', line 80

def self.extract_app_frame(backtrace)
  return nil if backtrace.nil?

  first_app_frame = backtrace.find { |frame|
    !frame.include?("/gems/")
  }

  first_app_frame&.split(":")&.first
end

.from_attributes(error_type:, message: nil, backtrace: nil, controller_name: nil, action_name: nil, application_id: nil) ⇒ String

Generate hash from error attributes (used by ErrorLog model callback) Uses ErrorNormalizer for smarter normalization and significant frame extraction

Parameters:

  • error_type (String)

    The error class name

  • message (String, nil) (defaults to: nil)

    The error message

  • backtrace (String, nil) (defaults to: nil)

    The backtrace as a string

  • controller_name (String, nil) (defaults to: nil)

    Controller context

  • action_name (String, nil) (defaults to: nil)

    Action context

  • application_id (Integer, nil) (defaults to: nil)

    Application for per-app deduplication

Returns:

  • (String)

    16-character hex hash



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/rails_error_dashboard/services/error_hash_generator.rb', line 49

def self.from_attributes(error_type:, message: nil, backtrace: nil, controller_name: nil, action_name: nil, application_id: nil)
  normalized_message = ErrorNormalizer.normalize(message)
  significant_frames = ErrorNormalizer.extract_significant_frames(backtrace, count: 3)

  digest_input = [
    error_type,
    normalized_message,
    significant_frames,
    controller_name,
    action_name,
    application_id.to_s
  ].compact.join("|")

  Digest::SHA256.hexdigest(digest_input)[0..15]
end

.normalize_message(message) ⇒ String?

Normalize dynamic values in error messages for consistent hashing

Parameters:

  • message (String, nil)

    The error message

Returns:

  • (String, nil)

    Normalized message



68
69
70
71
72
73
74
75
# File 'lib/rails_error_dashboard/services/error_hash_generator.rb', line 68

def self.normalize_message(message)
  message
    &.gsub(/0x[0-9a-f]+/i, "HEX")          # Replace hex addresses (before numbers)
    &.gsub(/#<[^>]+>/, "#<OBJ>")           # Replace object inspections
    &.gsub(/\d+/, "N")                     # Replace numbers
    &.gsub(/"[^"]*"/, '""')                # Replace double-quoted strings
    &.gsub(/'[^']*'/, "''")                # Replace single-quoted strings
end