right_support 2.11.3 → 2.12.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|