Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

backtrace_shortener arrives.

  • Loading branch information...
commit d6ea25fd8e24ee429f3b7a740d6667e5cf43a40a 0 parents
@philc authored
4 .gitignore
@@ -0,0 +1,4 @@
+*.gem
+.bundle
+Gemfile.lock
+pkg/*
4 Gemfile
@@ -0,0 +1,4 @@
+source "http://rubygems.org"
+
+# Specify your gem's dependencies in backtrace_shortener.gemspec
+gemspec
117 README.markdown
@@ -0,0 +1,117 @@
+bactrace_shortener
+------------------
+
+This shortens the backtraces of exceptions to make debugging more friendly. Exceptions which are thrown in
+Ruby apps which use rbenv and gems like Sinatra can be taller than one terminal screen, and each line can be
+long. See below for an example of the pain. Shortening these backtraces makes debugging more friendly.
+
+I find myself using this in many of my projects, so it's now packaged as a gem.
+
+Usage
+-----
+
+ $ gem install backtrace_shortener
+
+ (or you can just take backtrace_shortener.rb and copy it to your project. It's short.)
+
+ In your code
+
+ require "backtrace_shortener"
+
+Features
+--------
+
+With it installed, an unwieldy exception backtrace which previously looked like this:
+
+ RuntimeError: It's no good, I can't maneuver!
+ /Users/philc/api_server/server.rb:86:in `block in <class:ServerRoot>'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/sinatra-1.3.2/lib/sinatra/base.rb:1212:in `call'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/sinatra-1.3.2/lib/sinatra/base.rb:1212:in `block in compile!'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/sinatra-1.3.2/lib/sinatra/base.rb:785:in `[]'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/sinatra-1.3.2/lib/sinatra/base.rb:785:in `block (3 levels) in route!'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/sinatra-1.3.2/lib/sinatra/base.rb:801:in `route_eval'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/sinatra-1.3.2/lib/sinatra/base.rb:785:in `block (2 levels) in route!'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/sinatra-1.3.2/lib/sinatra/base.rb:822:in `block in process_route'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/sinatra-1.3.2/lib/sinatra/base.rb:820:in `catch'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/sinatra-1.3.2/lib/sinatra/base.rb:820:in `process_route'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/sinatra-1.3.2/lib/sinatra/base.rb:784:in `block in route!'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/sinatra-1.3.2/lib/sinatra/base.rb:783:in `each'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/sinatra-1.3.2/lib/sinatra/base.rb:783:in `route!'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/sinatra-1.3.2/lib/sinatra/base.rb:886:in `dispatch!'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/sinatra-1.3.2/lib/sinatra/base.rb:719:in `block in call!'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/sinatra-1.3.2/lib/sinatra/base.rb:871:in `block in invoke'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/sinatra-1.3.2/lib/sinatra/base.rb:871:in `catch'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/sinatra-1.3.2/lib/sinatra/base.rb:871:in `invoke'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/sinatra-1.3.2/lib/sinatra/base.rb:719:in `call!'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/sinatra-1.3.2/lib/sinatra/base.rb:705:in `call'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/rack-protection-1.2.0/lib/rack/protection/xss_header.rb:22:in `call'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/rack-protection-1.2.0/lib/rack/protection/path_traversal.rb:16:in `call'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/rack-protection-1.2.0/lib/rack/protection/json_csrf.rb:17:in `call'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/rack-protection-1.2.0/lib/rack/protection/base.rb:47:in `call'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/rack-protection-1.2.0/lib/rack/protection/xss_header.rb:22:in `call'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/rack-1.4.1/lib/rack/nulllogger.rb:9:in `call'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/rack-1.4.1/lib/rack/head.rb:9:in `call'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/sinatra-1.3.2/lib/sinatra/base.rb:1334:in `block in call'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/sinatra-1.3.2/lib/sinatra/base.rb:1416:in `synchronize'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/sinatra-1.3.2/lib/sinatra/base.rb:1334:in `call'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/rack-1.4.1/lib/rack/lint.rb:48:in `_call'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/rack-1.4.1/lib/rack/lint.rb:36:in `call'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/rack-1.4.1/lib/rack/showexceptions.rb:24:in `call'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/rack-1.4.1/lib/rack/commonlogger.rb:20:in `call'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/rack-1.4.1/lib/rack/chunked.rb:43:in `call'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/rack-1.4.1/lib/rack/content_length.rb:14:in `call'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/thin-1.3.1/lib/thin/connection.rb:80:in `block in pre_process'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/thin-1.3.1/lib/thin/connection.rb:78:in `catch'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/thin-1.3.1/lib/thin/connection.rb:78:in `pre_process'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/thin-1.3.1/lib/thin/connection.rb:53:in `process'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/thin-1.3.1/lib/thin/connection.rb:38:in `receive_data'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/eventmachine-0.12.10/lib/eventmachine.rb:256:in `run_machine'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/eventmachine-0.12.10/lib/eventmachine.rb:256:in `run'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/thin-1.3.1/lib/thin/backends/base.rb:61:in `start'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/thin-1.3.1/lib/thin/server.rb:159:in `start'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/rack-1.4.1/lib/rack/handler/thin.rb:13:in `run'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/rack-1.4.1/lib/rack/server.rb:265:in `start'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/rack-1.4.1/lib/rack/server.rb:137:in `start'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/rack-1.4.1/bin/rackup:4:in `<top (required)>'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/bin/rackup:19:in `load'
+ /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/bin/rackup:19:in `<main>'api_server
+
+
+Gets shortened to this. Long method chains which are within gems get collapsed, and the RubyGems path (in my case, `/Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1`) gets trimmed from each line.
+
+ RuntimeError: It's no good, I can't maneuver!
+ /Users/philc/html5player/api_server/app/server_root.rb:88:in `block in <class:ServerRoot>'
+ <..>
+ .../gems/sinatra-1.3.2/lib/sinatra/base.rb:705:in `call'
+ <..>
+ .../gems/rack-protection-1.2.0/lib/rack/protection/xss_header.rb:22:in `call'
+ .../gems/rack-1.4.1/lib/rack/nulllogger.rb:9:in `call'
+ .../gems/rack-1.4.1/lib/rack/head.rb:9:in `call'
+ <..>
+ .../gems/sinatra-1.3.2/lib/sinatra/base.rb:1334:in `call'
+ <..>
+ .../gems/rack-1.4.1/lib/rack/content_length.rb:14:in `call'
+ <..>
+ .../gems/thin-1.3.1/lib/thin/connection.rb:38:in `receive_data'
+ .../gems/eventmachine-0.12.10/lib/eventmachine.rb:256:in `run_machine'
+ .../gems/eventmachine-0.12.10/lib/eventmachine.rb:256:in `run'
+ .../gems/thin-1.3.1/lib/thin/backends/base.rb:61:in `start'
+ .../gems/thin-1.3.1/lib/thin/server.rb:159:in `start'
+ <..>
+ .../gems/rack-1.4.1/bin/rackup:4:in `<top (required)>'
+ .../bin/rackup:19:in `load'
+ .../bin/rackup:19:in `<main>
+
+
+If you want to see even less or filter things specific to your application, add your own filters. For example,
+this filter rejects any line originating from a Gem:
+
+ BacktraceShortener.filters.unshift(Proc.new do |backtrace|
+ backtrace.reject { |line| line.include?(Gem.dir) }
+ end)
+
+Another example which truncates the backtrace to include only the first 10 lines:
+
+ BacktraceShortener.filters.unshift(Proc.new { |backtrace| backtrace[0, 10] }
+
+If you need to access the full backtrace while debugging, you can call `my_exception.full_backtrace`.
1  Rakefile
@@ -0,0 +1 @@
+require "bundler/gem_tasks"
19 backtrace_shortener.gemspec
@@ -0,0 +1,19 @@
+# -*- encoding: utf-8 -*-
+$:.push File.expand_path("../lib", __FILE__)
+require "backtrace_shortener/version"
+
+Gem::Specification.new do |s|
+ s.name = "backtrace_shortener"
+ s.version = BacktraceShortener::VERSION
+ s.authors = ["Phil Crosby"]
+ s.email = ["phil.crosby@gmail.com"]
+ s.homepage = "http://github.com/philc/backtrace_shortener"
+ s.summary = "Shortens the backtraces of exceptions to make debugging more friendly."
+
+ s.rubyforge_project = "backtrace_shortener"
+
+ s.files = `git ls-files`.split("\n")
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
+ s.require_paths = ["lib"]
+end
72 lib/backtrace_shortener.rb
@@ -0,0 +1,72 @@
+# This can patch the Exception class to prune the size of the backtraces and to make each line shorter.
+# The idea is to improve the developer experience, because exceptions in apps using rbenv and gems can be
+# taller than one terminal screen and each line can be long. See how painful this is with a 50 line backtrace:
+# ...
+# /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/sequel-3.28.0/lib/sequel/adapters/mysql.rb:175:in `query'
+# /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/sequel-3.28.0/lib/sequel/adapters/mysql.rb:175:in `block in _execute'
+# ...
+#
+# To apply this patch, invoke BacktraceCleanear.monkey_patch_the_exception_class!
+#
+# If you want to access the full backtrace while debugging, you can use my_exception.full_backtrace.
+# If you want to write your own filter, append your own Proc to BacktraceShortener.filters:
+# BacktraceShortener.filters.unshift(Proc.new { |backtrace| backtrace[0, 10] }) # Shortens to 10 lines.
+module BacktraceShortener
+ def self.monkey_patch_the_exception_class!
+ return if Exception.new.respond_to?(:backtrace_prior_to_backtrace_shortener_monkey_patch)
+
+ Exception.class_eval do
+ alias :backtrace_prior_to_backtrace_shortener_monkey_patch :backtrace
+ alias :full_backtrace :backtrace_prior_to_backtrace_shortener_monkey_patch
+
+ def backtrace
+ backtrace = backtrace_prior_to_backtrace_shortener_monkey_patch
+ return nil if backtrace.nil?
+ BacktraceShortener.filters.inject(backtrace) { |backtrace, filter| filter.call(backtrace) }
+ end
+ end
+ end
+
+ # Abbreviate any long gem paths, e.g.
+ # /Users/philc/.rbenv/versions/1.9.2-p290/lib/ruby/gems/1.9.1/gems/sequel-3.28.0/lib
+ # => .../gems/1.9.1/gems/sequel-3.28.0/lib
+ def self.abbreviate_gem_directory_name(backtrace)
+ backtrace.map do |line|
+ line.sub(Gem.dir, "...")
+ end
+ end
+
+ # Backtraces which involve gems can include many lines from within the gem's internals. This usually
+ # isn't helpful. Collapse those long sequences of lines and include just the first and last line.
+ def self.collapse_gems(backtrace)
+ current_gem = nil
+ current_gem_line_number = nil
+ i = backtrace.size - 1
+
+ while i >= 0
+ line_gem = gem_from_line(backtrace[i])
+ if line_gem != current_gem || i == 0
+ if current_gem && (current_gem_line_number - i) > 2
+ backtrace[i..current_gem_line_number] = [backtrace[i], "<..>", backtrace[current_gem_line_number]]
+ end
+ current_gem = line_gem
+ current_gem_line_number = i
+ end
+ i -= 1
+ end
+ backtrace
+ end
+
+ # Returns the gem in the given backtrace line, or nil if the line does not include a gem in it.
+ def self.gem_from_line(backtrace_line)
+ # Pull out "sequel-3.28.0" from this path: ".../lib/ruby/gems/1.9.1/gems/sequel-3.28.0/lib/..."
+ (%r{/ruby/gems/[^/]+/gems/([^/]+)/}.match(backtrace_line) || [])[1]
+ end
+
+ class << self
+ attr_accessor :filters
+ end
+
+ # The two default filters are 1) collapsing long runs of lines from gems and 2) abbreviating those lines.
+ self.filters = [method(:collapse_gems), method(:abbreviate_gem_directory_name)]
+end
3  lib/backtrace_shortener/version.rb
@@ -0,0 +1,3 @@
+module BacktraceShortener
+ VERSION = "0.0.1"
+end
Please sign in to comment.
Something went wrong with that request. Please try again.