Module: ExceptionHandling

Defined in:
lib/exception_handling.rb,
lib/exception_handling_mailer.rb,
lib/exception_handling/version.rb

Overview

never included

Defined Under Namespace

Modules: Methods 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]
EXCEPTION_FILTER_LIST_PATH =
"#{defined?(Rails) ? Rails.root : '.'}/config/exception_filters.yml"
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 =
"0.2.0"

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.current_controllerObject

Returns the value of attribute current_controller.



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

def current_controller
  @current_controller
end

.email_environmentObject

Returns the value of attribute email_environment.



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

def email_environment
  @email_environment
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

.last_exception_timestampObject

Returns the value of attribute last_exception_timestamp.



84
85
86
# File 'lib/exception_handling.rb', line 84

def last_exception_timestamp
  @last_exception_timestamp
end

.loggerObject

Returns the value of attribute logger.



86
87
88
# File 'lib/exception_handling.rb', line 86

def logger
  @logger
end

.periodic_exception_intervalsObject

Returns the value of attribute periodic_exception_intervals.



85
86
87
# File 'lib/exception_handling.rb', line 85

def periodic_exception_intervals
  @periodic_exception_intervals
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

.server_nameObject

Returns the value of attribute server_name.



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

def server_name
  @server_name
end

Class Method Details

.enhance_exception_data(data) ⇒ Object

TODO: fix test to not use this.



257
258
259
# File 'lib/exception_handling.rb', line 257

def enhance_exception_data(data)

end

.ensure_completely_safe(exception_context = "") ⇒ Object



212
213
214
215
216
217
218
# File 'lib/exception_handling.rb', line 212

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



220
221
222
223
224
225
226
227
228
# File 'lib/exception_handling.rb', line 220

def ensure_escalation( email_subject )
  begin
    yield
  rescue => ex
    log_error ex
    escalate(email_subject, ex, ExceptionHandling.last_exception_timestamp)
    nil
  end
end

.ensure_safe(exception_context = "") ⇒ Object



205
206
207
208
209
210
# File 'lib/exception_handling.rb', line 205

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

.extract_and_merge_controller_data(controller, data) ⇒ Object

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



178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/exception_handling.rb', line 178

def extract_and_merge_controller_data(controller, data)
  data[:request] = {
    :params      => controller.request.parameters.to_hash,
    :rails_root  => Rails.root,
    :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



201
202
203
# File 'lib/exception_handling.rb', line 201

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.


122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/exception_handling.rb', line 122

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)

    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 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



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/exception_handling.rb', line 96

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

  # 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)
    extract_and_merge_controller_data(controller, exception_data) if controller
    log_error_email(exception_data, exception)
  end
end

.log_info(message) ⇒ Object



197
198
199
# File 'lib/exception_handling.rb', line 197

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

.log_periodically(exception_key, interval, message) ⇒ Object



247
248
249
250
251
252
253
254
# File 'lib/exception_handling.rb', line 247

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



193
194
195
# File 'lib/exception_handling.rb', line 193

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

.set_log_error_timestampObject



230
231
232
# File 'lib/exception_handling.rb', line 230

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

.should_send_email?Boolean

Returns:

  • (Boolean)


234
235
236
# File 'lib/exception_handling.rb', line 234

def should_send_email?
  defined?( EXCEPTION_HANDLING_MAILER_SEND_MAIL ) && EXCEPTION_HANDLING_MAILER_SEND_MAIL
end

.trace_timing(description) ⇒ Object



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

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.



163
164
165
166
167
168
169
170
171
172
173
# File 'lib/exception_handling.rb', line 163

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