Class: RailsErrorDashboard::ErrorReporter

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

Instance Method Summary collapse

Instance Method Details

#report(error, handled:, severity:, context:, source: nil) ⇒ Object



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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
# File 'lib/rails_error_dashboard/error_reporter.rb', line 21

def report(error, handled:, severity:, context:, source: nil)
  # Skip low-severity warnings
  return if handled && severity == :warning

  # CRITICAL: Wrap entire process in rescue to ensure failures don't break the app
  begin
    # Enrich context with request data from Thread.current when available.
    # Rails internals (ActionDispatch::Executor) report errors with
    # source: "application.action_dispatch" but pass NO request object,
    # resulting in placeholder values ("Rails Application", "{}", etc.).
    # Our middleware stores the Rack env in Thread.current so we can
    # build a proper request here — fixing issue #106.
    if context[:request].nil? && Thread.current[:rails_error_dashboard_request_env]
      env = Thread.current[:rails_error_dashboard_request_env]
      context = context.merge(request: ActionDispatch::Request.new(env))
    end

    # Skip duplicate reports from our own middleware when the subscriber
    # already captured this error with full request context above.
    # Without this, async logging enqueues two jobs for one exception —
    # and non-deterministic job ordering can overwrite good data.
    if source == "rack.middleware" &&
       Thread.current[:rails_error_dashboard_reported_errors]&.include?(error.object_id)
      return
    end

    # Track that we've reported this error (for dedup with middleware)
    if Thread.current[:rails_error_dashboard_request_env]
      Thread.current[:rails_error_dashboard_reported_errors] ||= Set.new
      Thread.current[:rails_error_dashboard_reported_errors].add(error.object_id)
    end

    # Extract context information
    error_context = ValueObjects::ErrorContext.new(context, source)

    # Log to our error dashboard using Command
    Commands::LogError.call(error, error_context.to_h.merge(source: source))
  rescue => e
    # Don't let error logging cause more errors - fail silently
    # Log failure for debugging but NEVER propagate exception
    RailsErrorDashboard::Logger.error("[RailsErrorDashboard] ErrorReporter failed: #{e.class} - #{e.message}")
    RailsErrorDashboard::Logger.error("Original error: #{error.class} - #{error.message}") if error
    RailsErrorDashboard::Logger.error("Context: #{context.inspect}") if context
    RailsErrorDashboard::Logger.error(e.backtrace&.first(5)&.join("\n")) if e.backtrace
    nil # Explicitly return nil, never raise
  end
end