Module: ExceptionHandling

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

Overview

some useful test objects

Defined Under Namespace

Modules: Methods, Sensu, Testing Classes: ClientLoggingError, ExceptionCatalog, ExceptionDescription, 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 =
(
<<EOF
CONTENT_TYPE: application/x-www-form-urlencoded
GATEWAY_INTERFACE: CGI/1.2
HTTP_ACCEPT: */*
HTTP_ACCEPT: */*, text/javascript, text/html, application/xml, text/xml, */*
HTTP_ACCEPT_CHARSET: ISO-8859-1,utf-8;q=0.7,*;q=0.7
HTTP_ACCEPT_ENCODING: gzip, deflate
HTTP_ACCEPT_ENCODING: gzip,deflate
HTTP_ACCEPT_LANGUAGE: en-us
HTTP_CACHE_CONTROL: no-cache
HTTP_CONNECTION: Keep-Alive
HTTP_HOST: www.invoca.com
HTTP_MAX_FORWARDS: 10
HTTP_UA_CPU: x86
HTTP_VERSION: HTTP/1.1
HTTP_X_FORWARDED_HOST: www.invoca.com
HTTP_X_FORWARDED_SERVER: www2.invoca.com
HTTP_X_REQUESTED_WITH: XMLHttpRequest
LANG:
LANG:
PATH: /sbin:/usr/sbin:/bin:/usr/bin
PWD: /
RAILS_ENV: production
RAW_POST_DATA: id=500
REMOTE_ADDR: 10.251.34.225
SCRIPT_NAME: /
SERVER_NAME: www.invoca.com
SERVER_PORT: 80
SERVER_PROTOCOL: HTTP/1.1
SERVER_SOFTWARE: Mongrel 1.1.4
SHLVL: 1
TERM: linux
TERM: xterm-color
_: /usr/bin/mongrel_cluster_ctl
EOF
).split("\n")
AUTHENTICATION_HEADERS =
['HTTP_AUTHORIZATION','X-HTTP_AUTHORIZATION','X_HTTP_AUTHORIZATION','REDIRECT_X_HTTP_AUTHORIZATION']
VERSION =
"1.1.0"

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.current_controllerObject

internal settings (don’t set directly)



148
149
150
# File 'lib/exception_handling.rb', line 148

def current_controller
  @current_controller
end

.custom_data_hookObject

Returns the value of attribute custom_data_hook.



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

def custom_data_hook
  @custom_data_hook
end

.email_environmentObject

Returns the value of attribute email_environment.



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

def email_environment
  @email_environment
end

.escalation_recipientsObject

optional settings



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

def escalation_recipients
  @escalation_recipients
end

.eventmachine_safeObject

Returns the value of attribute eventmachine_safe.



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

def eventmachine_safe
  @eventmachine_safe
end

.eventmachine_synchronyObject

Returns the value of attribute eventmachine_synchrony.



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

def eventmachine_synchrony
  @eventmachine_synchrony
end

.exception_recipientsObject

Returns the value of attribute exception_recipients.



81
82
83
# File 'lib/exception_handling.rb', line 81

def exception_recipients
  @exception_recipients
end

.filter_list_filenameObject

Returns the value of attribute filter_list_filename.



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

def filter_list_filename
  @filter_list_filename
end

.last_exception_timestampObject

Returns the value of attribute last_exception_timestamp.



149
150
151
# File 'lib/exception_handling.rb', line 149

def last_exception_timestamp
  @last_exception_timestamp
end

.loggerObject

Returns the value of attribute logger.



82
83
84
# File 'lib/exception_handling.rb', line 82

def logger
  @logger
end

.mailer_send_enabledObject

Returns the value of attribute mailer_send_enabled.



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

def mailer_send_enabled
  @mailer_send_enabled
end

.periodic_exception_intervalsObject

Returns the value of attribute periodic_exception_intervals.



150
151
152
# File 'lib/exception_handling.rb', line 150

def periodic_exception_intervals
  @periodic_exception_intervals
end

.post_log_error_hookObject

Returns the value of attribute post_log_error_hook.



110
111
112
# File 'lib/exception_handling.rb', line 110

def post_log_error_hook
  @post_log_error_hook
end

.sender_addressObject

Returns the value of attribute sender_address.



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

def sender_address
  @sender_address
end

.sensu_hostObject

Returns the value of attribute sensu_host.



112
113
114
# File 'lib/exception_handling.rb', line 112

def sensu_host
  @sensu_host
end

.sensu_portObject

Returns the value of attribute sensu_port.



113
114
115
# File 'lib/exception_handling.rb', line 113

def sensu_port
  @sensu_port
end

.sensu_prefixObject

Returns the value of attribute sensu_prefix.



114
115
116
# File 'lib/exception_handling.rb', line 114

def sensu_prefix
  @sensu_prefix
end

.server_nameObject

required settings



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

def server_name
  @server_name
end

.stub_handlerObject

Returns the value of attribute stub_handler.



111
112
113
# File 'lib/exception_handling.rb', line 111

def stub_handler
  @stub_handler
end

Class Method Details

.alert_warning(exception_or_string, alert_name, exception_context) ⇒ Object



312
313
314
315
316
317
318
319
320
# File 'lib/exception_handling.rb', line 312

def alert_warning(exception_or_string, alert_name, exception_context)
  ex = make_exception(exception_or_string)
  log_error(ex, exception_context)
  begin
    ExceptionHandling::Sensu.generate_event(alert_name, exception_context.to_s + "\n" + ex.message.to_s)
  rescue => send_ex
    log_error(send_ex, 'ExceptionHandling.alert_warning')
  end
end

.enhance_exception_data(data) ⇒ Object



357
358
359
360
361
362
363
364
365
# File 'lib/exception_handling.rb', line 357

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_alert(alert_name, exception_context) ⇒ Object



322
323
324
325
326
327
328
329
# File 'lib/exception_handling.rb', line 322

def ensure_alert(alert_name, exception_context)
  begin
    yield
  rescue => ex
    alert_warning(ex, alert_name, exception_context)
    nil
  end
end

.ensure_completely_safe(exception_context = "") ⇒ Object



283
284
285
286
287
288
289
# File 'lib/exception_handling.rb', line 283

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



303
304
305
306
307
308
309
310
# File 'lib/exception_handling.rb', line 303

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

.ensure_safe(exception_context = "") ⇒ Object



276
277
278
279
280
281
# File 'lib/exception_handling.rb', line 276

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

.escalate_error(exception_or_string, email_subject) ⇒ Object



291
292
293
294
295
# File 'lib/exception_handling.rb', line 291

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



297
298
299
300
301
# File 'lib/exception_handling.rb', line 297

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.



249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/exception_handling.rb', line 249

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



272
273
274
# File 'lib/exception_handling.rb', line 272

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.


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
227
228
229
230
231
232
233
234
235
# File 'lib/exception_handling.rb', line 193

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



160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/exception_handling.rb', line 160

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



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

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

.log_periodically(exception_key, interval, message) ⇒ Object



348
349
350
351
352
353
354
355
# File 'lib/exception_handling.rb', line 348

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



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

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

.set_log_error_timestampObject



331
332
333
# File 'lib/exception_handling.rb', line 331

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

.should_send_email?Boolean

Returns:

  • (Boolean)


335
336
337
# File 'lib/exception_handling.rb', line 335

def should_send_email?
  ExceptionHandling.mailer_send_enabled
end

.trace_timing(description) ⇒ Object



339
340
341
342
343
344
345
346
# File 'lib/exception_handling.rb', line 339

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.



240
241
242
243
244
# File 'lib/exception_handling.rb', line 240

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