exception_handling 2.2.1 → 2.3.0.pre.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|