Module: ExceptionHandling

Defined in:
lib/exception_handling.rb,
lib/exception_handling/mailer.rb,
lib/exception_handling/methods.rb,
lib/exception_handling/testing.rb,
lib/exception_handling/version.rb

Overview

some useful test objects

Defined Under Namespace

Modules: Methods, Testing Classes: ClientLoggingError, ExceptionFilters, Mailer, MailerTimeout, Warning

Constant Summary collapse

SUMMARY_THRESHOLD =
5
SUMMARY_PERIOD =

1.hour

60*60
SECTIONS =
[:request, :session, :environment, :backtrace, :event_response]
ENVIRONMENT_WHITELIST =
[
/^HTTP_/,
/^QUERY_/,
/^REQUEST_/,
/^SERVER_/
]
ENVIRONMENT_OMIT =
(
"CONTENT_TYPE: application/x-www-form-urlencoded\nGATEWAY_INTERFACE: CGI/1.2\nHTTP_ACCEPT: */*\nHTTP_ACCEPT: */*, text/javascript, text/html, application/xml, text/xml, */*\nHTTP_ACCEPT_CHARSET: ISO-8859-1,utf-8;q=0.7,*;q=0.7\nHTTP_ACCEPT_ENCODING: gzip, deflate\nHTTP_ACCEPT_ENCODING: gzip,deflate\nHTTP_ACCEPT_LANGUAGE: en-us\nHTTP_CACHE_CONTROL: no-cache\nHTTP_CONNECTION: Keep-Alive\nHTTP_HOST: www.invoca.com\nHTTP_MAX_FORWARDS: 10\nHTTP_UA_CPU: x86\nHTTP_VERSION: HTTP/1.1\nHTTP_X_FORWARDED_HOST: www.invoca.com\nHTTP_X_FORWARDED_SERVER: www2.invoca.com\nHTTP_X_REQUESTED_WITH: XMLHttpRequest\nLANG:\nLANG:\nPATH: /sbin:/usr/sbin:/bin:/usr/bin\nPWD: /\nRAILS_ENV: production\nRAW_POST_DATA: id=500\nREMOTE_ADDR: 10.251.34.225\nSCRIPT_NAME: /\nSERVER_NAME: www.invoca.com\nSERVER_PORT: 80\nSERVER_PROTOCOL: HTTP/1.1\nSERVER_SOFTWARE: Mongrel 1.1.4\nSHLVL: 1\nTERM: linux\nTERM: xterm-color\n_: /usr/bin/mongrel_cluster_ctl\n"
).split("\n")
AUTHENTICATION_HEADERS =
['HTTP_AUTHORIZATION','X-HTTP_AUTHORIZATION','X_HTTP_AUTHORIZATION','REDIRECT_X_HTTP_AUTHORIZATION']
VERSION =
"1.0.3"

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.current_controllerObject

internal settings (don’t set directly)



139
140
141
# File 'lib/exception_handling.rb', line 139

def current_controller
  @current_controller
end

.custom_data_hookObject

Returns the value of attribute custom_data_hook.



106
107
108
# File 'lib/exception_handling.rb', line 106

def custom_data_hook
  @custom_data_hook
end

.email_environmentObject

Returns the value of attribute email_environment.



101
102
103
# File 'lib/exception_handling.rb', line 101

def email_environment
  @email_environment
end

.escalation_recipientsObject

optional settings



100
101
102
# File 'lib/exception_handling.rb', line 100

def escalation_recipients
  @escalation_recipients
end

.eventmachine_safeObject

Returns the value of attribute eventmachine_safe.



104
105
106
# File 'lib/exception_handling.rb', line 104

def eventmachine_safe
  @eventmachine_safe
end

.eventmachine_synchronyObject

Returns the value of attribute eventmachine_synchrony.



105
106
107
# File 'lib/exception_handling.rb', line 105

def eventmachine_synchrony
  @eventmachine_synchrony
end

.exception_recipientsObject

Returns the value of attribute exception_recipients.



78
79
80
# File 'lib/exception_handling.rb', line 78

def exception_recipients
  @exception_recipients
end

.filter_list_filenameObject

Returns the value of attribute filter_list_filename.



102
103
104
# File 'lib/exception_handling.rb', line 102

def filter_list_filename
  @filter_list_filename
end

.last_exception_timestampObject

Returns the value of attribute last_exception_timestamp.



140
141
142
# File 'lib/exception_handling.rb', line 140

def last_exception_timestamp
  @last_exception_timestamp
end

.loggerObject

Returns the value of attribute logger.



79
80
81
# File 'lib/exception_handling.rb', line 79

def logger
  @logger
