Module: ExceptionHandling
- Defined in:
- lib/exception_handling.rb,
lib/exception_handling/testing.rb,
lib/exception_handling/version.rb,
lib/exception_handling/exception_info.rb,
lib/exception_handling/logging_methods.rb,
lib/exception_handling/escalate_callback.rb,
lib/exception_handling/exception_catalog.rb,
lib/exception_handling/exception_description.rb
Overview
some useful test objects
Defined Under Namespace
Modules: EscalateCallback, LoggingMethods, Testing Classes: ClientLoggingError, ExceptionCatalog, ExceptionDescription, ExceptionInfo, Warning
Constant Summary collapse
- SUMMARY_THRESHOLD =
5
- SUMMARY_PERIOD =
1.hour
60 * 60
- AUTHENTICATION_HEADERS =
['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION', 'REDIRECT_X_HTTP_AUTHORIZATION'].freeze
- HONEYBADGER_STATUSES =
[:success, :failure, :skipped].freeze
- VERSION =
'3.1.2'
Class Attribute Summary collapse
-
.current_controller ⇒ Object
internal settings (don’t set directly).
-
.custom_data_hook ⇒ Object
Returns the value of attribute custom_data_hook.
-
.environment ⇒ Object
Returns the value of attribute environment.
-
.filter_list_filename ⇒ Object
Returns the value of attribute filter_list_filename.
-
.honeybadger_auto_tagger ⇒ Object
Returns the value of attribute honeybadger_auto_tagger.
-
.honeybadger_log_context_tags ⇒ Object
readonly
Returns the value of attribute honeybadger_log_context_tags.
-
.last_exception_timestamp ⇒ Object
Returns the value of attribute last_exception_timestamp.
-
.periodic_exception_intervals ⇒ Object
Returns the value of attribute periodic_exception_intervals.
-
.post_log_error_hook ⇒ Object
Returns the value of attribute post_log_error_hook.
-
.production_support_recipients ⇒ Object
optional settings.
- .server_name ⇒ Object
-
.stub_handler ⇒ Object
Returns the value of attribute stub_handler.
Class Method Summary collapse
- .add_honeybadger_tag_from_log_context(tag_name, path:) ⇒ Object
- .clean_backtrace(exception) ⇒ Object
- .clear_honeybadger_tags_from_log_context ⇒ Object
- .configured? ⇒ Boolean
- .default_metric_name(exception_data, exception, treat_like_warning) ⇒ Object
-
.enable_honeybadger(**config) ⇒ Object
Expects passed in hash to only include keys which be directly set on the Honeybadger config.
- .encode_utf8(string) ⇒ Object
- .ensure_completely_safe(exception_context = "", **log_context) ⇒ Object
- .ensure_safe(exception_context = "", **log_context) ⇒ Object
- .exception_catalog ⇒ Object
-
.honeybadger_defined? ⇒ Boolean
Check if Honeybadger defined.
- .log_debug(message, **log_context) ⇒ Object
-
.log_error(exception_or_string, exception_context = '', controller = nil, treat_like_warning: false, **log_context, &data_callback) ⇒ Object
Normal Operation: Called directly by our code, usually from rescue blocks.
-
.log_error_rack(exception, env, _rack_filter) ⇒ Object
Gets called by Rack Middleware: DebugExceptions or ShowExceptions it does 2 things: log the error may send to honeybadger.
- .log_info(message, **log_context) ⇒ Object
- .log_periodically(exception_key, interval, message, **log_context) ⇒ Object
- .log_warning(message, **log_context) ⇒ Object
- .logger ⇒ Object
- .logger=(logger) ⇒ Object
-
.send_exception_to_honeybadger(exception_info) ⇒ Object
Log exception to honeybadger.io.
-
.send_exception_to_honeybadger_unless_filtered(exception_info) ⇒ Object
Returns :success or :failure or :skipped.
-
.send_external_notifications(exception_info) ⇒ Object
Send notifications to configured external services.
- .set_log_error_timestamp ⇒ Object
- .trace_timing(description) ⇒ Object
-
.write_exception_to_log(ex, exception_context, timestamp, log_context = {}) ⇒ Object
Write an exception out to the log file using our own custom format.
Class Attribute Details
.current_controller ⇒ Object
internal settings (don’t set directly)
122 123 124 |
# File 'lib/exception_handling.rb', line 122 def current_controller @current_controller end |
.custom_data_hook ⇒ Object
Returns the value of attribute custom_data_hook.
77 78 79 |
# File 'lib/exception_handling.rb', line 77 def custom_data_hook @custom_data_hook end |
.environment ⇒ Object
Returns the value of attribute environment.
76 77 78 |
# File 'lib/exception_handling.rb', line 76 def environment @environment end |
.filter_list_filename ⇒ Object
Returns the value of attribute filter_list_filename.
81 82 83 |
# File 'lib/exception_handling.rb', line 81 def filter_list_filename @filter_list_filename end |
.honeybadger_auto_tagger ⇒ Object
Returns the value of attribute honeybadger_auto_tagger.
82 83 84 |
# File 'lib/exception_handling.rb', line 82 def honeybadger_auto_tagger @honeybadger_auto_tagger end |
.honeybadger_log_context_tags ⇒ Object (readonly)
Returns the value of attribute honeybadger_log_context_tags.
83 84 85 |
# File 'lib/exception_handling.rb', line 83 def @honeybadger_log_context_tags end |
.last_exception_timestamp ⇒ Object
Returns the value of attribute last_exception_timestamp.
123 124 125 |
# File 'lib/exception_handling.rb', line 123 def @last_exception_timestamp end |
.periodic_exception_intervals ⇒ Object
Returns the value of attribute periodic_exception_intervals.
124 125 126 |
# File 'lib/exception_handling.rb', line 124 def periodic_exception_intervals @periodic_exception_intervals end |
.post_log_error_hook ⇒ Object
Returns the value of attribute post_log_error_hook.
78 79 80 |
# File 'lib/exception_handling.rb', line 78 def post_log_error_hook @post_log_error_hook end |
.production_support_recipients ⇒ Object
optional settings
75 76 77 |
# File 'lib/exception_handling.rb', line 75 def production_support_recipients @production_support_recipients end |
.server_name ⇒ Object
40 41 42 |
# File 'lib/exception_handling.rb', line 40 def server_name @server_name or raise ArgumentError, "You must assign a value to #{name}.server_name" end |
.stub_handler ⇒ Object
Returns the value of attribute stub_handler.
79 80 81 |
# File 'lib/exception_handling.rb', line 79 def stub_handler @stub_handler end |
Class Method Details
.add_honeybadger_tag_from_log_context(tag_name, path:) ⇒ Object
106 107 108 109 110 111 112 113 |
# File 'lib/exception_handling.rb', line 106 def add_honeybadger_tag_from_log_context(tag_name, path:) tag_name.is_a?(String) or raise ArgumentError, "tag_name must be a String, #{tag_name.inspect}" (path.is_a?(Array) && path.all? { _1.is_a?(String) }) or raise ArgumentError, "path must be an Array<String>, #{path.inspect}" if @honeybadger_log_context_tags.key?(tag_name) log_warning("Overwriting existing tag path for #{tag_name.inspect} from #{@honeybadger_log_context_tags[tag_name]} to #{path}") end @honeybadger_log_context_tags[tag_name] = path end |
.clean_backtrace(exception) ⇒ Object
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 |
# File 'lib/exception_handling.rb', line 334 def clean_backtrace(exception) backtrace = if exception.backtrace.nil? ['<no backtrace>'] elsif exception.is_a?(ClientLoggingError) exception.backtrace elsif defined?(Rails) && defined?(Rails.backtrace_cleaner) Rails.backtrace_cleaner.clean(exception.backtrace) else exception.backtrace end # The rails backtrace cleaner returns an empty array for a backtrace if the exception was raised outside the app (inside a gem for instance) if backtrace.is_a?(Array) && backtrace.empty? exception.backtrace else backtrace end end |
.clear_honeybadger_tags_from_log_context ⇒ Object
115 116 117 |
# File 'lib/exception_handling.rb', line 115 def @honeybadger_log_context_tags = {} end |
.configured? ⇒ Boolean
44 45 46 |
# File 'lib/exception_handling.rb', line 44 def configured? !@logger.nil? end |
.default_metric_name(exception_data, exception, treat_like_warning) ⇒ Object
59 60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/exception_handling.rb', line 59 def default_metric_name(exception_data, exception, treat_like_warning) if exception_data['metric_name'] exception_data['metric_name'] elsif exception.is_a?(ExceptionHandling::Warning) "warning" elsif treat_like_warning exception_name = "_#{exception.class.name.split('::').last}" if exception.present? "unforwarded_exception#{exception_name}" else "exception" end end |
.enable_honeybadger(**config) ⇒ Object
Expects passed in hash to only include keys which be directly set on the Honeybadger config
262 263 264 265 266 267 268 269 270 271 272 273 |
# File 'lib/exception_handling.rb', line 262 def enable_honeybadger(**config) Bundler.require(:honeybadger) Honeybadger.configure do |config_klass| config.each do |k, v| if k == :before_notify config_klass.send(k, v) else config_klass.send(:"#{k}=", v) end end end end |
.encode_utf8(string) ⇒ Object
327 328 329 330 331 332 |
# File 'lib/exception_handling.rb', line 327 def encode_utf8(string) string.encode('UTF-8', replace: '?', undef: :replace, invalid: :replace) end |
.ensure_completely_safe(exception_context = "", **log_context) ⇒ Object
296 297 298 299 300 301 302 303 |
# File 'lib/exception_handling.rb', line 296 def ensure_completely_safe(exception_context = "", **log_context) yield rescue SystemExit, SystemStackError, NoMemoryError, SecurityError, SignalException raise rescue Exception => ex log_error(ex, exception_context, **log_context) nil end |
.ensure_safe(exception_context = "", **log_context) ⇒ Object
289 290 291 292 293 294 |
# File 'lib/exception_handling.rb', line 289 def ensure_safe(exception_context = "", **log_context) yield rescue => ex log_error(ex, exception_context, **log_context) nil end |
.exception_catalog ⇒ Object
92 93 94 |
# File 'lib/exception_handling.rb', line 92 def exception_catalog @exception_catalog ||= ExceptionCatalog.new(@filter_list_filename) end |
.honeybadger_defined? ⇒ Boolean
Check if Honeybadger defined.
255 256 257 |
# File 'lib/exception_handling.rb', line 255 def honeybadger_defined? Object.const_defined?("Honeybadger") end |
.log_debug(message, **log_context) ⇒ Object
285 286 287 |
# File 'lib/exception_handling.rb', line 285 def log_debug(, **log_context) ExceptionHandling.logger.debug(, **log_context) end |
.log_error(exception_or_string, exception_context = '', controller = nil, treat_like_warning: false, **log_context, &data_callback) ⇒ Object
Normal Operation:
Called directly by our code, usually from rescue blocks.
Writes to log file and may send to honeybadger
TODO: the **log_context means we can never have context named treat_like_warning. In general, keyword args will be conflated with log_context. Ideally we’d separate to log_context from the other keywords so they don’t interfere in any way. Or have no keyword args.
Functional Test Operation:
Calls into handle_stub_log_error and returns. no log file. no honeybadger
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
# File 'lib/exception_handling.rb', line 164 def log_error(exception_or_string, exception_context = '', controller = nil, treat_like_warning: false, **log_context, &data_callback) ex = make_exception(exception_or_string) = exception_info = ExceptionInfo.new(ex, exception_context, , controller: controller || current_controller, data_callback: data_callback, log_context: log_context) if stub_handler stub_handler.handle_stub_log_error(exception_info.data) else write_exception_to_log(ex, exception_context, , log_context) external_notification_results = unless treat_like_warning || ex.is_a?(Warning) send_external_notifications(exception_info) end || {} execute_custom_log_error_callback(exception_info.enhanced_data.merge(log_context: log_context), exception_info.exception, treat_like_warning, external_notification_results) end ExceptionHandling. rescue LogErrorStub::UnexpectedExceptionLogged, LogErrorStub::ExpectedExceptionNotLogged raise rescue Exception => ex warn("ExceptionHandlingError: 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, "ExceptionHandlingError: log_error rescued exception while logging #{exception_context}: #{exception_or_string}", ) ensure ExceptionHandling. end |
.log_error_rack(exception, env, _rack_filter) ⇒ Object
Gets called by Rack Middleware: DebugExceptions or ShowExceptions it does 2 things:
log the error
may send to honeybadger
but not during functional tests, when rack middleware is not used
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/exception_handling.rb', line 134 def log_error_rack(exception, env, _rack_filter) = exception_info = ExceptionInfo.new(exception, env, ) if stub_handler stub_handler.handle_stub_log_error(exception_info.data) else # 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, ) send_external_notifications(exception_info) nil end end |
.log_info(message, **log_context) ⇒ Object
281 282 283 |
# File 'lib/exception_handling.rb', line 281 def log_info(, **log_context) ExceptionHandling.logger.info(, **log_context) end |
.log_periodically(exception_key, interval, message, **log_context) ⇒ Object
318 319 320 321 322 323 324 325 |
# File 'lib/exception_handling.rb', line 318 def log_periodically(exception_key, interval, , **log_context) self.periodic_exception_intervals ||= {} last_logged = self.periodic_exception_intervals[exception_key] if !last_logged || ((last_logged + interval) < Time.now) log_error(, **log_context) self.periodic_exception_intervals[exception_key] = Time.now end end |
.log_warning(message, **log_context) ⇒ Object
275 276 277 278 279 |
# File 'lib/exception_handling.rb', line 275 def log_warning(, **log_context) warning = Warning.new() warning.set_backtrace([]) log_error(warning, **log_context) end |
.logger ⇒ Object
48 49 50 |
# File 'lib/exception_handling.rb', line 48 def logger @logger or raise ArgumentError, "You must assign a value to #{name}.logger" end |
.logger=(logger) ⇒ Object
52 53 54 55 56 57 |
# File 'lib/exception_handling.rb', line 52 def logger=(logger) logger.nil? || logger.is_a?(ContextualLogger::LoggerMixin) or raise ArgumentError, "The logger must be a ContextualLogger::LoggerMixin, not a #{logger.class}" @logger = logger EscalateCallback.register_if_configured! end |
.send_exception_to_honeybadger(exception_info) ⇒ Object
Log exception to honeybadger.io.
Returns :success or :failure
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 |
# File 'lib/exception_handling.rb', line 232 def send_exception_to_honeybadger(exception_info) exception = exception_info.exception exception_description = exception_info.exception_description # Note: Both commas and spaces are treated as delimiters for the :tags string. Space-delimiters are not officially documented. # https://github.com/honeybadger-io/honeybadger-ruby/pull/422 = (exception_info).join(' ') response = Honeybadger.notify(error_class: exception_description ? exception_description.filter_name : exception.class.name, error_message: exception..to_s, exception: exception, context: exception_info.honeybadger_context_data, controller: exception_info.controller_name, tags: ) response ? :success : :failure rescue Exception => ex warn("ExceptionHandling.send_exception_to_honeybadger rescued exception while logging #{exception_info.exception_context}:\n#{exception.class}: #{exception.}:\n#{ex.class}: #{ex.}\n#{ex.backtrace.join("\n")}") write_exception_to_log(ex, "ExceptionHandling.send_exception_to_honeybadger rescued exception while logging #{exception_info.exception_context}:\n#{exception.class}: #{exception.}", exception_info.) :failure end |
.send_exception_to_honeybadger_unless_filtered(exception_info) ⇒ Object
Returns :success or :failure or :skipped
218 219 220 221 222 223 224 225 |
# File 'lib/exception_handling.rb', line 218 def send_exception_to_honeybadger_unless_filtered(exception_info) if exception_info.send_to_honeybadger? send_exception_to_honeybadger(exception_info) else log_info("Filtered exception using '#{exception_info.exception_description.filter_name}'; not sending notification to Honeybadger") :skipped end end |
.send_external_notifications(exception_info) ⇒ Object
Send notifications to configured external services
209 210 211 212 213 214 215 |
# File 'lib/exception_handling.rb', line 209 def send_external_notifications(exception_info) results = {} if honeybadger_defined? results[:honeybadger_status] = send_exception_to_honeybadger_unless_filtered(exception_info) end results end |
.set_log_error_timestamp ⇒ Object
305 306 307 |
# File 'lib/exception_handling.rb', line 305 def ExceptionHandling. = Time.now.to_i end |
.trace_timing(description) ⇒ Object
309 310 311 312 313 314 315 316 |
# File 'lib/exception_handling.rb', line 309 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, log_context = {}) ⇒ Object
Write an exception out to the log file using our own custom format.
194 195 196 197 198 199 200 201 202 203 204 |
# File 'lib/exception_handling.rb', line 194 def write_exception_to_log(ex, exception_context, , log_context = {}) with_deprecations_silenced do = "#{exception_context}\n#{ex.class}: (#{encode_utf8(ex..to_s)}):\n " + clean_backtrace(ex).join("\n ") + "\n\n" if ex.is_a?(Warning) ExceptionHandling.logger.warn("\nExceptionHandlingWarning (Warning:#{}) #{}", **log_context) else ExceptionHandling.logger.fatal("\nExceptionHandlingError (Error:#{}) #{}", **log_context) end end end |