exception_handling 2.17.0.pre.tstarck.1 → 3.0.0.pre.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,12 +5,11 @@ require 'timeout'
5
5
  require 'active_support'
6
6
  require 'active_support/core_ext'
7
7
  require 'contextual_logger'
8
+ require 'yaml'
8
9
 
9
10
  require 'invoca/utils'
10
11
 
11
- require 'exception_handling/mailer'
12
- require 'exception_handling/sensu'
13
- require 'exception_handling/methods'
12
+ require 'exception_handling/logging_methods'
14
13
  require 'exception_handling/log_stub_error'
15
14
  require 'exception_handling/exception_description'
16
15
  require 'exception_handling/exception_catalog'
@@ -21,7 +20,6 @@ _ = ActiveSupport::HashWithIndifferentAccess
21
20
 
22
21
  module ExceptionHandling # never included
23
22
  class Warning < StandardError; end
24
- class MailerTimeout < Timeout::Error; end
25
23
  class ClientLoggingError < StandardError; end
26
24
 
27
25
  SUMMARY_THRESHOLD = 5
@@ -30,29 +28,17 @@ module ExceptionHandling # never included
30
28
  AUTHENTICATION_HEADERS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION', 'REDIRECT_X_HTTP_AUTHORIZATION'].freeze
31
29
  HONEYBADGER_STATUSES = [:success, :failure, :skipped].freeze
32
30
 
33
- Deprecation3_0 = ActiveSupport::Deprecation.new('3.0', 'exception_handling')
34
-
35
31
  class << self
36
32
 
37
33
  #
38
34
  # required settings
39
35
  #
40
36
  attr_writer :server_name
41
- attr_writer :sender_address
42
- attr_writer :exception_recipients
43
37
 
44
38
  def server_name
45
39
  @server_name or raise ArgumentError, "You must assign a value to #{name}.server_name"
46
40
  end
47
41
 
48
- def sender_address
49
- @sender_address or raise ArgumentError, "You must assign a value to #{name}.sender_address"
50
- end
51
-
52
- def exception_recipients
53
- @exception_recipients or raise ArgumentError, "You must assign a value to #{name}.exception_recipients"
54
- end
55
-
56
42
  def configured?
57
43
  !@logger.nil?
58
44
  end
@@ -62,90 +48,38 @@ module ExceptionHandling # never included
62
48
  end
63
49
 
64
50
  def logger=(logger)
65
- @logger = if logger.nil? || logger.is_a?(ContextualLogger::LoggerMixin)
66
- logger
67
- else
68
- Deprecation3_0.deprecation_warning('implicit extend with ContextualLogger::LoggerMixin', 'extend your logger instance or include into your logger class first')
69
- logger.extend(ContextualLogger::LoggerMixin)
70
- end
51
+ logger.nil? || logger.is_a?(ContextualLogger::LoggerMixin) or raise ArgumentError,
52
+ "The logger must be a ContextualLogger::LoggerMixin, not a #{logger.class}"
53
+ @logger = logger
71
54
  EscalateCallback.register_if_configured!
72
55
  end
73
56
 
74
- def default_metric_name(exception_data, exception, treat_like_warning, include_prefix: true)
75
- include_prefix and Deprecation3_0.deprecation_warning("the 'expection_handling.' prefix in ExceptionHandling::default_metric_name",
76
- "do not rely on metric names including the 'exception_handling.' prefix.")
77
-
78
- metric_name = if exception_data['metric_name']
79
- exception_data['metric_name']
80
- elsif exception.is_a?(ExceptionHandling::Warning)
81
- "warning"
82
- elsif treat_like_warning
83
- exception_name = "_#{exception.class.name.split('::').last}" if exception.present?
84
- "unforwarded_exception#{exception_name}"
85
- else
86
- "exception"
87
- end
88
-
89
- "#{'exception_handling.' if include_prefix}#{metric_name}"
90
- end
91
-
92
- def default_honeybadger_metric_name(honeybadger_status)
93
- metric_name = if honeybadger_status.in?(HONEYBADGER_STATUSES)
94
- honeybadger_status
95
- else
96
- :unknown_status
97
- end
98
- "exception_handling.honeybadger.#{metric_name}"
57
+ def default_metric_name(exception_data, exception, treat_like_warning)
58
+ if exception_data['metric_name']
59
+ exception_data['metric_name']
60
+ elsif exception.is_a?(ExceptionHandling::Warning)
61
+ "warning"
62
+ elsif treat_like_warning
63
+ exception_name = "_#{exception.class.name.split('::').last}" if exception.present?
64
+ "unforwarded_exception#{exception_name}"
65
+ else
66
+ "exception"
67
+ end
99
68
  end
