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.
Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +4 -0
  3. data/VERSION +1 -1
  4. data/lib/right_support/notifiers/airbrake.rb +194 -0
  5. data/lib/right_support/notifiers/base.rb +73 -0
  6. data/lib/right_support/notifiers/blacklisters/base.rb +48 -0
  7. data/lib/right_support/notifiers/blacklisters/canonical.rb +60 -0
  8. data/lib/right_support/notifiers/blacklisters/regular_expression.rb +86 -0
  9. data/{features/support/file_utils_bundler_mixin.rb → lib/right_support/notifiers/blacklisters/simple.rb} +21 -20
  10. data/lib/right_support/notifiers/blacklisters/snake_case.rb +60 -0
  11. data/lib/right_support/notifiers/blacklisters/wildcard.rb +65 -0
  12. data/lib/right_support/notifiers/blacklisters.rb +34 -0
  13. data/lib/right_support/notifiers/logger.rb +94 -0
  14. data/lib/right_support/notifiers/notification.rb +57 -0
  15. data/lib/right_support/notifiers/utilities/backtrace_decoder.rb +234 -0
  16. data/lib/right_support/notifiers/utilities.rb +29 -0
  17. data/lib/right_support/notifiers.rb +32 -0
  18. data/lib/right_support/rack/request_logger.rb +13 -9
  19. data/lib/right_support.rb +1 -0
  20. data/right_support.gemspec +19 -70
  21. metadata +17 -69
  22. data/.coveralls.yml +0 -2
  23. data/.rspec +0 -3
  24. data/.simplecov +0 -6
  25. data/.travis.yml +0 -13
  26. data/Gemfile +0 -51
  27. data/Gemfile.lock +0 -153
  28. data/features/balancer_error_handling.feature +0 -34
  29. data/features/balancer_health_check.feature +0 -33
  30. data/features/hash_tools.feature +0 -27
  31. data/features/http_client_timeout.feature +0 -19
  32. data/features/serialization.feature +0 -113
  33. data/features/step_definitions/hash_tools_steps.rb +0 -41
  34. data/features/step_definitions/http_client_steps.rb +0 -27
  35. data/features/step_definitions/request_balancer_steps.rb +0 -93
  36. data/features/step_definitions/ruby_steps.rb +0 -176
  37. data/features/step_definitions/serialization_steps.rb +0 -133
  38. data/features/step_definitions/server_steps.rb +0 -134
  39. data/features/support/env.rb +0 -148
  40. data/right_support.rconf +0 -9
  41. data/spec/config/feature_set_spec.rb +0 -83
  42. data/spec/crypto/signed_hash_spec.rb +0 -73
  43. data/spec/data/hash_tools_spec.rb +0 -602
  44. data/spec/data/mash_spec.rb +0 -313
  45. data/spec/data/token_spec.rb +0 -21
  46. data/spec/data/uuid_spec.rb +0 -45
  47. data/spec/db/cassandra_model_part1_spec.rb +0 -84
  48. data/spec/db/cassandra_model_part2_spec.rb +0 -73
  49. data/spec/db/cassandra_model_spec.rb +0 -375
  50. data/spec/fixtures/encrypted_priv_rsa.pem +0 -30
  51. data/spec/fixtures/good_priv_dsa.pem +0 -12
  52. data/spec/fixtures/good_priv_rsa.pem +0 -15
  53. data/spec/fixtures/good_pub_dsa.ssh +0 -1
  54. data/spec/fixtures/good_pub_rsa.pem +0 -5
  55. data/spec/fixtures/good_pub_rsa.ssh +0 -1
  56. data/spec/log/exception_logger_spec.rb +0 -76
  57. data/spec/log/filter_logger_spec.rb +0 -66
  58. data/spec/log/mixin_spec.rb +0 -141
  59. data/spec/log/multiplexer_spec.rb +0 -54
  60. data/spec/log/null_logger_spec.rb +0 -36
  61. data/spec/log/step_level_logger_spec.rb +0 -49
  62. data/spec/log/system_logger_spec.rb +0 -172
  63. data/spec/net/address_helper_spec.rb +0 -57
  64. data/spec/net/dns_spec.rb +0 -187
  65. data/spec/net/http_client_spec.rb +0 -181
  66. data/spec/net/lb/health_check_spec.rb +0 -417
  67. data/spec/net/lb/round_robin_spec.rb +0 -15
  68. data/spec/net/lb/sticky_spec.rb +0 -92
  69. data/spec/net/request_balancer_spec.rb +0 -690
  70. data/spec/net/s3_helper_spec.rb +0 -160
  71. data/spec/net/ssl_spec.rb +0 -42
  72. data/spec/net/string_encoder_spec.rb +0 -58
  73. data/spec/rack/log_setter_spec.rb +0 -5
  74. data/spec/rack/request_logger_spec.rb +0 -225
  75. data/spec/rack/request_tracker_spec.rb +0 -115
  76. data/spec/rack/runtime_spec.rb +0 -49
  77. data/spec/ruby/easy_singleton_spec.rb +0 -72
  78. data/spec/ruby/object_extensions_spec.rb +0 -27
  79. data/spec/ruby/string_extensions_spec.rb +0 -98
  80. data/spec/spec_helper.rb +0 -188
  81. data/spec/stats/activity_spec.rb +0 -425
  82. data/spec/stats/exceptions_spec.rb +0 -247
  83. data/spec/stats/helpers_spec.rb +0 -685
  84. data/spec/validation/openssl_spec.rb +0 -37
  85. 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
- # limit exception backtrace length; usually only the first few lines are
39
- # significant. the same error dumped repeatedly can make grepping difficult.
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
- trace = e.backtrace || []
231
- if trace.size > BACKTRACE_LIMIT
232
- trace = trace[0, BACKTRACE_LIMIT] << '...'
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, *trace].join("\n")
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
data/lib/right_support.rb CHANGED
@@ -15,4 +15,5 @@ module RightSupport
15
15
  autoload :Stats, 'right_support/stats'
16
16
  autoload :CI, 'right_support/ci'
17
17
  autoload :Deployment, 'right_support/deployment'
18
+ autoload :Notifier, 'right_support/notifiers'
18
19
  end