exception_handling 3.0.pre.1 → 3.0.0.pre.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/CODEOWNERS +1 -0
- data/.github/workflows/pipeline.yml +36 -0
- data/.gitignore +3 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -1
- data/.tool-versions +1 -0
- data/Appraisals +13 -0
- data/CHANGELOG.md +150 -0
- data/Gemfile +10 -16
- data/Gemfile.lock +65 -128
- data/README.md +51 -19
- data/Rakefile +8 -11
- data/exception_handling.gemspec +11 -13
- data/gemfiles/rails_5.gemfile +16 -0
- data/gemfiles/rails_6.gemfile +16 -0
- data/gemfiles/rails_7.gemfile +16 -0
- data/lib/exception_handling/escalate_callback.rb +19 -0
- data/lib/exception_handling/exception_info.rb +15 -11
- data/lib/exception_handling/log_stub_error.rb +2 -1
- data/lib/exception_handling/logging_methods.rb +21 -0
- data/lib/exception_handling/testing.rb +9 -12
- data/lib/exception_handling/version.rb +1 -1
- data/lib/exception_handling.rb +83 -173
- data/{test → spec}/helpers/exception_helpers.rb +2 -2
- data/spec/rake_test_warning_false.rb +20 -0
- data/{test/test_helper.rb → spec/spec_helper.rb} +63 -66
- data/spec/unit/exception_handling/escalate_callback_spec.rb +81 -0
- data/spec/unit/exception_handling/exception_catalog_spec.rb +85 -0
- data/spec/unit/exception_handling/exception_description_spec.rb +82 -0
- data/{test/unit/exception_handling/exception_info_test.rb → spec/unit/exception_handling/exception_info_spec.rb} +170 -114
- data/{test/unit/exception_handling/log_error_stub_test.rb → spec/unit/exception_handling/log_error_stub_spec.rb} +38 -22
- data/spec/unit/exception_handling/logging_methods_spec.rb +38 -0
- data/spec/unit/exception_handling_spec.rb +1063 -0
- metadata +60 -89
- data/lib/exception_handling/honeybadger_callbacks.rb +0 -59
- data/lib/exception_handling/mailer.rb +0 -70
- data/lib/exception_handling/methods.rb +0 -101
- data/lib/exception_handling/sensu.rb +0 -28
- data/semaphore_ci/setup.sh +0 -3
- data/test/unit/exception_handling/exception_catalog_test.rb +0 -85
- data/test/unit/exception_handling/exception_description_test.rb +0 -82
- data/test/unit/exception_handling/honeybadger_callbacks_test.rb +0 -122
- data/test/unit/exception_handling/mailer_test.rb +0 -98
- data/test/unit/exception_handling/methods_test.rb +0 -84
- data/test/unit/exception_handling/sensu_test.rb +0 -52
- data/test/unit/exception_handling_test.rb +0 -1109
- data/views/exception_handling/mailer/escalate_custom.html.erb +0 -17
- data/views/exception_handling/mailer/escalation_notification.html.erb +0 -17
- data/views/exception_handling/mailer/log_parser_exception_notification.html.erb +0 -82
- /data/{test → spec}/helpers/controller_helpers.rb +0 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "activesupport", "~> 5.2"
|
6
|
+
gem "appraisal", "~> 2.2"
|
7
|
+
gem "honeybadger", "~> 4.11"
|
8
|
+
gem "pry"
|
9
|
+
gem "pry-byebug"
|
10
|
+
gem "rake"
|
11
|
+
gem "rspec"
|
12
|
+
gem "rspec_junit_formatter"
|
13
|
+
gem "rubocop"
|
14
|
+
gem "test-unit"
|
15
|
+
|
16
|
+
gemspec path: "../"
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "activesupport", "~> 6.0"
|
6
|
+
gem "appraisal", "~> 2.2"
|
7
|
+
gem "honeybadger", "~> 4.11"
|
8
|
+
gem "pry"
|
9
|
+
gem "pry-byebug"
|
10
|
+
gem "rake"
|
11
|
+
gem "rspec"
|
12
|
+
gem "rspec_junit_formatter"
|
13
|
+
gem "rubocop"
|
14
|
+
gem "test-unit"
|
15
|
+
|
16
|
+
gemspec path: "../"
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "activesupport", "~> 7.0"
|
6
|
+
gem "appraisal", "~> 2.2"
|
7
|
+
gem "honeybadger", "~> 4.11"
|
8
|
+
gem "pry"
|
9
|
+
gem "pry-byebug"
|
10
|
+
gem "rake"
|
11
|
+
gem "rspec"
|
12
|
+
gem "rspec_junit_formatter"
|
13
|
+
gem "rubocop"
|
14
|
+
gem "test-unit"
|
15
|
+
|
16
|
+
gemspec path: "../"
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'escalate'
|
4
|
+
|
5
|
+
module ExceptionHandling
|
6
|
+
module EscalateCallback
|
7
|
+
class << self
|
8
|
+
def register_if_configured!
|
9
|
+
register! if ::ExceptionHandling.configured?
|
10
|
+
end
|
11
|
+
|
12
|
+
def register!
|
13
|
+
Escalate.on_escalate(log_first: false) do |exception, location_message, **context|
|
14
|
+
::ExceptionHandling.log_error(exception, location_message, **context)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module ExceptionHandling
|
4
4
|
class ExceptionInfo
|
5
5
|
|
6
|
-
|
6
|
+
ENVIRONMENT_ALLOWLIST = [
|
7
7
|
/^HTTP_/,
|
8
8
|
/^QUERY_/,
|
9
9
|
/^REQUEST_/,
|
@@ -46,16 +46,20 @@ module ExceptionHandling
|
|
46
46
|
EOS
|
47
47
|
|
48
48
|
SECTIONS = [:request, :session, :environment, :backtrace, :event_response].freeze
|
49
|
-
HONEYBADGER_CONTEXT_SECTIONS = [:timestamp, :error_class, :exception_context, :server, :scm_revision, :notes,
|
49
|
+
HONEYBADGER_CONTEXT_SECTIONS = [:timestamp, :error_class, :exception_context, :server, :scm_revision, :notes,
|
50
|
+
:user_details, :request, :session, :environment, :backtrace, :event_response, :log_context].freeze
|
50
51
|
|
51
|
-
attr_reader :exception, :controller, :exception_context, :timestamp
|
52
|
+
attr_reader :exception, :controller, :exception_context, :timestamp, :honeybadger_tags
|
52
53
|
|
53
|
-
def initialize(exception, exception_context, timestamp, controller
|
54
|
+
def initialize(exception, exception_context, timestamp, controller: nil, data_callback: nil, log_context: nil)
|
54
55
|
@exception = exception
|
55
56
|
@exception_context = exception_context
|
56
57
|
@timestamp = timestamp
|
57
58
|
@controller = controller || controller_from_context(exception_context)
|
58
59
|
@data_callback = data_callback
|
60
|
+
# merge into the surrounding context just like ContextualLogger does when logging
|
61
|
+
@merged_log_context = ExceptionHandling.logger.current_context_for_thread.deep_merge(log_context || {})
|
62
|
+
@honeybadger_tags = Array(@merged_log_context[:honeybadger_tags] || [])
|
59
63
|
end
|
60
64
|
|
61
65
|
def data
|
@@ -79,9 +83,10 @@ module ExceptionHandling
|
|
79
83
|
end
|
80
84
|
|
81
85
|
def controller_name
|
82
|
-
@controller_name ||=
|
83
|
-
|
84
|
-
|
86
|
+
@controller_name ||= (
|
87
|
+
@merged_log_context[:honeybadger_grouping] ||
|
88
|
+
(@controller && @controller.request.parameters.with_indifferent_access[:controller])
|
89
|
+
).to_s
|
85
90
|
end
|
86
91
|
|
87
92
|
private
|
@@ -176,7 +181,7 @@ module ExceptionHandling
|
|
176
181
|
|
177
182
|
def clean_environment(env)
|
178
183
|
Hash[ env.map do |k, v|
|
179
|
-
[k, v] if !"#{k}: #{v}".in?(ENVIRONMENT_OMIT) &&
|
184
|
+
[k, v] if !"#{k}: #{v}".in?(ENVIRONMENT_OMIT) && ENVIRONMENT_ALLOWLIST.any? { |regex| k =~ regex }
|
180
185
|
end.compact ]
|
181
186
|
end
|
182
187
|
|
@@ -267,14 +272,13 @@ module ExceptionHandling
|
|
267
272
|
data = enhanced_data.dup
|
268
273
|
data[:server] = ExceptionHandling.server_name
|
269
274
|
data[:exception_context] = deep_clean_hash(@exception_context) if @exception_context.present?
|
275
|
+
data[:log_context] = @merged_log_context
|
270
276
|
unstringify_sections(data)
|
271
|
-
|
277
|
+
HONEYBADGER_CONTEXT_SECTIONS.each_with_object({}) do |section, context|
|
272
278
|
if data[section].present?
|
273
279
|
context[section] = data[section]
|
274
280
|
end
|
275
|
-
context
|
276
281
|
end
|
277
|
-
context_data
|
278
282
|
end
|
279
283
|
end
|
280
284
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/concern'
|
4
|
+
require 'active_support/core_ext/module/delegation.rb'
|
5
|
+
|
6
|
+
module ExceptionHandling
|
7
|
+
module LoggingMethods # included on models and controllers
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
protected
|
11
|
+
|
12
|
+
delegate :log_error_rack, :log_warning, :log_info, :log_debug, :log_error, to: ExceptionHandling
|
13
|
+
|
14
|
+
def ensure_safe(exception_context = "")
|
15
|
+
yield
|
16
|
+
rescue => ex
|
17
|
+
log_error ex, exception_context
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -4,7 +4,7 @@
|
|
4
4
|
|
5
5
|
module ExceptionHandling
|
6
6
|
module Testing
|
7
|
-
class
|
7
|
+
class ControllerStubBase
|
8
8
|
|
9
9
|
class Request
|
10
10
|
attr_accessor :parameters, :protocol, :host, :request_uri, :env, :session_options
|
@@ -25,7 +25,7 @@ module ExceptionHandling
|
|
25
25
|
attr_accessor :around_filter_method
|
26
26
|
|
27
27
|
def around_filter(method)
|
28
|
-
|
28
|
+
self.around_filter_method = method
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
@@ -44,14 +44,6 @@ module ExceptionHandling
|
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
|
-
def simulate_around_filter(&block)
|
48
|
-
set_current_controller(&block)
|
49
|
-
end
|
50
|
-
|
51
|
-
def controller_name
|
52
|
-
"ControllerStub"
|
53
|
-
end
|
54
|
-
|
55
47
|
def action_name
|
56
48
|
"test_action"
|
57
49
|
end
|
@@ -59,9 +51,14 @@ module ExceptionHandling
|
|
59
51
|
def complete_request_uri
|
60
52
|
"#{@request.protocol}#{@request.host}#{@request.request_uri}"
|
61
53
|
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class LoggingMethodsControllerStub < ControllerStubBase
|
57
|
+
include ExceptionHandling::LoggingMethods
|
62
58
|
|
63
|
-
|
64
|
-
|
59
|
+
def controller_name
|
60
|
+
"LoggingMethodsControllerStub"
|
61
|
+
end
|
65
62
|
end
|
66
63
|
end
|
67
64
|
end
|
data/lib/exception_handling.rb
CHANGED
@@ -1,26 +1,25 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'digest'
|
3
4
|
require 'timeout'
|
4
5
|
require 'active_support'
|
5
|
-
require 'active_support/core_ext
|
6
|
+
require 'active_support/core_ext'
|
6
7
|
require 'contextual_logger'
|
8
|
+
require 'yaml'
|
7
9
|
|
8
10
|
require 'invoca/utils'
|
9
11
|
|
10
|
-
require
|
11
|
-
require
|
12
|
-
require
|
13
|
-
require
|
14
|
-
require
|
15
|
-
require
|
16
|
-
require "exception_handling/exception_info"
|
17
|
-
require "exception_handling/honeybadger_callbacks.rb"
|
12
|
+
require 'exception_handling/logging_methods'
|
13
|
+
require 'exception_handling/log_stub_error'
|
14
|
+
require 'exception_handling/exception_description'
|
15
|
+
require 'exception_handling/exception_catalog'
|
16
|
+
require 'exception_handling/exception_info'
|
17
|
+
require 'exception_handling/escalate_callback'
|
18
18
|
|
19
19
|
_ = ActiveSupport::HashWithIndifferentAccess
|
20
20
|
|
21
21
|
module ExceptionHandling # never included
|
22
22
|
class Warning < StandardError; end
|
23
|
-
class MailerTimeout < Timeout::Error; end
|
24
23
|
class ClientLoggingError < StandardError; end
|
25
24
|
|
26
25
|
SUMMARY_THRESHOLD = 5
|
@@ -35,19 +34,13 @@ module ExceptionHandling # never included
|
|
35
34
|
# required settings
|
36
35
|
#
|
37
36
|
attr_writer :server_name
|
38
|
-
attr_writer :sender_address
|
39
|
-
attr_writer :exception_recipients
|
40
37
|
|
41
38
|
def server_name
|
42
39
|
@server_name or raise ArgumentError, "You must assign a value to #{name}.server_name"
|
43
40
|
end
|
44
41
|
|
45
|
-
def
|
46
|
-
|
47
|
-
end
|
48
|
-
|
49
|
-
def exception_recipients
|
50
|
-
@exception_recipients or raise ArgumentError, "You must assign a value to #{name}.exception_recipients"
|
42
|
+
def configured?
|
43
|
+
!@logger.nil?
|
51
44
|
end
|
52
45
|
|
53
46
|
def logger
|
@@ -55,79 +48,38 @@ module ExceptionHandling # never included
|
|
55
48
|
end
|
56
49
|
|
57
50
|
def logger=(logger)
|
58
|
-
|
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
|
54
|
+
EscalateCallback.register_if_configured!
|
59
55
|
end
|
60
56
|
|
61
57
|
def default_metric_name(exception_data, exception, treat_like_warning)
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
"exception_handling.#{metric_name}"
|
74
|
-
end
|
75
|
-
|
76
|
-
def default_honeybadger_metric_name(honeybadger_status)
|
77
|
-
metric_name = if honeybadger_status.in?(HONEYBADGER_STATUSES)
|
78
|
-
honeybadger_status
|
79
|
-
else
|
80
|
-
:unknown_status
|
81
|
-
end
|
82
|
-
"exception_handling.honeybadger.#{metric_name}"
|
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
|
83
68
|
end
|
84
69
|
|
85
70
|
#
|
86
71
|
# optional settings
|
87
72
|
#
|
88
73
|
attr_accessor :production_support_recipients
|
89
|
-
attr_accessor :
|
90
|
-
attr_accessor :email_environment
|
74
|
+
attr_accessor :environment
|
91
75
|
attr_accessor :custom_data_hook
|
92
76
|
attr_accessor :post_log_error_hook
|
93
77
|
attr_accessor :stub_handler
|
94
|
-
attr_accessor :sensu_host
|
95
|
-
attr_accessor :sensu_port
|
96
|
-
attr_accessor :sensu_prefix
|
97
78
|
|
98
79
|
attr_reader :filter_list_filename
|
99
|
-
attr_reader :
|
100
|
-
attr_reader :eventmachine_synchrony
|
80
|
+
attr_reader :honeybadger_auto_tagger
|
101
81
|
|
102
82
|
@filter_list_filename = "./config/exception_filters.yml"
|
103
|
-
@email_environment = ""
|
104
|
-
@eventmachine_safe = false
|
105
|
-
@eventmachine_synchrony = false
|
106
|
-
@sensu_host = "127.0.0.1"
|
107
|
-
@sensu_port = 3030
|
108
|
-
@sensu_prefix = ""
|
109
|
-
|
110
|
-
# set this for operation within an eventmachine reactor
|
111
|
-
def eventmachine_safe=(bool)
|
112
|
-
if bool != true && bool != false
|
113
|
-
raise ArgumentError, "#{name}.eventmachine_safe must be a boolean."
|
114
|
-
end
|
115
|
-
|
116
|
-
if bool
|
117
|
-
require 'eventmachine'
|
118
|
-
require 'em/protocols/smtpclient'
|
119
|
-
end
|
120
|
-
@eventmachine_safe = bool
|
121
|
-
end
|
122
|
-
|
123
|
-
# set this for EM::Synchrony async operation
|
124
|
-
def eventmachine_synchrony=(bool)
|
125
|
-
if bool != true && bool != false
|
126
|
-
raise ArgumentError, "#{name}.eventmachine_synchrony must be a boolean."
|
127
|
-
end
|
128
|
-
|
129
|
-
@eventmachine_synchrony = bool
|
130
|
-
end
|
131
83
|
|
132
84
|
def filter_list_filename=(filename)
|
133
85
|
@filter_list_filename = filename
|
@@ -138,6 +90,14 @@ module ExceptionHandling # never included
|
|
138
90
|
@exception_catalog ||= ExceptionCatalog.new(@filter_list_filename)
|
139
91
|
end
|
140
92
|
|
93
|
+
# rubocop:disable Style/TrivialAccessors
|
94
|
+
# @param value [Proc|nil] Proc that accepts 1 parameter that will be the exception object or nil to disable the auto-tagger.
|
95
|
+
# The proc is always expected to return an array of strings. The array can be empty.
|
96
|
+
def honeybadger_auto_tagger=(value)
|
97
|
+
@honeybadger_auto_tagger = value
|
98
|
+
end
|
99
|
+
# rubocop:enable Style/TrivialAccessors
|
100
|
+
|
141
101
|
#
|
142
102
|
# internal settings (don't set directly)
|
143
103
|
#
|
@@ -177,13 +137,18 @@ module ExceptionHandling # never included
|
|
177
137
|
# Called directly by our code, usually from rescue blocks.
|
178
138
|
# Writes to log file and may send to honeybadger
|
179
139
|
#
|
140
|
+
# TODO: the **log_context means we can never have context named treat_like_warning. In general, keyword args will be conflated with log_context.
|
141
|
+
# Ideally we'd separate to log_context from the other keywords so they don't interfere in any way. Or have no keyword args.
|
142
|
+
#
|
180
143
|
# Functional Test Operation:
|
181
144
|
# Calls into handle_stub_log_error and returns. no log file. no honeybadger
|
182
145
|
#
|
183
|
-
def log_error(exception_or_string, exception_context = '', treat_like_warning: false, **log_context, &data_callback)
|
146
|
+
def log_error(exception_or_string, exception_context = '', controller = nil, treat_like_warning: false, **log_context, &data_callback)
|
184
147
|
ex = make_exception(exception_or_string)
|
185
148
|
timestamp = set_log_error_timestamp
|
186
|
-
exception_info = ExceptionInfo.new(ex, exception_context, timestamp,
|
149
|
+
exception_info = ExceptionInfo.new(ex, exception_context, timestamp,
|
150
|
+
controller: controller || current_controller, data_callback: data_callback,
|
151
|
+
log_context: log_context)
|
187
152
|
|
188
153
|
if stub_handler
|
189
154
|
stub_handler.handle_stub_log_error(exception_info.data)
|
@@ -210,7 +175,13 @@ module ExceptionHandling # never included
|
|
210
175
|
#
|
211
176
|
def write_exception_to_log(ex, exception_context, timestamp, log_context = {})
|
212
177
|
ActiveSupport::Deprecation.silence do
|
213
|
-
|
178
|
+
log_message = "#{exception_context}\n#{ex.class}: (#{encode_utf8(ex.message.to_s)}):\n " + clean_backtrace(ex).join("\n ") + "\n\n"
|
179
|
+
|
180
|
+
if ex.is_a?(Warning)
|
181
|
+
ExceptionHandling.logger.warn("\nExceptionHandlingWarning (Warning:#{timestamp}) #{log_message}", **log_context)
|
182
|
+
else
|
183
|
+
ExceptionHandling.logger.fatal("\nExceptionHandlingError (Error:#{timestamp}) #{log_message}", **log_context)
|
184
|
+
end
|
214
185
|
end
|
215
186
|
end
|
216
187
|
|
@@ -243,11 +214,16 @@ module ExceptionHandling # never included
|
|
243
214
|
def send_exception_to_honeybadger(exception_info)
|
244
215
|
exception = exception_info.exception
|
245
216
|
exception_description = exception_info.exception_description
|
217
|
+
|
218
|
+
# Note: Both commas and spaces are treated as delimiters for the :tags string. Space-delimiters are not officially documented.
|
219
|
+
# https://github.com/honeybadger-io/honeybadger-ruby/pull/422
|
220
|
+
tags = (honeybadger_auto_tags(exception) + exception_info.honeybadger_tags).join(' ')
|
246
221
|
response = Honeybadger.notify(error_class: exception_description ? exception_description.filter_name : exception.class.name,
|
247
222
|
error_message: exception.message.to_s,
|
248
223
|
exception: exception,
|
249
224
|
context: exception_info.honeybadger_context_data,
|
250
|
-
controller: exception_info.controller_name
|
225
|
+
controller: exception_info.controller_name,
|
226
|
+
tags: tags)
|
251
227
|
response ? :success : :failure
|
252
228
|
rescue Exception => ex
|
253
229
|
warn("ExceptionHandling.send_exception_to_honeybadger rescued exception while logging #{exception_info.exception_context}:\n#{exception.class}: #{exception.message}:\n#{ex.class}: #{ex.message}\n#{ex.backtrace.join("\n")}")
|
@@ -255,6 +231,18 @@ module ExceptionHandling # never included
|
|
255
231
|
:failure
|
256
232
|
end
|
257
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
|
+
|
258
246
|
#
|
259
247
|
# Check if Honeybadger defined.
|
260
248
|
#
|
@@ -265,38 +253,41 @@ module ExceptionHandling # never included
|
|
265
253
|
#
|
266
254
|
# Expects passed in hash to only include keys which be directly set on the Honeybadger config
|
267
255
|
#
|
268
|
-
def enable_honeybadger(config
|
256
|
+
def enable_honeybadger(**config)
|
269
257
|
Bundler.require(:honeybadger)
|
270
|
-
HoneybadgerCallbacks.register_callbacks
|
271
258
|
Honeybadger.configure do |config_klass|
|
272
259
|
config.each do |k, v|
|
273
|
-
|
260
|
+
if k == :before_notify
|
261
|
+
config_klass.send(k, v)
|
262
|
+
else
|
263
|
+
config_klass.send(:"#{k}=", v)
|
264
|
+
end
|
274
265
|
end
|
275
266
|
end
|
276
267
|
end
|
277
268
|
|
278
|
-
def log_warning(message, log_context
|
269
|
+
def log_warning(message, **log_context)
|
279
270
|
warning = Warning.new(message)
|
280
271
|
warning.set_backtrace([])
|
281
272
|
log_error(warning, **log_context)
|
282
273
|
end
|
283
274
|
|
284
|
-
def log_info(message, log_context
|
285
|
-
ExceptionHandling.logger.info(message, log_context)
|
275
|
+
def log_info(message, **log_context)
|
276
|
+
ExceptionHandling.logger.info(message, **log_context)
|
286
277
|
end
|
287
278
|
|
288
|
-
def log_debug(message, log_context
|
289
|
-
ExceptionHandling.logger.debug(message, log_context)
|
279
|
+
def log_debug(message, **log_context)
|
280
|
+
ExceptionHandling.logger.debug(message, **log_context)
|
290
281
|
end
|
291
282
|
|
292
|
-
def ensure_safe(exception_context = "", log_context
|
283
|
+
def ensure_safe(exception_context = "", **log_context)
|
293
284
|
yield
|
294
285
|
rescue => ex
|
295
286
|
log_error(ex, exception_context, **log_context)
|
296
287
|
nil
|
297
288
|
end
|
298
289
|
|
299
|
-
def ensure_completely_safe(exception_context = "", log_context
|
290
|
+
def ensure_completely_safe(exception_context = "", **log_context)
|
300
291
|
yield
|
301
292
|
rescue SystemExit, SystemStackError, NoMemoryError, SecurityError, SignalException
|
302
293
|
raise
|
@@ -305,48 +296,6 @@ module ExceptionHandling # never included
|
|
305
296
|
nil
|
306
297
|
end
|
307
298
|
|
308
|
-
def escalate_to_production_support(exception_or_string, email_subject)
|
309
|
-
production_support_recipients or raise ArgumentError, "In order to escalate to production support, you must set #{name}.production_recipients"
|
310
|
-
ex = make_exception(exception_or_string)
|
311
|
-
escalate(email_subject, ex, last_exception_timestamp, production_support_recipients)
|
312
|
-
end
|
313
|
-
|
314
|
-
def escalate_error(exception_or_string, email_subject, custom_recipients = nil, log_context = {})
|
315
|
-
ex = make_exception(exception_or_string)
|
316
|
-
log_error(ex, **log_context)
|
317
|
-
escalate(email_subject, ex, last_exception_timestamp, custom_recipients)
|
318
|
-
end
|
319
|
-
|
320
|
-
def escalate_warning(message, email_subject, custom_recipients = nil, log_context = {})
|
321
|
-
ex = Warning.new(message)
|
322
|
-
log_error(ex, **log_context)
|
323
|
-
escalate(email_subject, ex, last_exception_timestamp, custom_recipients)
|
324
|
-
end
|
325
|
-
|
326
|
-
def ensure_escalation(email_subject, custom_recipients = nil, log_context = {})
|
327
|
-
yield
|
328
|
-
rescue => ex
|
329
|
-
escalate_error(ex, email_subject, custom_recipients, log_context)
|
330
|
-
nil
|
331
|
-
end
|
332
|
-
|
333
|
-
def alert_warning(exception_or_string, alert_name, exception_context, log_context)
|
334
|
-
ex = make_exception(exception_or_string)
|
335
|
-
log_error(ex, exception_context, **log_context)
|
336
|
-
begin
|
337
|
-
ExceptionHandling::Sensu.generate_event(alert_name, exception_context.to_s + "\n" + encode_utf8(ex.message.to_s))
|
338
|
-
rescue => ex
|
339
|
-
log_error(ex, 'ExceptionHandling.alert_warning')
|
340
|
-
end
|
341
|
-
end
|
342
|
-
|
343
|
-
def ensure_alert(alert_name, exception_context, log_context = {})
|
344
|
-
yield
|
345
|
-
rescue => ex
|
346
|
-
alert_warning(ex, alert_name, exception_context, log_context)
|
347
|
-
nil
|
348
|
-
end
|
349
|
-
|
350
299
|
def set_log_error_timestamp
|
351
300
|
ExceptionHandling.last_exception_timestamp = Time.now.to_i
|
352
301
|
end
|
@@ -360,7 +309,7 @@ module ExceptionHandling # never included
|
|
360
309
|
result
|
361
310
|
end
|
362
311
|
|
363
|
-
def log_periodically(exception_key, interval, message, log_context
|
312
|
+
def log_periodically(exception_key, interval, message, **log_context)
|
364
313
|
self.periodic_exception_intervals ||= {}
|
365
314
|
last_logged = self.periodic_exception_intervals[exception_key]
|
366
315
|
if !last_logged || ((last_logged + interval) < Time.now)
|
@@ -409,47 +358,6 @@ module ExceptionHandling # never included
|
|
409
358
|
log_info("Unable to execute custom log_error callback. #{ex_message} #{ex_backtrace}")
|
410
359
|
end
|
411
360
|
|
412
|
-
def escalate(email_subject, ex, timestamp, custom_recipients = nil)
|
413
|
-
exception_info = ExceptionInfo.new(ex, nil, timestamp)
|
414
|
-
deliver(ExceptionHandling::Mailer.escalation_notification(email_subject, exception_info.data, custom_recipients))
|
415
|
-
end
|
416
|
-
|
417
|
-
def deliver(mail_object)
|
418
|
-
if ExceptionHandling.eventmachine_safe
|
419
|
-
EventMachine.schedule do # in case we're running outside the reactor
|
420
|
-
async_send_method = ExceptionHandling.eventmachine_synchrony ? :asend : :send
|
421
|
-
smtp_settings = ActionMailer::Base.smtp_settings
|
422
|
-
dns_deferrable = EventMachine::DNS::Resolver.resolve(smtp_settings[:address])
|
423
|
-
dns_deferrable.callback do |addrs|
|
424
|
-
send_deferrable = EventMachine::Protocols::SmtpClient.__send__(
|
425
|
-
async_send_method,
|
426
|
-
host: addrs.first,
|
427
|
-
port: smtp_settings[:port],
|
428
|
-
domain: smtp_settings[:domain],
|
429
|
-
auth: { type: :plain, username: smtp_settings[:user_name], password: smtp_settings[:password] },
|
430
|
-
from: mail_object['from'].to_s,
|
431
|
-
to: mail_object['to'].to_s,
|
432
|
-
content: "#{mail_object}\r\n.\r\n"
|
433
|
-
)
|
434
|
-
send_deferrable.errback { |err| ExceptionHandling.logger.fatal("Failed to email by SMTP: #{err.inspect}") }
|
435
|
-
end
|
436
|
-
dns_deferrable.errback { |err| ExceptionHandling.logger.fatal("Failed to resolv DNS for #{smtp_settings[:address]}: #{err.inspect}") }
|
437
|
-
end
|
438
|
-
else
|
439
|
-
safe_email_deliver do
|
440
|
-
mail_object.deliver_now
|
441
|
-
end
|
442
|
-
end
|
443
|
-
end
|
444
|
-
|
445
|
-
def safe_email_deliver
|
446
|
-
Timeout.timeout 30, MailerTimeout do
|
447
|
-
yield
|
448
|
-
end
|
449
|
-
rescue StandardError, MailerTimeout => ex
|
450
|
-
log_error(ex, "ExceptionHandling::safe_email_deliver", treat_like_warning: true)
|
451
|
-
end
|
452
|
-
|
453
361
|
def make_exception(exception_or_string)
|
454
362
|
if exception_or_string.is_a?(Exception)
|
455
363
|
exception_or_string
|
@@ -463,4 +371,6 @@ module ExceptionHandling # never included
|
|
463
371
|
end
|
464
372
|
end
|
465
373
|
end
|
374
|
+
|
375
|
+
EscalateCallback.register_if_configured!
|
466
376
|
end
|
@@ -7,7 +7,7 @@ module ExceptionHelpers
|
|
7
7
|
|
8
8
|
def exception_with_nil_message
|
9
9
|
exception_with_nil_message = RuntimeError.new(nil)
|
10
|
-
|
10
|
+
allow(exception_with_nil_message).to receive(:message).and_return(nil)
|
11
11
|
exception_with_nil_message
|
12
12
|
end
|
13
13
|
|
@@ -15,6 +15,6 @@ module ExceptionHelpers
|
|
15
15
|
|
16
16
|
def capture_notifications
|
17
17
|
@sent_notifications = []
|
18
|
-
|
18
|
+
allow(ExceptionHandling).to receive(:send_exception_to_honeybadger).with(any_args) { |exception_info| @sent_notifications << exception_info }
|
19
19
|
end
|
20
20
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Rake 11+ has a misfeature where @warning = true by default
|
4
|
+
# See https://github.com/ruby/rake/pull/97/files
|
5
|
+
# This causes all tests to be run with `ruby -w`, causing a huge number of warnings
|
6
|
+
# from gems we don't control and overwhelming our test output.
|
7
|
+
# This patch reverts that.
|
8
|
+
|
9
|
+
_ = Rake::TestTask
|
10
|
+
|
11
|
+
class Rake::TestTask
|
12
|
+
module SetWarningFalseMixin
|
13
|
+
def initialize(*args)
|
14
|
+
super
|
15
|
+
self.warning = false
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
prepend SetWarningFalseMixin
|
20
|
+
end
|