100
69
 
101
70
  #
102
71
  # optional settings
103
72
  #
104
73
  attr_accessor :production_support_recipients
105
- attr_accessor :escalation_recipients
106
- attr_accessor :email_environment
74
+ attr_accessor :environment
107
75
  attr_accessor :custom_data_hook
108
76
  attr_accessor :post_log_error_hook
109
77
  attr_accessor :stub_handler
110
- attr_accessor :sensu_host
111
- attr_accessor :sensu_port
112
- attr_accessor :sensu_prefix
113
- attr_reader :honeybadger_log_context_tags
114
78
 
115
79
  attr_reader :filter_list_filename
116
- attr_reader :eventmachine_safe
117
- attr_reader :eventmachine_synchrony
118
80
  attr_reader :honeybadger_auto_tagger
119
81
 
120
82
  @filter_list_filename = "./config/exception_filters.yml"
121
- @email_environment = ""
122
- @eventmachine_safe = false
123
- @eventmachine_synchrony = false
124
- @sensu_host = "127.0.0.1"
125
- @sensu_port = 3030
126
- @sensu_prefix = ""
127
-
128
- # set this for operation within an eventmachine reactor
129
- def eventmachine_safe=(bool)
130
- if bool != true && bool != false
131
- raise ArgumentError, "#{name}.eventmachine_safe must be a boolean."
132
- end
133
-
134
- if bool
135
- require 'eventmachine'
136
- require 'em/protocols/smtpclient'
137
- end
138
- @eventmachine_safe = bool
139
- end
140
-
141
- # set this for EM::Synchrony async operation
142
- def eventmachine_synchrony=(bool)
143
- if bool != true && bool != false
144
- raise ArgumentError, "#{name}.eventmachine_synchrony must be a boolean."
145
- end
146
-
147
- @eventmachine_synchrony = bool
148
- end
149
83
 
150
84
  def filter_list_filename=(filename)
151
85
  @filter_list_filename = filename
@@ -164,22 +98,6 @@ module ExceptionHandling # never included
164
98
  end
165
99
  # rubocop:enable Style/TrivialAccessors
166
100
 
167
- # @param tag_name [String]
168
- # @param path [Array]
169
- def add_honeybadger_tag_from_log_context(tag_name, path:)
170
- tag_name.is_a?(String) or raise ArgumentError, "tag_name must be a String, #{tag_name.inspect}"
171
- path.is_a?(Array) or raise ArgumentError, "path must be an Array, #{path.inspect}"
172
- @honeybadger_log_context_tags ||= {}
173
- if @honeybadger_log_context_tags.key?(tag_name)
174
- log_warning("Overwriting existing tag path for '#{tag_name}' from #{@honeybadger_log_context_tags[tag_name]} to #{path}")
175
- end
176
- @honeybadger_log_context_tags[tag_name] = path
177
- end
178
-
179
- def clear_honeybadger_tags_from_log_context
180
- @honeybadger_log_context_tags = nil
181
- end
182
-
183
101
  #
184
102
  # internal settings (don't set directly)
185
103
  #
@@ -299,7 +217,7 @@ module ExceptionHandling # never included
299
217
 
300
218
  # Note: Both commas and spaces are treated as delimiters for the :tags string. Space-delimiters are not officially documented.
301
219
  # https://github.com/honeybadger-io/honeybadger-ruby/pull/422