end

.mailer_send_enabledObject

Returns the value of attribute mailer_send_enabled.



103
104
105
# File 'lib/exception_handling.rb', line 103

def mailer_send_enabled
  @mailer_send_enabled
end

.periodic_exception_intervalsObject

Returns the value of attribute periodic_exception_intervals.



141
142
143
# File 'lib/exception_handling.rb', line 141

def periodic_exception_intervals
  @periodic_exception_intervals
end

.post_log_error_hookObject

Returns the value of attribute post_log_error_hook.



107
108
109
# File 'lib/exception_handling.rb', line 107

def post_log_error_hook
  @post_log_error_hook
end

.sender_addressObject

Returns the value of attribute sender_address.



77
78
79
# File 'lib/exception_handling.rb', line 77

def sender_address
  @sender_address
end

.server_nameObject

required settings



76
77
78
# File 'lib/exception_handling.rb', line 76

def server_name
  @server_name
end

.stub_handlerObject

Returns the value of attribute stub_handler.



108
109
110
# File 'lib/exception_handling.rb', line 108

def stub_handler
  @stub_handler
end

Class Method Details

.enhance_exception_data(data) ⇒ Object



335
336
337
338
339
340
341
342
343
# File 'lib/exception_handling.rb', line 335

def enhance_exception_data(data)
  return if ! ExceptionHandling.custom_data_hook
  begin
    ExceptionHandling.custom_data_hook.call(data)
  rescue Exception => ex
    # can't call log_error here or we will blow the call stack
    log_info( "Unable to execute custom custom_data_hook callback. #{ex.message} #{ex.backtrace.each {|l| "#{l}\n"}}" )
  end
end

.ensure_completely_safe(exception_context = "") ⇒ Object



280
281
282
283
284
285
286
# File 'lib/exception_handling.rb', line 280

def ensure_completely_safe( exception_context = "" )
  yield
rescue SystemExit, SystemStackError, NoMemoryError, SecurityError, SignalException
  raise
rescue Exception => ex
  log_error ex, exception_context
end

.ensure_escalation(email_subject) ⇒ Object



300
301
302
303
304
305
306
307
# File 'lib/exception_handling.rb', line 300

def ensure_escalation(email_subject)
  begin
    yield
  rescue => ex
    escalate_error(ex, email_subject)
    nil
  end
end

.ensure_safe(exception_context = "") ⇒ Object



273
274
275
276
277
278
# File 'lib/exception_handling.rb', line 273

def ensure_safe( exception_context = "" )
  yield
rescue => ex
  log_error ex, exception_context
  return nil
end

.escalate_error(exception_or_string, email_subject) ⇒ Object



288
289
290
291
292
# File 'lib/exception_handling.rb', line 288

def escalate_error(exception_or_string, email_subject)
  ex = make_exception(exception_or_string)
  log_error(ex)
  escalate(email_subject, ex, ExceptionHandling.last_exception_timestamp)
end

.escalate_warning(message, email_subject) ⇒ Object



294
295
296
297
298
# File 'lib/exception_handling.rb', line 294

def escalate_warning(message, email_subject)
  ex = Warning.new(message)
  log_error(ex)
  escalate(email_subject, ex, ExceptionHandling.last_exception_timestamp)
end

.extract_and_merge_controller_data(controller, data) ⇒ Object

Pull certain fields out of the controller and add to the data hash.



246
247
248
249
250
251
252
253
254
255
256
257
258
259
# File 'lib/exception_handling.rb', line 246

def extract_and_merge_controller_data(controller, data)
  data[:request] = {
    :params      => controller.request.parameters.to_hash,
    :rails_root  => defined?(Rails) ? Rails.root : "Rails.root not defined. Is this a test environment?",
    :url         => controller.complete_request_uri
  }
  data[:environment].merge!(controller.request.env.to_hash)

  controller.session[:fault_in_session]
  data[:session] = {
    :key         => controller.request.session_options[:id],
    :data        => controller.session.dup
  }
end

.log_debug(message) ⇒ Object



269
270
271
# File 'lib/exception_handling.rb', line 269

def log_debug( message )
  ExceptionHandling.logger.debug( message )
end

.log_error(exception_or_string, exception_context = '', controller = nil, treat_as_local = false) ⇒ Object

Normal Operation:

Called directly by our code, usually from rescue blocks.
Does two things: write to log file and send an email

Functional Test Operation:

Calls into handle_stub_log_error and returns. no log file. no email.


184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/exception_handling.rb', line 184

def log_error(exception_or_string, exception_context = '', controller = nil, treat_as_local = false)
  begin
    ex = make_exception(exception_or_string)
    timestamp = set_log_error_timestamp
    data = exception_to_data(ex, exception_context, timestamp)

    if stub_handler
      return stub_handler.handle_stub_log_error(data)
    end

    write_exception_to_log(ex, exception_context, timestamp)

    if treat_as_local
      return
    end

    if should_send_email?
      controller ||= current_controller

      if block_given?
        # the expectation is that if the caller passed a block then they will be
        # doing their own merge of hash values into data
        begin
          yield data
        rescue Exception => ex
          data.merge!(:environment => "Exception in yield: #{ex.class}:#{ex}")
        end
      elsif controller
      # most of the time though, this method will not get passed a block
      # and additional hash data is extracted from the controller
        extract_and_merge_controller_data(controller, data)
      end

      log_error_email(data, ex)
    end

  rescue LogErrorStub::UnexpectedExceptionLogged, LogErrorStub::ExpectedExceptionNotLogged
    raise
  rescue Exception => ex
    $stderr.puts("ExceptionHandling.log_error rescued exception while logging #{exception_context}: #{exception_or_string}:\n#{ex.class}: #{ex}\n#{ex.backtrace.join("\n")}")
    write_exception_to_log(ex, "ExceptionHandling.log_error rescued exception while logging #{exception_context}: #{exception_or_string}", timestamp)
  end
end

.log_error_rack(exception, env, rack_filter) ⇒ Object

Gets called by Rack Middleware: DebugExceptions or ShowExceptions it does 2 things:

log the error
email the error

but not during functional tests, when rack middleware is not used



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/exception_handling.rb', line 151

def log_error_rack(exception, env, rack_filter)
  timestamp = set_log_error_timestamp
  exception_data = exception_to_data(exception, env, timestamp)

  if stub_handler
    return stub_handler.handle_stub_log_error(exception_data)
  end

  # TODO: add a more interesting custom description, like:
  # custom_description = ": caught and processed by Rack middleware filter #{rack_filter}"
  # which would be nice, but would also require changing quite a few tests
  custom_description = ""
  write_exception_to_log(exception, custom_description, timestamp)

  if should_send_email?
    controller = env['action_controller.instance']
    # controller may not exist in some cases (like most 404 errors)
    if controller
      extract_and_merge_controller_data(controller, exception_data)
      controller.session["last_exception_timestamp"] = ExceptionHandling.last_exception_timestamp
    end
    log_error_email(exception_data, exception)
  end
end

.log_info(message) ⇒ Object



265
266
267
# File 'lib/exception_handling.rb', line 265

def log_info( message )
  ExceptionHandling.logger.info( message )
end

.log_periodically(exception_key, interval, message) ⇒ Object



326
327
328
329
330
331
332
333
# File 'lib/exception_handling.rb', line 326

def log_periodically(exception_key, interval, message)
  self.periodic_exception_intervals ||= {}
  last_logged = self.periodic_exception_intervals[exception_key]
  if !last_logged || ( (last_logged + interval) < Time.now )
    log_error( message )
    self.periodic_exception_intervals[exception_key] = Time.now
  end
end

.log_warning(message) ⇒ Object



261
262
263
# File 'lib/exception_handling.rb', line 261

def log_warning( message )
  log_error( Warning.new(message) )
end

.set_log_error_timestampObject



309
310
311
# File 'lib/exception_handling.rb', line 309

def set_log_error_timestamp
  ExceptionHandling.last_exception_timestamp = Time.now.to_i
end

.should_send_email?Boolean

Returns:

  • (Boolean)


313
314
315
# File 'lib/exception_handling.rb', line 313

def should_send_email?
  ExceptionHandling.mailer_send_enabled
end

.trace_timing(description) ⇒ Object



317
318
319
320
321
322
323
324
# File 'lib/exception_handling.rb', line 317

def trace_timing(description)
  result = nil
  time = Benchmark.measure do
    result = yield
  end
  log_info "#{description} %.4fs  " % time.real
  result
end

.write_exception_to_log(ex, exception_context, timestamp) ⇒ Object

Write an exception out to the log file using our own custom format.



231
232
233
234
235
236
237
238
239
240
241
# File 'lib/exception_handling.rb', line 231

def write_exception_to_log(ex, exception_context, timestamp)
  ActiveSupport::Deprecation.silence do
    ExceptionHandling.logger.fatal(
      if ActionView::TemplateError === ex
        "#{ex} Error:#{timestamp}"
      else
        "\n(Error:#{timestamp}) #{ex.class} #{exception_context} (#{ex.message}):\n  " + clean_backtrace(ex).join("\n  ") + "\n\n"
      end
    )
  end
end