exception_handling 3.0.pre.1 → 3.0.0
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.
- 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 +62 -91
- 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
|