exception_handling 2.2.1 → 2.3.0.pre.1
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/.gitignore +1 -0
- data/.rubocop.yml +5 -0
- data/Gemfile +7 -6
- data/Gemfile.lock +26 -23
- data/README.md +0 -1
- data/Rakefile +4 -4
- data/config/exception_filters.yml +2 -3
- data/exception_handling.gemspec +12 -10
- data/lib/exception_handling/exception_catalog.rb +5 -4
- data/lib/exception_handling/exception_description.rb +28 -28
- data/lib/exception_handling/exception_info.rb +80 -72
- data/lib/exception_handling/honeybadger_callbacks.rb +41 -24
- data/lib/exception_handling/log_stub_error.rb +10 -8
- data/lib/exception_handling/mailer.rb +27 -48
- data/lib/exception_handling/methods.rb +15 -11
- data/lib/exception_handling/sensu.rb +7 -5
- data/lib/exception_handling/testing.rb +21 -19
- data/lib/exception_handling/version.rb +1 -1
- data/lib/exception_handling.rb +105 -200
- data/test/helpers/controller_helpers.rb +3 -1
- data/test/helpers/exception_helpers.rb +9 -0
- data/test/test_helper.rb +26 -21
- data/test/unit/exception_handling/exception_catalog_test.rb +15 -14
- data/test/unit/exception_handling/exception_description_test.rb +22 -31
- data/test/unit/exception_handling/exception_info_test.rb +76 -37
- data/test/unit/exception_handling/honeybadger_callbacks_test.rb +46 -9
- data/test/unit/exception_handling/log_error_stub_test.rb +6 -4
- data/test/unit/exception_handling/mailer_test.rb +8 -14
- data/test/unit/exception_handling/methods_test.rb +9 -6
- data/test/unit/exception_handling/sensu_test.rb +6 -4
- data/test/unit/exception_handling_test.rb +279 -364
- metadata +24 -24
- data/views/exception_handling/mailer/exception_notification.html.erb +0 -92
@@ -1,41 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ExceptionHandling
|
2
4
|
module HoneybadgerCallbacks
|
3
|
-
|
4
|
-
|
5
|
-
|
5
|
+
class << self
|
6
|
+
def register_callbacks
|
7
|
+
if ExceptionHandling.honeybadger_defined?
|
8
|
+
Honeybadger.local_variable_filter(&method(:local_variable_filter))
|
9
|
+
end
|
6
10
|
end
|
7
|
-
end
|
8
11
|
|
9
|
-
|
12
|
+
private
|
10
13
|
|
11
|
-
|
12
|
-
case object
|
13
|
-
# Honeybadger will filter these data types for us
|
14
|
-
when String, Hash, Array, Set, Numeric, TrueClass, FalseClass, NilClass
|
15
|
-
object
|
16
|
-
else # handle other Ruby objects, intended for POROs
|
14
|
+
def inspect_object(object, filter_keys)
|
17
15
|
inspection_output = object.inspect
|
16
|
+
|
18
17
|
if contains_filter_key?(filter_keys, inspection_output)
|
19
18
|
filtered_object(object)
|
20
19
|
else
|
21
20
|
inspection_output
|
22
21
|
end
|
22
|
+
rescue => ex
|
23
|
+
details = if object.respond_to?(:to_pk)
|
24
|
+
" @pk=#{object.to_pk}"
|
25
|
+
elsif object.respond_to?(:id)
|
26
|
+
" @id=#{object.id}"
|
27
|
+
end
|
28
|
+
|
29
|
+
"#<#{object.class.name}#{details} [error '#{ex.class.name}: #{ex.message}' while calling #inspect]>"
|
23
30
|
end
|
24
|
-
end
|
25
31
|
|
26
|
-
|
27
|
-
|
28
|
-
|
32
|
+
def local_variable_filter(_symbol, object, filter_keys)
|
33
|
+
case object
|
34
|
+
# Honeybadger will filter these data types for us
|
35
|
+
when String, Hash, Array, Set, Numeric, TrueClass, FalseClass, NilClass
|
36
|
+
object
|
37
|
+
else # handle other Ruby objects, intended for POROs
|
38
|
+
inspect_object(object, filter_keys)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def contains_filter_key?(filter_keys, string)
|
43
|
+
filter_keys._?.any? { |key| string.include?(key) }
|
44
|
+
end
|
29
45
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
46
|
+
def filtered_object(object)
|
47
|
+
# make the output look similar to inspect
|
48
|
+
# use [FILTERED], just like honeybadger does
|
49
|
+
if object.respond_to?(:to_pk)
|
50
|
+
"#<#{object.class.name} @pk=#{object.to_pk}, [FILTERED]>"
|
51
|
+
elsif object.respond_to?(:id)
|
52
|
+
"#<#{object.class.name} @id=#{object.id}, [FILTERED]>"
|
53
|
+
else
|
54
|
+
"#<#{object.class.name} [FILTERED]>"
|
55
|
+
end
|
39
56
|
end
|
40
57
|
end
|
41
58
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
#
|
2
4
|
# Used by functional tests to track exceptions.
|
3
5
|
#
|
@@ -22,7 +24,7 @@ module LogErrorStub
|
|
22
24
|
message = "log_error expected #{match[:expected]} times with pattern: '#{pattern.is_a?(Regexp) ? pattern.source : pattern}' found #{match[:found]}"
|
23
25
|
|
24
26
|
if is_mini_test?
|
25
|
-
|
27
|
+
flunk(message)
|
26
28
|
else
|
27
29
|
add_failure(message)
|
28
30
|
end
|
@@ -34,8 +36,9 @@ module LogErrorStub
|
|
34
36
|
|
35
37
|
# for overriding when testing this module
|
36
38
|
def is_mini_test?
|
37
|
-
defined?(Minitest::Test) &&
|
39
|
+
defined?(Minitest::Test) && is_a?(Minitest::Test)
|
38
40
|
end
|
41
|
+
|
39
42
|
#
|
40
43
|
# Call this function in your functional tests - usually first line after a "should" statement
|
41
44
|
# once called, you can then call expects_exception
|
@@ -60,7 +63,7 @@ module LogErrorStub
|
|
60
63
|
# Did the calling code call expects_exception on this exception?
|
61
64
|
#
|
62
65
|
def exception_filtered?(exception_data)
|
63
|
-
@exception_whitelist
|
66
|
+
@exception_whitelist&.any? do |expectation|
|
64
67
|
if expectation[0] === exception_data[:error]
|
65
68
|
expectation[1][:found] += 1
|
66
69
|
true
|
@@ -74,8 +77,8 @@ module LogErrorStub
|
|
74
77
|
def expects_exception(pattern, options = {})
|
75
78
|
@exception_whitelist ||= []
|
76
79
|
expected_count = options[:count] || 1
|
77
|
-
options = {:
|
78
|
-
if to_increment = @exception_whitelist.find {|ex| ex[0] == pattern}
|
80
|
+
options = { expected: expected_count, found: 0 }
|
81
|
+
if to_increment = @exception_whitelist.find { |ex| ex[0] == pattern }
|
79
82
|
to_increment[1][:expected] += expected_count
|
80
83
|
else
|
81
84
|
@exception_whitelist << [pattern, options]
|
@@ -90,10 +93,9 @@ module LogErrorStub
|
|
90
93
|
|
91
94
|
def raise_unexpected_exception(exception_data)
|
92
95
|
raise(UnexpectedExceptionLogged,
|
93
|
-
exception_data[:error] + "\n"
|
96
|
+
exception_data[:error] + "\n" \
|
94
97
|
"---original backtrace---\n" +
|
95
|
-
exception_data[:backtrace].join("\n") + "\n"
|
98
|
+
exception_data[:backtrace].join("\n") + "\n" \
|
96
99
|
"------")
|
97
100
|
end
|
98
|
-
|
99
101
|
end
|
@@ -1,10 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'action_mailer'
|
2
4
|
|
3
5
|
module ExceptionHandling
|
4
6
|
class Mailer < ActionMailer::Base
|
5
|
-
default :
|
7
|
+
default content_type: "text/html"
|
6
8
|
|
7
|
-
|
9
|
+
append_view_path "#{File.dirname(__FILE__)}/../../views"
|
8
10
|
|
9
11
|
[:email_environment, :server_name, :sender_address, :exception_recipients, :escalation_recipients].each do |method|
|
10
12
|
define_method method do
|
@@ -16,76 +18,53 @@ module ExceptionHandling
|
|
16
18
|
"#{email_environment} exception: "
|
17
19
|
end
|
18
20
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
if cleaned_data.is_a?(Hash)
|
23
|
-
cleaned_data.merge!({:occurrences => occurrences, :first_seen_at => first_seen_at}) if first_seen_at
|
24
|
-
cleaned_data.merge!({:server => server_name })
|
21
|
+
class << self
|
22
|
+
def reloadable?
|
23
|
+
false
|
25
24
|
end
|
26
25
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
mail(:from => from,
|
33
|
-
:to => recipients,
|
34
|
-
:subject => subject)
|
35
|
-
end
|
36
|
-
|
37
|
-
def escalation_notification( summary, data)
|
38
|
-
subject = "#{email_environment} Escalation: #{summary}"
|
39
|
-
from = sender_address.gsub('xception', 'scalation')
|
40
|
-
recipients = escalation_recipients rescue exception_recipients
|
41
|
-
|
42
|
-
@summary = summary
|
43
|
-
@server = ExceptionHandling.server_name
|
44
|
-
@cleaned_data = data
|
45
|
-
|
46
|
-
mail(:from => from,
|
47
|
-
:to => recipients,
|
48
|
-
:subject => subject)
|
26
|
+
def mailer_method_category
|
27
|
+
{
|
28
|
+
log_parser_exception_notification: :NetworkOptout
|
29
|
+
}
|
30
|
+
end
|
49
31
|
end
|
50
32
|
|
51
|
-
def
|
33
|
+
def escalation_notification(summary, data, custom_recipients = nil)
|
52
34
|
subject = "#{email_environment} Escalation: #{summary}"
|
53
35
|
from = sender_address.gsub('xception', 'scalation')
|
54
|
-
recipients =
|
36
|
+
recipients = begin
|
37
|
+
custom_recipients || escalation_recipients
|
38
|
+
rescue
|
39
|
+
exception_recipients
|
40
|
+
end
|
55
41
|
|
56
42
|
@summary = summary
|
57
43
|
@server = ExceptionHandling.server_name
|
58
44
|
@cleaned_data = data
|
59
45
|
|
60
|
-
mail(:
|
61
|
-
:
|
62
|
-
:
|
46
|
+
mail(from: from,
|
47
|
+
to: recipients,
|
48
|
+
subject: subject)
|
63
49
|
end
|
64
50
|
|
65
|
-
def log_parser_exception_notification(
|
51
|
+
def log_parser_exception_notification(cleaned_data, key)
|
66
52
|
if cleaned_data.is_a?(Hash)
|
67
53
|
cleaned_data = cleaned_data.symbolize_keys
|
68
54
|
local_subject = cleaned_data[:error]
|
69
55
|
else
|
70
56
|
local_subject = "#{key}: #{cleaned_data}"
|
71
|
-
cleaned_data = { :
|
57
|
+
cleaned_data = { error: cleaned_data.to_s }
|
72
58
|
end
|
73
59
|
|
74
|
-
@subject = "#{email_prefix}#{local_subject}"[0,300]
|
60
|
+
@subject = "#{email_prefix}#{local_subject}"[0, 300]
|
75
61
|
@recipients = exception_recipients
|
76
62
|
from = sender_address
|
77
63
|
@cleaned_data = cleaned_data
|
78
64
|
|
79
|
-
mail(:
|
80
|
-
:
|
81
|
-
:
|
82
|
-
end
|
83
|
-
|
84
|
-
def self.mailer_method_category
|
85
|
-
{
|
86
|
-
:exception_notification => :NetworkOptout,
|
87
|
-
:log_parser_exception_notification => :NetworkOptout
|
88
|
-
}
|
65
|
+
mail(from: from,
|
66
|
+
to: @recipients,
|
67
|
+
subject: @subject)
|
89
68
|
end
|
90
69
|
end
|
91
70
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'active_support/concern'
|
2
4
|
|
3
5
|
module ExceptionHandling
|
@@ -20,20 +22,18 @@ module ExceptionHandling
|
|
20
22
|
end
|
21
23
|
|
22
24
|
def log_info(message)
|
23
|
-
ExceptionHandling.logger.info(
|
25
|
+
ExceptionHandling.logger.info(message)
|
24
26
|
end
|
25
27
|
|
26
28
|
def log_debug(message)
|
27
|
-
ExceptionHandling.logger.debug(
|
29
|
+
ExceptionHandling.logger.debug(message)
|
28
30
|
end
|
29
31
|
|
30
32
|
def ensure_safe(exception_context = "")
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
nil
|
36
|
-
end
|
33
|
+
yield
|
34
|
+
rescue => ex
|
35
|
+
log_error ex, exception_context
|
36
|
+
nil
|
37
37
|
end
|
38
38
|
|
39
39
|
def escalate_error(exception_or_string, email_subject)
|
@@ -74,9 +74,13 @@ module ExceptionHandling
|
|
74
74
|
time = Benchmark.measure do
|
75
75
|
result = yield
|
76
76
|
end
|
77
|
-
if time.real >
|
78
|
-
name =
|
79
|
-
|
77
|
+
if time.real > long_controller_action_timeout && !['development', 'test'].include?(ExceptionHandling.email_environment)
|
78
|
+
name = begin
|
79
|
+
" in #{controller_name}::#{action_name}"
|
80
|
+
rescue
|
81
|
+
" "
|
82
|
+
end
|
83
|
+
log_error("Long controller action detected#{name} %.4fs " % time.real)
|
80
84
|
end
|
81
85
|
result
|
82
86
|
ensure
|
@@ -1,17 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "socket"
|
2
4
|
|
3
5
|
module ExceptionHandling
|
4
6
|
module Sensu
|
5
7
|
LEVELS = {
|
6
|
-
|
7
|
-
|
8
|
-
}
|
8
|
+
warning: 1,
|
9
|
+
critical: 2
|
10
|
+
}.freeze
|
9
11
|
|
10
12
|
class << self
|
11
13
|
def generate_event(name, message, level = :warning)
|
12
14
|
status = LEVELS[level] or raise "Invalid alert level #{level}"
|
13
15
|
|
14
|
-
event = {name: ExceptionHandling.sensu_prefix.to_s + name, output: message, status: status}
|
16
|
+
event = { name: ExceptionHandling.sensu_prefix.to_s + name, output: message, status: status }
|
15
17
|
|
16
18
|
send_event(event)
|
17
19
|
end
|
@@ -23,4 +25,4 @@ module ExceptionHandling
|
|
23
25
|
end
|
24
26
|
end
|
25
27
|
end
|
26
|
-
end
|
28
|
+
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
|
2
3
|
# some useful test objects
|
3
4
|
|
@@ -7,38 +8,44 @@ module ExceptionHandling
|
|
7
8
|
|
8
9
|
class Request
|
9
10
|
attr_accessor :parameters, :protocol, :host, :request_uri, :env, :session_options
|
11
|
+
|
10
12
|
def initialize
|
11
|
-
@parameters = {:
|
13
|
+
@parameters = { id: "1" }
|
12
14
|
@protocol = 'http'
|
13
15
|
@host = 'localhost'
|
14
16
|
@request_uri = "/fun/testing.html?foo=bar"
|
15
|
-
@env = {:
|
16
|
-
@session_options = { :
|
17
|
+
@env = { HOST: "local" }
|
18
|
+
@session_options = { id: '93951506217301' }
|
17
19
|
end
|
18
20
|
end
|
19
21
|
|
20
22
|
attr_accessor :request, :session
|
23
|
+
|
21
24
|
class << self
|
22
25
|
attr_accessor :around_filter_method
|
26
|
+
|
27
|
+
def around_filter(method)
|
28
|
+
ControllerStub.around_filter_method = method
|
29
|
+
end
|
23
30
|
end
|
24
31
|
|
25
32
|
def initialize
|
26
33
|
@request = Request.new
|
27
34
|
@session_id = "ZKL95"
|
28
35
|
@session =
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
36
|
+
if defined?(Username)
|
37
|
+
{
|
38
|
+
login_count: 22,
|
39
|
+
username_id: Username.first.id,
|
40
|
+
user_id: User.first.id,
|
41
|
+
}
|
42
|
+
else
|
43
|
+
{}
|
44
|
+
end
|
38
45
|
end
|
39
46
|
|
40
|
-
def simulate_around_filter(
|
41
|
-
set_current_controller(
|
47
|
+
def simulate_around_filter(&block)
|
48
|
+
set_current_controller(&block)
|
42
49
|
end
|
43
50
|
|
44
51
|
def controller_name
|
@@ -49,10 +56,6 @@ module ExceptionHandling
|
|
49
56
|
"test_action"
|
50
57
|
end
|
51
58
|
|
52
|
-
def self.around_filter( method )
|
53
|
-
ControllerStub.around_filter_method = method
|
54
|
-
end
|
55
|
-
|
56
59
|
def complete_request_uri
|
57
60
|
"#{@request.protocol}#{@request.host}#{@request.request_uri}"
|
58
61
|
end
|
@@ -60,6 +63,5 @@ module ExceptionHandling
|
|
60
63
|
include ExceptionHandling::Methods
|
61
64
|
set_long_controller_action_timeout 2
|
62
65
|
end
|
63
|
-
|
64
66
|
end
|
65
67
|
end
|