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

Sign up to get free protection for your applications and to get access to all the features.
@@ -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)