Class: PartnerService::Reservations::Exports::ErrorHandler

Inherits:
ApplicationService show all
Includes:
ElasticAPM::SpanHelpers
Defined in:
app/services/partner_service/reservations/exports/error_handler.rb

Constant Summary collapse

ERROR_CATEGORIES =

Error categories with specific handling strategies

{
  data_collection: {
    retryable: true,
    max_retries: 3,
    backoff_strategy: :exponential,
    user_message: 'Issue collecting reservation data',
    recovery_suggestions: ['Reduce date range', 'Try again in a few minutes'],
  },
  file_generation: {
    retryable: true,
    max_retries: 2,
    backoff_strategy: :linear,
    user_message: 'Issue generating export file',
    recovery_suggestions: ['Reduce data size', 'Contact support if issue persists'],
  },
  file_upload: {
    retryable: true,
    max_retries: 3,
    backoff_strategy: :exponential,
    user_message: 'Issue uploading export file',
    recovery_suggestions: ['Try again in a few minutes', 'Check storage availability'],
  },
  email_sending: {
    retryable: true,
    max_retries: 2,
    backoff_strategy: :linear,
    user_message: 'Issue sending notification email',
    recovery_suggestions: ['Check email address', 'Try again later'],
  },
  system_error: {
    retryable: false,
    max_retries: 0,
    backoff_strategy: :none,
    user_message: 'System error occurred',
    recovery_suggestions: ['Contact support', 'Try again later'],
  },
}.freeze
EXCEPTION_MAPPINGS =

Specific exception types and their handling

{
  ActiveRecord::ConnectionTimeoutError => :data_collection,
  Mysql2::Error::TimeoutError => :data_collection,
  Timeout::Error => :data_collection,
  Net::ReadTimeout => :data_collection,
  Net::OpenTimeout => :data_collection,
  Errno::ECONNRESET => :file_upload,
  Errno::ECONNREFUSED => :file_upload,
  Aws::S3::Errors::ServiceError => :file_upload,
  Aws::SES::Errors::ServiceError => :email_sending,
  Aws::SES::Errors::Throttling => :email_sending,
  StandardError => :system_error,
}.freeze

Instance Attribute Summary collapse

Attributes inherited from ApplicationService

#object

Instance Method Summary collapse

Methods inherited from ApplicationService

#execute, #execute!

Constructor Details

#initialize(attachment, tracker = nil) ⇒ ErrorHandler

Initialize error handler

Parameters:

  • attachment (Attachment)

    The attachment being processed

  • tracker (TrackingService) (defaults to: nil)

    The tracking service instance



74
75
76
77
78
# File 'app/services/partner_service/reservations/exports/error_handler.rb', line 74

def initialize(attachment, tracker = nil)
  @attachment = attachment
  @tracker = tracker
  @error_history = []
end

Instance Attribute Details

#attachmentObject (readonly)

Returns the value of attribute attachment.



68
69
70
# File 'app/services/partner_service/reservations/exports/error_handler.rb', line 68

def attachment
  @attachment
end

#error_historyObject (readonly)

Returns the value of attribute error_history.



68
69
70
# File 'app/services/partner_service/reservations/exports/error_handler.rb', line 68

def error_history
  @error_history
end

#trackerObject (readonly)

Returns the value of attribute tracker.



68
69
70
# File 'app/services/partner_service/reservations/exports/error_handler.rb', line 68

def tracker
  @tracker
end

Instance Method Details

#error_statisticsHash

Get error statistics for monitoring

Returns:

  • (Hash)

    Error statistics



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'app/services/partner_service/reservations/exports/error_handler.rb', line 126

def error_statistics
  return {} if @error_history.empty?

  stats = {
    total_errors: @error_history.size,
    categories: @error_history.group_by { |e| e[:category] }.transform_values(&:size),
    retryable_errors: @error_history.count { |e| e[:retryable] },
    fatal_errors: @error_history.count { |e| !e[:retryable] },
    first_error_at: @error_history.first[:occurred_at],
    last_error_at: @error_history.last[:occurred_at],
  }

  if stats[:total_errors] > 1
    total_duration = stats[:last_error_at] - stats[:first_error_at]
    stats[:error_frequency] = stats[:total_errors] / [total_duration / 60, 1].max # errors per minute
  end

  stats
end

#handle_error(exception, suggested_category = nil, retry_count: 0, context: {}) ⇒ Hash

Handle an error with appropriate categorization and recovery

Parameters:

  • exception (Exception)

    The exception that occurred

  • suggested_category (Symbol) (defaults to: nil)

    Suggested error category (optional)

  • retry_count (Integer) (defaults to: 0)

    Current retry attempt

  • context (Hash) (defaults to: {})

    Additional context information

Returns:

  • (Hash)

    Error handling result



87
# File 'app/services/partner_service/reservations/exports/error_handler.rb', line 87

span_method :handle_error

#should_abandon_export?Boolean

Check if export should be abandoned due to too many errors

Returns:

  • (Boolean)

    true if export should be abandoned



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'app/services/partner_service/reservations/exports/error_handler.rb', line 149

def should_abandon_export?
  return false if @error_history.empty?

  # Abandon if too many errors in short time period
  recent_errors = @error_history.select { |e| e[:occurred_at] > 10.minutes.ago }
  return true if recent_errors.size >= 5

  # Abandon if too many fatal errors
  fatal_errors = @error_history.count { |e| !e[:retryable] }
  return true if fatal_errors >= 2

  # Abandon if export has been running too long with errors
  if @error_history.first[:occurred_at] < 2.hours.ago
    return true
  end

  false
end