302
- tags = tags_for_honeybadger(exception_info).join(' ')
220
+ tags = (honeybadger_auto_tags(exception) + exception_info.honeybadger_tags).join(' ')
303
221
  response = Honeybadger.notify(error_class: exception_description ? exception_description.filter_name : exception.class.name,
304
222
  error_message: exception.message.to_s,
305
223
  exception: exception,
@@ -313,6 +231,18 @@ module ExceptionHandling # never included
313
231
  :failure
314
232
  end
315
233
 
234
+ # @param exception [Exception]
235
+ #
236
+ # @return [Array<String>]
237
+ def honeybadger_auto_tags(exception)
238
+ @honeybadger_auto_tagger&.call(exception) || []
239
+ rescue => ex
240
+ traces = ex.backtrace.join("\n")
241
+ message = "Unable to execute honeybadger_auto_tags callback. #{ExceptionHandling.encode_utf8(ex.message.to_s)} #{traces}\n"
242
+ ExceptionHandling.log_info(message)
243
+ []
244
+ end
245
+
316
246
  #
317
247
  # Check if Honeybadger defined.
318
248
  #
@@ -366,51 +296,6 @@ module ExceptionHandling # never included
366
296
  nil
367
297
  end
368
298
 
369
- def escalate_to_production_support(exception_or_string, email_subject)
370
- production_support_recipients or raise ArgumentError, "In order to escalate to production support, you must set #{name}.production_recipients"
371
- ex = make_exception(exception_or_string)
372
- escalate(email_subject, ex, last_exception_timestamp, production_support_recipients)
373
- end
374
-
375
- def escalate_error(exception_or_string, email_subject, custom_recipients = nil, **log_context)
376
- ex = make_exception(exception_or_string)
377
- log_error(ex, **log_context)
378
- escalate(email_subject, ex, last_exception_timestamp, custom_recipients)
379
- end
380
-
381
- def escalate_warning(message, email_subject, custom_recipients = nil, **log_context)
382
- ex = Warning.new(message)
383
- log_error(ex, **log_context)
384
- escalate(email_subject, ex, last_exception_timestamp, custom_recipients)
385
- end
386
-
387
- def ensure_escalation(email_subject, custom_recipients = nil, **log_context)
388
- yield
389
- rescue => ex
390
- escalate_error(ex, email_subject, custom_recipients, **log_context)
391
- nil
392
- end
393
-
394
- deprecate :escalate_to_production_support, :escalate_error, :escalate_warning, :ensure_escalation,
395
- deprecator: ActiveSupport::Deprecation.new('3.0', 'ExceptionHandling')
396
-
397
- def alert_warning(exception_or_string, alert_name, exception_context, **log_context)
398
- ex = make_exception(exception_or_string)
399
- log_error(ex, exception_context, **log_context)
400
- begin
401
- ExceptionHandling::Sensu.generate_event(alert_name, exception_context.to_s + "\n" + encode_utf8(ex.message.to_s))
402
- rescue => ex
403
- log_error(ex, 'ExceptionHandling.alert_warning')
404
- end
405
- end
406
-
407
- def ensure_alert(alert_name, exception_context, **log_context)
408
- yield
409
- rescue => ex
410
- alert_warning(ex, alert_name, exception_context, **log_context)
411
- nil
412
- end
413
-
414
299
  def set_log_error_timestamp
415
300
  ExceptionHandling.last_exception_timestamp = Time.now.to_i
416
301
  end
@@ -461,41 +346,6 @@ module ExceptionHandling # never included
461
346
 
462
347
  private
463
348
 
464
- # @param exception_info [ExceptionInfo]
465
- #
466
- # @return [Array<String>]
467
- def tags_for_honeybadger(exception_info)
468
- (
469
- honeybadger_auto_tags(exception_info.exception) +
470
- exception_info.honeybadger_tags +
471
- honeybadger_tags_from_log_context(exception_info.honeybadger_context_data)
472
- ).uniq
473
- end
474
-
475
- # @param exception [Exception]
476
- #
477
- # @return [Array<String>]
478
- def honeybadger_auto_tags(exception)
479
- @honeybadger_auto_tagger&.call(exception) || []
480
- rescue => ex
481
- traces = ex.backtrace.join("\n")
482
- message = "Unable to execute honeybadger_auto_tags callback. #{ExceptionHandling.encode_utf8(ex.message.to_s)} #{traces}\n"
483
- ExceptionHandling.log_info(message)
484
- []
485
- end
486
-
487
- def honeybadger_tags_from_log_context(honeybadger_context_data)
488
- if @honeybadger_log_context_tags
489
- @honeybadger_log_context_tags.map do |tag_name, tag_path|
490
- if (value_from_log_context = honeybadger_context_data.dig(:log_context, *tag_path))
491
- "#{tag_name}:#{value_from_log_context}"
492
- end
493
- end.compact
494
- else
495
- []
496
- end
497
- end
498
-
499
349
  def execute_custom_log_error_callback(exception_data, exception, treat_like_warning, external_notification_results)
500
350
  if ExceptionHandling.post_log_error_hook
501
351
  honeybadger_status = external_notification_results[:honeybadger_status] || :skipped
@@ -508,47 +358,6 @@ module ExceptionHandling # never included
508
358
  log_info("Unable to execute custom log_error callback. #{ex_message} #{ex_backtrace}")
509
359
  end
510
360
 
511
- def escalate(email_subject, ex, timestamp, custom_recipients = nil)
512
- exception_info = ExceptionInfo.new(ex, nil, timestamp)
513
- deliver(ExceptionHandling::Mailer.escalation_notification(email_subject, exception_info.data, custom_recipients))
514
- end
515
-
516
- def deliver(mail_object)
517
- if ExceptionHandling.eventmachine_safe
518
- EventMachine.schedule do # in case we're running outside the reactor
519
- async_send_method = ExceptionHandling.eventmachine_synchrony ? :asend : :send
520
- smtp_settings = ActionMailer::Base.smtp_settings
521
- dns_deferrable = EventMachine::DNS::Resolver.resolve(smtp_settings[:address])
522
- dns_deferrable.callback do |addrs|
523
- send_deferrable = EventMachine::Protocols::SmtpClient.__send__(
524
- async_send_method,
525
- host: addrs.first,
526
- port: smtp_settings[:port],
527
- domain: smtp_settings[:domain],
528
- auth: { type: :plain, username: smtp_settings[:user_name], password: smtp_settings[:password] },
529
- from: mail_object['from'].to_s,
530
- to: mail_object['to'].to_s,
531
- content: "#{mail_object}\r\n.\r\n"
532
- )
533
- send_deferrable.errback { |err| ExceptionHandling.logger.fatal("Failed to email by SMTP: #{err.inspect}") }
534
- end
535
- dns_deferrable.errback { |err| ExceptionHandling.logger.fatal("Failed to resolv DNS for #{smtp_settings[:address]}: #{err.inspect}") }
536
- end
537
- else
538
- safe_email_deliver do
539
- mail_object.deliver_now
540
- end
541
- end
542
- end
543
-
544
- def safe_email_deliver
545
- Timeout.timeout 30, MailerTimeout do
546
- yield
547
- end
548
- rescue StandardError, MailerTimeout => ex
549
- log_error(ex, "ExceptionHandling::safe_email_deliver", treat_like_warning: true)
550
- end
551
-
552
361
  def make_exception(exception_or_string)
553
362
  if exception_or_string.is_a?(Exception)
554
363
  exception_or_string
data/spec/spec_helper.rb CHANGED
@@ -5,7 +5,6 @@ require 'rspec/mocks'
5
5
  require 'rspec_junit_formatter'
6
6
 
7
7
  require 'pry'
8
- require 'pry-byebug'
9
8
  require 'honeybadger'
10
9
  require 'contextual_logger'
11
10
 
@@ -23,28 +22,20 @@ class LoggerStub
23
22
  clear
24
23
  end
25
24
 
26
- def debug(message, **log_context)
27
- super.tap do
28
- logged << { message: message, context: log_context, severity: 'DEBUG' }
29
- end
25
+ def debug(message, log_context = {})
26
+ logged << { message: message, context: log_context, severity: 'DEBUG' }
30
27
  end
31
28
 
32
- def info(message, **log_context)
33
- super.tap do
34
- logged << { message: message, context: log_context, severity: 'INFO' }
35
- end
29
+ def info(message, log_context = {})
30
+ logged << { message: message, context: log_context, severity: 'INFO' }
36
31
  end
37
32
 
38
- def warn(message, **log_context)
39
- super.tap do
40
- logged << { message: message, context: log_context, severity: 'WARN' }
41
- end
33
+ def warn(message, log_context = {})
34
+ logged << { message: message, context: log_context, severity: 'WARN' }
42
35
  end
43
36
 
44
- def fatal(message, **log_context)
45
- super.tap do
46
- logged << { message: message, context: log_context, severity: 'FATAL' }
47
- end
37
+ def fatal(message, log_context = {})
38
+ logged << { message: message, context: log_context, severity: 'FATAL' }
48
39
  end
49
40
 
50
41
  def clear
@@ -83,16 +74,12 @@ def dont_stub_log_error
83
74
  true
84
75
  end
85
76
 
86
- ActionMailer::Base.delivery_method = :test
87
-
88
-
89
77
  module TestHelper
90
78
  @constant_overrides = []
91
79
  class << self
92
80
  attr_accessor :constant_overrides
93
81
  end
94
82
 
95
-
96
83
  def setup_constant_overrides
97
84
  unless TestHelper.constant_overrides.nil? || TestHelper.constant_overrides.empty?
98
85
  raise "Uh-oh! constant_overrides left over: #{TestHelper.constant_overrides.inspect}"
@@ -100,19 +87,9 @@ module TestHelper
100
87
 
101
88
  Time.now_override = nil
102
89
 
103
- ActionMailer::Base.deliveries.clear
104
-
105
- ExceptionHandling.email_environment = 'Test'
106
- ExceptionHandling.sender_address = 'server@example.com'
107
- ExceptionHandling.exception_recipients = 'exceptions@example.com'
108
- ExceptionHandling.escalation_recipients = 'escalation@example.com'
90
+ ExceptionHandling.environment = 'not_test'
109
91
  ExceptionHandling.server_name = 'server'
110
92
  ExceptionHandling.filter_list_filename = "./config/exception_filters.yml"
111
- ExceptionHandling.eventmachine_safe = false
112
- ExceptionHandling.eventmachine_synchrony = false
113
- ExceptionHandling.sensu_host = "127.0.0.1"
114
- ExceptionHandling.sensu_port = 3030
115
- ExceptionHandling.sensu_prefix = ""
116
93
  end
117
94
 
118
95
  def teardown_constant_overrides
@@ -151,16 +128,6 @@ module TestHelper
151
128
 
152
129
  silence_warnings { final_parent_module.const_set(final_const_name, value) }
153
130
  end
154
-
155
- def assert_emails(expected, message = nil)
156
- if block_given?
157
- original_count = ActionMailer::Base.deliveries.size
158
- yield
159
- else
160
- original_count = 0
161
- end
162
- expect(ActionMailer::Base.deliveries.size - original_count).to eq(expected), "wrong number of emails#{': ' + message.to_s if message}"
163
- end
164
131
  end
165
132
 
166
133
  def assert_equal_with_diff(arg1, arg2, msg = '')
@@ -33,7 +33,7 @@ module ExceptionHandling
33
33
  context 'when already configured' do
34
34
  before do
35
35
  @original_logger = ExceptionHandling.logger
36
- ExceptionHandling.logger = ::Logger.new('/dev/null')
36
+ ExceptionHandling.logger = ::Logger.new('/dev/null').extend(ContextualLogger::LoggerMixin)
37
37
  end
38
38
 
39
39
  after do
@@ -66,7 +66,7 @@ module ExceptionHandling
66
66
 
67
67
  expect(Escalate.on_escalate_callbacks).to be_empty
68
68
 
69
- ExceptionHandling.logger = ::Logger.new('/dev/null')
69
+ ExceptionHandling.logger = ::Logger.new('/dev/null').extend(ContextualLogger::LoggerMixin)
70
70
  expect(Escalate.on_escalate_callbacks).to_not be_empty
71
71
 
72
72
  expect(logger).to_not receive(:error)