Class: RightSupport::Notifier::Utility::BacktraceDecoder
- Defined in:
- lib/right_support/notifiers/utilities/backtrace_decoder.rb
Overview
decoder for ruby-formatted backtraces.
Defined Under Namespace
Classes: Frame
Constant Summary collapse
- DEFAULT_BACKTRACE_LIMIT =
default limit on size of decoded stack trace arrays.
10
- MAX_BACKTRACE_LIMIT =
hard limit on size of decoded stack trace arrays.
255
- WALK_LIMIT =
limit on error walk when following cause chain.
32
- BACKTRACE_REGEXP =
regular expression used to decode a line of ruby backtrace.
/^(.*):(\d+)?:in `(.*)'$/
- ELLIPSIS =
ellipsis used for limited trace.
'...'.freeze
Instance Attribute Summary collapse
-
#backtrace_limit ⇒ Object
readonly
Returns the value of attribute backtrace_limit.
-
#backtrace_offset ⇒ Object
readonly
Returns the value of attribute backtrace_offset.
-
#path_blacklist ⇒ Object
readonly
Returns the value of attribute path_blacklist.
-
#root_path ⇒ Object
readonly
Returns the value of attribute root_path.
Class Method Summary collapse
-
.format_frames(frames) ⇒ String
formats the given frames for display on the console.
Instance Method Summary collapse
-
#caller ⇒ Array
The current frames for the caller with limit, filters, etc.
-
#decode(trace) ⇒ Array
decodes lines of backtrace in string form into their component parts.
-
#initialize(options = {}) ⇒ BacktraceDecoder
constructor
A new instance of BacktraceDecoder.
-
#walk_error(error, options = {}) {|cause| ... } ⇒ Array
walks the error to find the backtrace closest to the root cause.
Constructor Details
#initialize(options = {}) ⇒ BacktraceDecoder
Returns a new instance of BacktraceDecoder.
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/right_support/notifiers/utilities/backtrace_decoder.rb', line 68 def initialize( = {}) = { backtrace_offset: 0, backtrace_limit: DEFAULT_BACKTRACE_LIMIT, path_blacklist: nil }.merge() @backtrace_offset = Integer([:backtrace_offset]) @backtrace_limit = Integer([:backtrace_limit]) if @backtrace_limit < 0 || @backtrace_limit > MAX_BACKTRACE_LIMIT @backtrace_limit = MAX_BACKTRACE_LIMIT end @path_blacklist = Array([:path_blacklist]) # resolve current application root path. @root_path = ::File.([:root_path] || ::Dir.pwd) + '/' @root_parent_path = ::File.dirname(@root_path) + '/' # resolve current 'lib/ruby' path. @ruby_lib_path = ::File.(::RbConfig::CONFIG['rubylibprefix']) + '/' @ruby_lib_parent_path = ::File.dirname(@ruby_lib_path) + '/' end |
Instance Attribute Details
#backtrace_limit ⇒ Object (readonly)
Returns the value of attribute backtrace_limit.
41 42 43 |
# File 'lib/right_support/notifiers/utilities/backtrace_decoder.rb', line 41 def backtrace_limit @backtrace_limit end |
#backtrace_offset ⇒ Object (readonly)
Returns the value of attribute backtrace_offset.
41 42 43 |
# File 'lib/right_support/notifiers/utilities/backtrace_decoder.rb', line 41 def backtrace_offset @backtrace_offset end |
#path_blacklist ⇒ Object (readonly)
Returns the value of attribute path_blacklist.
41 42 43 |
# File 'lib/right_support/notifiers/utilities/backtrace_decoder.rb', line 41 def path_blacklist @path_blacklist end |
#root_path ⇒ Object (readonly)
Returns the value of attribute root_path.
41 42 43 |
# File 'lib/right_support/notifiers/utilities/backtrace_decoder.rb', line 41 def root_path @root_path end |
Class Method Details
.format_frames(frames) ⇒ String
formats the given frames for display on the console.
243 244 245 246 247 248 249 250 251 252 |
# File 'lib/right_support/notifiers/utilities/backtrace_decoder.rb', line 243 def self.format_frames(frames) frames.map do |frame| if frame.function == ELLIPSIS line = ELLIPSIS else line = frame end " #{line}" # indent and .to_s end.join("\n") end |
Instance Method Details
#caller ⇒ Array
Returns the current frames for the caller with limit, filters, etc.
188 189 190 191 192 |
# File 'lib/right_support/notifiers/utilities/backtrace_decoder.rb', line 188 def caller # note that Kernel.caller always omits the immediate frame so that the # caller of this method is at the top of the trace. decode(::Kernel.caller) end |
#decode(trace) ⇒ Array
decodes lines of backtrace in string form into their component parts.
borrowed originally from:
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
# File 'lib/right_support/notifiers/utilities/backtrace_decoder.rb', line 98 def decode(trace) frames = [] trace ||= [] trace = trace[@backtrace_offset..-1] if @backtrace_offset > 0 seen_root_path = false trace.each do |t| has_root_path = false omit = false file = nil line = 0 function = nil if m = BACKTRACE_REGEXP.match(t) file = m[1] line = Integer(m[2]) function = m[3] @path_blacklist.each do |pbl| if file.include?(pbl) omit = true break end end unless omit # remove base path from the frame file path for simplicity and because # the absolute root path is only meaningful on the file system where # the code is running. the root path basename (i.e. the application # directory name) is kept for display. if file.start_with?(@root_path) file = file[@root_parent_path.length..-1] has_root_path = true elsif file.start_with?(@ruby_lib_path) # remove absoluteness of the lib/ruby path for the same reason. file = file[@ruby_lib_parent_path.length..-1] end # remove everything before '/gems/' on the assumption that the # rubygems or '/vendor/bundle/.../gems/' prefixes are only noise that # makes the trace harder to read for a human. '/gems/' may also appear # more than once so use the last found. # # also note that vendored gitted gems and their binstubs can appear # directly under the '<app root>/vendor/bundle|cache/' directory and # so are not explicitly under a '/gems/' directory. founder = nil founder_offset = nil %w( /gems/ /vendor/cache/ /vendor/bundle/ ).each do |finder| if founder_offset = file.rindex(finder) founder = finder break end end if founder file = file[founder_offset + founder.length..-1] # note that vendored gems are also technically on the 'root path' # but we do not want to include them in the 'seen root path' # logic because we want to see the trace back to real app code. has_root_path = false end end else # show a failure message for other patterns such as java plugins for # ruby and nonsense only because we have no known use cases. # FIX: support any needed patterns. file = t function = '<< trace decoder error >>' end unless omit # enforce the backtrace limit when configured *unless* we have not yet # seen the root path (i.e. the application root). in this case we want # to keep walking the backtrace until we have shown at least one frame # that references the application. otherwise the backtrace may not be # useful for debugging purposes. done = seen_root_path && @backtrace_limit >= 0 && frames.size >= @backtrace_limit # show at least one full application frame before appending ellipsis. seen_root_path ||= has_root_path # set an ellipsis as function name of last frame when over limit. frames << Frame.new(file, line, done ? ELLIPSIS : function) break if done end end frames end |
#walk_error(error, options = {}) {|cause| ... } ⇒ Array
walks the error to find the backtrace closest to the root cause.
borrowed originally from:
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 |
# File 'lib/right_support/notifiers/utilities/backtrace_decoder.rb', line 208 def walk_error(error, ={}, &callback) = { raw_trace: false }.merge() cause = error trace = cause.backtrace || ::Kernel.caller[1..-1] callback.call(cause) if callback counter = 0 while cause.respond_to?(:cause) next_cause = cause.cause if next_cause && next_cause != cause cause = next_cause callback.call(cause) if callback trace = cause.backtrace if cause.backtrace # sanity check that the error is wrapped an unbelievable number of # times or that the cause != next_cause logic has failed somehow; no # infinite loops. counter += 1 break if counter >= WALK_LIMIT else break end end unless [:raw_trace] trace = decode(trace) end [cause, trace] end |