right_support 2.11.3 → 2.12.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 +4 -4
- data/Rakefile +4 -0
- data/VERSION +1 -1
- data/lib/right_support/notifiers/airbrake.rb +194 -0
- data/lib/right_support/notifiers/base.rb +73 -0
- data/lib/right_support/notifiers/blacklisters/base.rb +48 -0
- data/lib/right_support/notifiers/blacklisters/canonical.rb +60 -0
- data/lib/right_support/notifiers/blacklisters/regular_expression.rb +86 -0
- data/{features/support/file_utils_bundler_mixin.rb → lib/right_support/notifiers/blacklisters/simple.rb} +21 -20
- data/lib/right_support/notifiers/blacklisters/snake_case.rb +60 -0
- data/lib/right_support/notifiers/blacklisters/wildcard.rb +65 -0
- data/lib/right_support/notifiers/blacklisters.rb +34 -0
- data/lib/right_support/notifiers/logger.rb +94 -0
- data/lib/right_support/notifiers/notification.rb +57 -0
- data/lib/right_support/notifiers/utilities/backtrace_decoder.rb +234 -0
- data/lib/right_support/notifiers/utilities.rb +29 -0
- data/lib/right_support/notifiers.rb +32 -0
- data/lib/right_support/rack/request_logger.rb +13 -9
- data/lib/right_support.rb +1 -0
- data/right_support.gemspec +19 -70
- metadata +17 -69
- data/.coveralls.yml +0 -2
- data/.rspec +0 -3
- data/.simplecov +0 -6
- data/.travis.yml +0 -13
- data/Gemfile +0 -51
- data/Gemfile.lock +0 -153
- data/features/balancer_error_handling.feature +0 -34
- data/features/balancer_health_check.feature +0 -33
- data/features/hash_tools.feature +0 -27
- data/features/http_client_timeout.feature +0 -19
- data/features/serialization.feature +0 -113
- data/features/step_definitions/hash_tools_steps.rb +0 -41
- data/features/step_definitions/http_client_steps.rb +0 -27
- data/features/step_definitions/request_balancer_steps.rb +0 -93
- data/features/step_definitions/ruby_steps.rb +0 -176
- data/features/step_definitions/serialization_steps.rb +0 -133
- data/features/step_definitions/server_steps.rb +0 -134
- data/features/support/env.rb +0 -148
- data/right_support.rconf +0 -9
- data/spec/config/feature_set_spec.rb +0 -83
- data/spec/crypto/signed_hash_spec.rb +0 -73
- data/spec/data/hash_tools_spec.rb +0 -602
- data/spec/data/mash_spec.rb +0 -313
- data/spec/data/token_spec.rb +0 -21
- data/spec/data/uuid_spec.rb +0 -45
- data/spec/db/cassandra_model_part1_spec.rb +0 -84
- data/spec/db/cassandra_model_part2_spec.rb +0 -73
- data/spec/db/cassandra_model_spec.rb +0 -375
- data/spec/fixtures/encrypted_priv_rsa.pem +0 -30
- data/spec/fixtures/good_priv_dsa.pem +0 -12
- data/spec/fixtures/good_priv_rsa.pem +0 -15
- data/spec/fixtures/good_pub_dsa.ssh +0 -1
- data/spec/fixtures/good_pub_rsa.pem +0 -5
- data/spec/fixtures/good_pub_rsa.ssh +0 -1
- data/spec/log/exception_logger_spec.rb +0 -76
- data/spec/log/filter_logger_spec.rb +0 -66
- data/spec/log/mixin_spec.rb +0 -141
- data/spec/log/multiplexer_spec.rb +0 -54
- data/spec/log/null_logger_spec.rb +0 -36
- data/spec/log/step_level_logger_spec.rb +0 -49
- data/spec/log/system_logger_spec.rb +0 -172
- data/spec/net/address_helper_spec.rb +0 -57
- data/spec/net/dns_spec.rb +0 -187
- data/spec/net/http_client_spec.rb +0 -181
- data/spec/net/lb/health_check_spec.rb +0 -417
- data/spec/net/lb/round_robin_spec.rb +0 -15
- data/spec/net/lb/sticky_spec.rb +0 -92
- data/spec/net/request_balancer_spec.rb +0 -690
- data/spec/net/s3_helper_spec.rb +0 -160
- data/spec/net/ssl_spec.rb +0 -42
- data/spec/net/string_encoder_spec.rb +0 -58
- data/spec/rack/log_setter_spec.rb +0 -5
- data/spec/rack/request_logger_spec.rb +0 -225
- data/spec/rack/request_tracker_spec.rb +0 -115
- data/spec/rack/runtime_spec.rb +0 -49
- data/spec/ruby/easy_singleton_spec.rb +0 -72
- data/spec/ruby/object_extensions_spec.rb +0 -27
- data/spec/ruby/string_extensions_spec.rb +0 -98
- data/spec/spec_helper.rb +0 -188
- data/spec/stats/activity_spec.rb +0 -425
- data/spec/stats/exceptions_spec.rb +0 -247
- data/spec/stats/helpers_spec.rb +0 -685
- data/spec/validation/openssl_spec.rb +0 -37
- data/spec/validation/ssh_spec.rb +0 -39
@@ -0,0 +1,34 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2016 RightScale Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
23
|
+
module RightSupport
|
24
|
+
module Notifier
|
25
|
+
module Blacklister
|
26
|
+
autoload :Base, 'right_support/notifiers/blacklisters/base'
|
27
|
+
autoload :Canonical, 'right_support/notifiers/blacklisters/canonical'
|
28
|
+
autoload :RegularExpression, 'right_support/notifiers/blacklisters/regular_expression'
|
29
|
+
autoload :Simple, 'right_support/notifiers/blacklisters/simple'
|
30
|
+
autoload :SnakeCase, 'right_support/notifiers/blacklisters/snake_case'
|
31
|
+
autoload :Wildcard, 'right_support/notifiers/blacklisters/wildcard'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2016 RightScale Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
23
|
+
require 'airbrake-ruby'
|
24
|
+
|
25
|
+
class RightSupport::Notifier::Logger < RightSupport::Notifier::Base
|
26
|
+
|
27
|
+
# arbitrary upper bound on payload dumped to log output.
|
28
|
+
MAX_PAYLOAD = 4096
|
29
|
+
|
30
|
+
def notify(notification)
|
31
|
+
# payload is usually not logged unless DEBUG_MODE=true but for consistency
|
32
|
+
# we will always include it in the log with the error message.
|
33
|
+
#
|
34
|
+
# note that there is no payload blacklisting as access to view the logs in
|
35
|
+
# production is already restricted. what is most important about
|
36
|
+
# blacklisting data is that it not reach the less secure error notification
|
37
|
+
# services where it is stored permanently.
|
38
|
+
payload = notification.payload
|
39
|
+
if payload.empty?
|
40
|
+
payload = nil
|
41
|
+
else
|
42
|
+
payload = payload.inspect[0, MAX_PAYLOAD]
|
43
|
+
end
|
44
|
+
|
45
|
+
# we are interested in the root cause for logging purposes except when
|
46
|
+
# DEBUG_MODE==true, in which case iterate over all causes without the limit
|
47
|
+
# on backtrace size.
|
48
|
+
if debug_mode?
|
49
|
+
backtrace_decoder.walk_error(notification.error, raw_trace: true) do |cause|
|
50
|
+
log_error(notification, cause, cause.backtrace, payload)
|
51
|
+
payload = nil # do not repeat payload
|
52
|
+
end
|
53
|
+
else
|
54
|
+
cause, frames = backtrace_decoder.walk_error(notification.error)
|
55
|
+
log_error(notification, cause, frames, payload)
|
56
|
+
end
|
57
|
+
true
|
58
|
+
rescue ::Exception => e
|
59
|
+
lines = [e.class.name, e.message, e.backtrace].flatten.compact
|
60
|
+
msg = "Failed to notify: #{lines.join("\n")}"
|
61
|
+
logger.error(msg)
|
62
|
+
true
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def log_error(notification, cause, trace_or_frames, payload)
|
68
|
+
lines = []
|
69
|
+
lines << "[%s] Error in %s#%s" % [
|
70
|
+
notification.error_token,
|
71
|
+
notification.component,
|
72
|
+
notification.action
|
73
|
+
]
|
74
|
+
lines << ''
|
75
|
+
lines << "Error: %s %s" % [cause.class.name, cause.message]
|
76
|
+
lines << ''
|
77
|
+
if (trace_or_frames || []).empty?
|
78
|
+
lines << 'Missing backtrace.'
|
79
|
+
else
|
80
|
+
lines << 'Dump:'
|
81
|
+
if trace_or_frames.first.is_a?(::String)
|
82
|
+
lines += trace_or_frames.map { |t| " #{t}" }
|
83
|
+
else
|
84
|
+
lines << ::RightSupport::Notifier::Utility::BacktraceDecoder.format_frames(trace_or_frames)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
if payload
|
88
|
+
lines << ''
|
89
|
+
lines << "Payload: #{payload}"
|
90
|
+
end
|
91
|
+
logger.error(lines.join("\n"))
|
92
|
+
end
|
93
|
+
|
94
|
+
end # RightSupport::Notifier::Logger
|
@@ -0,0 +1,57 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2016 RightScale Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
23
|
+
# definition for a notification to be sent to notifiers.
|
24
|
+
class RightSupport::Notifier::Notification
|
25
|
+
|
26
|
+
attr_reader :error, :error_token, :env, :payload, :global_session
|
27
|
+
attr_reader :component, :action
|
28
|
+
|
29
|
+
# @param [Exception] error leading to notification
|
30
|
+
# @param [Hash] options
|
31
|
+
# @option options [String] :error_token or nil to generate a new token
|
32
|
+
# @option options [Hash] :env from Rack or nil or empty
|
33
|
+
# @option options [Hash] :global_session or nil or empty
|
34
|
+
# @option options [String] :component as optional name of component (the
|
35
|
+
# controller name, etc.). default='unknown'.
|
36
|
+
# @option options [String] :action as optional name of action (the controller
|
37
|
+
# method invoked by request URI, etc.). default='unknown'.
|
38
|
+
def initialize(error, options = {})
|
39
|
+
options = {
|
40
|
+
error_token: nil,
|
41
|
+
env: nil,
|
42
|
+
payload: nil,
|
43
|
+
global_session: nil,
|
44
|
+
component: 'unknown',
|
45
|
+
action: 'unknown',
|
46
|
+
}.merge(options)
|
47
|
+
|
48
|
+
@error = error
|
49
|
+
@error_token = (options[:error_token] || ::RightSupport::Data::Token.generate).to_s
|
50
|
+
@env = (options[:env] || {}).to_hash
|
51
|
+
@payload = (options[:payload] || {}).to_hash
|
52
|
+
@global_session = (options[:global_session] || {}).to_hash
|
53
|
+
@component = options[:component].to_s
|
54
|
+
@action = options[:action].to_s
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,234 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2016 RightScale Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
23
|
+
# decoder for ruby-formatted backtraces.
|
24
|
+
class RightSupport::Notifier::Utility::BacktraceDecoder
|
25
|
+
|
26
|
+
# default limit on size of decoded stack trace arrays.
|
27
|
+
DEFAULT_BACKTRACE_LIMIT = 10
|
28
|
+
|
29
|
+
# hard limit on size of decoded stack trace arrays.
|
30
|
+
MAX_BACKTRACE_LIMIT = 255
|
31
|
+
|
32
|
+
# limit on error walk when following cause chain.
|
33
|
+
WALK_LIMIT = 32
|
34
|
+
|
35
|
+
# regular expression used to decode a line of ruby backtrace.
|
36
|
+
BACKTRACE_REGEXP = /^(.*):(\d+)?:in `(.*)'$/
|
37
|
+
|
38
|
+
# ellipsis used for limited trace.
|
39
|
+
ELLIPSIS = '...'.freeze
|
40
|
+
|
41
|
+
attr_reader :backtrace_offset, :backtrace_limit, :path_blacklist, :root_path
|
42
|
+
|
43
|
+
class Frame
|
44
|
+
attr_reader :file, :line, :function
|
45
|
+
|
46
|
+
def initialize(file, line, function)
|
47
|
+
@file = file
|
48
|
+
@line = line
|
49
|
+
@function = function
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_s
|
53
|
+
# the following encoded format is the ruby style (strange quotes, etc.)
|
54
|
+
"#{@file}:#{@line}:in `#{@function}'"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# @param [Hash] options
|
59
|
+
# @option options [Integer] :backtrace_offset as number of stack frames to
|
60
|
+
# skip initially. default = 0.
|
61
|
+
# @option options [Integer] :backtrace_limit as maximum number of stack frames
|
62
|
+
# to decode or negative for all. default = 10.
|
63
|
+
# @option options [String|Array] :path_blacklist for unwanted paths in
|
64
|
+
# backtrace. the blacklisted path substring causes the frame to be omitted
|
65
|
+
# when it appears anywhere in the traced path. default = none.
|
66
|
+
# @option options [String] :root_path for application to be removed from
|
67
|
+
# decoded file paths. default = <working directory>
|
68
|
+
def initialize(options = {})
|
69
|
+
options = {
|
70
|
+
backtrace_offset: 0,
|
71
|
+
backtrace_limit: DEFAULT_BACKTRACE_LIMIT,
|
72
|
+
path_blacklist: nil
|
73
|
+
}.merge(options)
|
74
|
+
@backtrace_offset = Integer(options[:backtrace_offset])
|
75
|
+
@backtrace_limit = Integer(options[:backtrace_limit])
|
76
|
+
if @backtrace_limit < 0 || @backtrace_limit > MAX_BACKTRACE_LIMIT
|
77
|
+
@backtrace_limit = MAX_BACKTRACE_LIMIT
|
78
|
+
end
|
79
|
+
@path_blacklist = Array(options[:path_blacklist])
|
80
|
+
|
81
|
+
# resolve current application root path.
|
82
|
+
@root_path = ::File.expand_path(options[:root_path] || ::Dir.pwd) + '/'
|
83
|
+
@root_parent_path = ::File.dirname(@root_path) + '/'
|
84
|
+
|
85
|
+
# resolve current 'lib/ruby' path.
|
86
|
+
@ruby_lib_path = ::File.expand_path(::RbConfig::CONFIG['rubylibprefix']) + '/'
|
87
|
+
@ruby_lib_parent_path = ::File.dirname(@ruby_lib_path) + '/'
|
88
|
+
end
|
89
|
+
|
90
|
+
# decodes lines of backtrace in string form into their component parts.
|
91
|
+
#
|
92
|
+
# borrowed originally from:
|
93
|
+
# @see https://github.com/airbrake/airbrake-ruby/blob/master/lib/airbrake-ruby/backtrace.rb
|
94
|
+
#
|
95
|
+
# @param [Array] trace from error.backtrace or Kernel.caller or empty or nil
|
96
|
+
#
|
97
|
+
# @return [Array] frames of decoded trace or empty
|
98
|
+
def decode(trace)
|
99
|
+
frames = []
|
100
|
+
trace ||= []
|
101
|
+
trace = trace[@backtrace_offset..-1] if @backtrace_offset > 0
|
102
|
+
gems_finder = '/gems/'
|
103
|
+
seen_root_path = false
|
104
|
+
trace.each do |t|
|
105
|
+
has_root_path = false
|
106
|
+
omit = false
|
107
|
+
file = nil
|
108
|
+
line = 0
|
109
|
+
function = nil
|
110
|
+
if m = BACKTRACE_REGEXP.match(t)
|
111
|
+
file = m[1]
|
112
|
+
line = Integer(m[2])
|
113
|
+
function = m[3]
|
114
|
+
@path_blacklist.each do |pbl|
|
115
|
+
if file.include?(pbl)
|
116
|
+
omit = true
|
117
|
+
break
|
118
|
+
end
|
119
|
+
end
|
120
|
+
unless omit
|
121
|
+
# remove base path from the frame file path for simplicity and because
|
122
|
+
# the absolute root path is only meaningful on the file system where
|
123
|
+
# the code is running. the root path basename (i.e. the application
|
124
|
+
# directory name) is kept for display.
|
125
|
+
if file.start_with?(@root_path)
|
126
|
+
file = file[@root_parent_path.length..-1]
|
127
|
+
has_root_path = true
|
128
|
+
elsif file.start_with?(@ruby_lib_path)
|
129
|
+
# remove absoluteness of the lib/ruby path for the same reason.
|
130
|
+
file = file[@ruby_lib_parent_path.length..-1]
|
131
|
+
end
|
132
|
+
|
133
|
+
# remove everything before '/gems/' on the assumption that the
|
134
|
+
# rubygems or '/vendor/bundle/.../gems/' prefixes are only noise that
|
135
|
+
# makes the trace harder to read for a human. '/gems/' may also appear
|
136
|
+
# more than once so use the last found.
|
137
|
+
if gems_offset = file.rindex(gems_finder)
|
138
|
+
file = file[gems_offset + gems_finder.length..-1]
|
139
|
+
end
|
140
|
+
end
|
141
|
+
else
|
142
|
+
# show a failure message for other patterns such as java plugins for
|
143
|
+
# ruby and nonsense only because we have no known use cases.
|
144
|
+
# FIX: support any needed patterns.
|
145
|
+
file = t
|
146
|
+
function = '<< trace decoder error >>'
|
147
|
+
end
|
148
|
+
unless omit
|
149
|
+
# enforce the backtrace limit when configured *unless* we have not yet
|
150
|
+
# seen the root path (i.e. the application root). in this case we want
|
151
|
+
# to keep walking the backtrace until we have shown at least one frame
|
152
|
+
# that references the application. otherwise the backtrace may not be
|
153
|
+
# useful for debugging purposes.
|
154
|
+
done = seen_root_path && @backtrace_limit >= 0 && frames.size >= @backtrace_limit
|
155
|
+
|
156
|
+
# show at least one full application frame before appending ellipsis.
|
157
|
+
seen_root_path ||= has_root_path
|
158
|
+
|
159
|
+
# set an ellipsis as function name of last frame when over limit.
|
160
|
+
frames << Frame.new(file, line, done ? ELLIPSIS : function)
|
161
|
+
break if done
|
162
|
+
end
|
163
|
+
end
|
164
|
+
frames
|
165
|
+
end
|
166
|
+
|
167
|
+
# @return [Array] the current frames for the caller with limit, filters, etc.
|
168
|
+
def caller
|
169
|
+
# note that Kernel.caller always omits the immediate frame so that the
|
170
|
+
# caller of this method is at the top of the trace.
|
171
|
+
decode(::Kernel.caller)
|
172
|
+
end
|
173
|
+
|
174
|
+
# walks the error to find the backtrace closest to the root cause.
|
175
|
+
#
|
176
|
+
# @param [Exception] error to walk
|
177
|
+
# @param [Hash] options
|
178
|
+
# @option options [TrueClass|FalseClass] :raw_trace true to return the raw
|
179
|
+
# backtrace that was closest to cause, false to decode it (default).
|
180
|
+
#
|
181
|
+
# borrowed originally from:
|
182
|
+
# @see "https://github.com/airbrake/airbrake-ruby/blob/master/lib/airbrake-ruby/nested_exception.rb"
|
183
|
+
#
|
184
|
+
# @yield [cause] yields each walked error without decoding its backtrace.
|
185
|
+
# @yieldparam [Exception] error in cause chain that is currently being walked.
|
186
|
+
#
|
187
|
+
# @return [Array] tuple of [cause, trace_or_frames]
|
188
|
+
def walk_error(error, options={}, &callback)
|
189
|
+
options = {
|
190
|
+
raw_trace: false
|
191
|
+
}.merge(options)
|
192
|
+
cause = error
|
193
|
+
trace = cause.backtrace || ::Kernel.caller[1..-1]
|
194
|
+
callback.call(cause) if callback
|
195
|
+
counter = 0
|
196
|
+
while cause.respond_to?(:cause)
|
197
|
+
next_cause = cause.cause
|
198
|
+
if next_cause && next_cause != cause
|
199
|
+
cause = next_cause
|
200
|
+
callback.call(cause) if callback
|
201
|
+
trace = cause.backtrace if cause.backtrace
|
202
|
+
|
203
|
+
# sanity check that the error is wrapped an unbelievable number of
|
204
|
+
# times or that the cause != next_cause logic has failed somehow; no
|
205
|
+
# infinite loops.
|
206
|
+
counter += 1
|
207
|
+
break if counter >= WALK_LIMIT
|
208
|
+
else
|
209
|
+
break
|
210
|
+
end
|
211
|
+
end
|
212
|
+
unless options[:raw_trace]
|
213
|
+
trace = decode(trace)
|
214
|
+
end
|
215
|
+
[cause, trace]
|
216
|
+
end
|
217
|
+
|
218
|
+
# formats the given frames for display on the console.
|
219
|
+
#
|
220
|
+
# @param [Array] frames to format
|
221
|
+
#
|
222
|
+
# @return [String] formatted frames
|
223
|
+
def self.format_frames(frames)
|
224
|
+
frames.map do |frame|
|
225
|
+
if frame.function == ELLIPSIS
|
226
|
+
line = ELLIPSIS
|
227
|
+
else
|
228
|
+
line = frame
|
229
|
+
end
|
230
|
+
" #{line}" # indent and .to_s
|
231
|
+
end.join("\n")
|
232
|
+
end
|
233
|
+
|
234
|
+
end # RightSupport::Notifier::Utility::BacktraceDecoder
|
@@ -0,0 +1,29 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2016 RightScale Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
23
|
+
module RightSupport
|
24
|
+
module Notifier
|
25
|
+
module Utility
|
26
|
+
autoload :BacktraceDecoder, 'right_support/notifiers/utilities/backtrace_decoder'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2016 RightScale Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
23
|
+
module RightSupport
|
24
|
+
module Notifier
|
25
|
+
autoload :Airbrake, 'right_support/notifiers/airbrake'
|
26
|
+
autoload :Base, 'right_support/notifiers/base'
|
27
|
+
autoload :Blacklister, 'right_support/notifiers/blacklisters'
|
28
|
+
autoload :Logger, 'right_support/notifiers/logger'
|
29
|
+
autoload :Notification, 'right_support/notifiers/notification'
|
30
|
+
autoload :Utility, 'right_support/notifiers/utilities'
|
31
|
+
end
|
32
|
+
end
|
@@ -35,8 +35,8 @@ module RightSupport::Rack
|
|
35
35
|
# with Rack::Logger or another middleware that provides logging services.
|
36
36
|
class RequestLogger
|
37
37
|
|
38
|
-
#
|
39
|
-
#
|
38
|
+
# default backtrace limit that can be overridden by configuring the
|
39
|
+
# middleware with a specific :backtrace_limit value.
|
40
40
|
BACKTRACE_LIMIT = 10
|
41
41
|
|
42
42
|
# for debug-mode only logging. debug-mode can also be enabled by the
|
@@ -79,6 +79,8 @@ module RightSupport::Rack
|
|
79
79
|
# override the usual global session behavior of always responding with a
|
80
80
|
# 'Set-Cookie' header regardless of status code. defaults to having the
|
81
81
|
# same value as the :normalize_40x option.
|
82
|
+
# @option options [Integer] :backtrace_limit maximum number of stack frames
|
83
|
+
# to display on error or negative for all (default = 10)
|
82
84
|
def initialize(app, options = {})
|
83
85
|
@app = app
|
84
86
|
@only = options[:only] || []
|
@@ -89,6 +91,8 @@ module RightSupport::Rack
|
|
89
91
|
@normalize_40x = !!options[:normalize_40x]
|
90
92
|
@normalize_40x_set_cookie = options[:normalize_40x_set_cookie]
|
91
93
|
@normalize_40x_set_cookie = @normalize_40x if @normalize_40x_set_cookie.nil?
|
94
|
+
@backtrace_decoder = ::RightSupport::Notifier::Utility::BacktraceDecoder.new(
|
95
|
+
backtrace_limit: Integer(options[:backtrace_limit] || BACKTRACE_LIMIT))
|
92
96
|
end
|
93
97
|
|
94
98
|
# Add a logger to the Rack environment and call the next middleware.
|
@@ -224,18 +228,18 @@ module RightSupport::Rack
|
|
224
228
|
# Formats a concise error message with limited backtrace, etc.
|
225
229
|
#
|
226
230
|
# @param [Exception] e to format
|
231
|
+
# @param [Exception] e to format
|
227
232
|
#
|
228
233
|
# @return [String] formatted error
|
229
|
-
def self.format_exception_message(e)
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
end
|
234
|
+
def self.format_exception_message(e, backtrace_decoder = nil)
|
235
|
+
backtrace_decoder ||= ::RightSupport::Notifier::Utility::BacktraceDecoder.new
|
236
|
+
trace = ::RightSupport::Notifier::Utility::BacktraceDecoder.format_frames(
|
237
|
+
backtrace_decoder.decode(e.backtrace))
|
234
238
|
kind = e.class.name
|
235
239
|
if (msg = e.message) && !msg.empty? && msg != kind
|
236
240
|
kind = "#{kind}: #{msg}"
|
237
241
|
end
|
238
|
-
[kind,
|
242
|
+
[kind, trace].join("\n")
|
239
243
|
end
|
240
244
|
|
241
245
|
# @return [String] accepted/generated request UUID or else 'missing' to
|
@@ -380,7 +384,7 @@ module RightSupport::Rack
|
|
380
384
|
#
|
381
385
|
# @return [TrueClass] always true
|
382
386
|
def log_exception(logger, e)
|
383
|
-
logger.error(self.class.format_exception_message(e))
|
387
|
+
logger.error(self.class.format_exception_message(e, @backtrace_decoder))
|
384
388
|
rescue
|
385
389
|
# no-op; something is seriously messed up by this point...
|
386
390
|
end
|