├── .gitignore ├── History.txt ├── MIT-LICENSE.txt ├── README.md ├── Rakefile ├── Thorfile ├── lib └── rack │ ├── bug.rb │ └── bug │ ├── autoloading.rb │ ├── filtered_backtrace.rb │ ├── options.rb │ ├── panel.rb │ ├── panel_app.rb │ ├── panels │ ├── active_record_panel.rb │ ├── active_record_panel │ │ └── activerecord_extensions.rb │ ├── cache_panel.rb │ ├── cache_panel │ │ ├── dalli_extension.rb │ │ ├── memcache_extension.rb │ │ ├── panel_app.rb │ │ └── stats.rb │ ├── log_panel.rb │ ├── log_panel │ │ └── logger_extension.rb │ ├── memory_panel.rb │ ├── mongo_panel.rb │ ├── mongo_panel │ │ ├── mongo_extension.rb │ │ └── stats.rb │ ├── rails_info_panel.rb │ ├── redis_panel.rb │ ├── redis_panel │ │ ├── redis_extension.rb │ │ └── stats.rb │ ├── request_variables_panel.rb │ ├── sphinx_panel.rb │ ├── sphinx_panel │ │ ├── sphinx_extension.rb │ │ └── stats.rb │ ├── sql_panel.rb │ ├── sql_panel │ │ ├── panel_app.rb │ │ ├── query.rb │ │ └── sql_extension.rb │ ├── templates_panel.rb │ ├── templates_panel │ │ ├── actionview_extension.rb │ │ ├── rendering.rb │ │ └── trace.rb │ └── timer_panel.rb │ ├── params_signature.rb │ ├── public │ └── __rack_bug__ │ │ ├── bookmarklet.html │ │ ├── bookmarklet.js │ │ ├── bug.css │ │ ├── bug.js │ │ ├── jquery-1.3.2.js │ │ ├── jquery.tablesorter.min.js │ │ └── spinner.gif │ ├── rack_static_bug_avoider.rb │ ├── redirect_interceptor.rb │ ├── render.rb │ ├── toolbar.rb │ └── views │ ├── error.html.erb │ ├── panels │ ├── active_record.html.erb │ ├── cache.html.erb │ ├── execute_sql.html.erb │ ├── explain_sql.html.erb │ ├── log.html.erb │ ├── mongo.html.erb │ ├── profile_sql.html.erb │ ├── rails_info.html.erb │ ├── redis.html.erb │ ├── request_variables.html.erb │ ├── sphinx.html.erb │ ├── sql.html.erb │ ├── templates.html.erb │ ├── timer.html.erb │ └── view_cache.html.erb │ ├── redirect.html.erb │ └── toolbar.html.erb ├── rack-bug.gemspec └── spec ├── custom_matchers.rb ├── fixtures ├── config.ru ├── dummy_panel.rb └── sample_app.rb ├── rack ├── bug │ └── panels │ │ ├── active_record_panel_spec.rb │ │ ├── cache_panel_spec.rb │ │ ├── log_panel_spec.rb │ │ ├── memory_panel_spec.rb │ │ ├── mongo_panel_spec.rb │ │ ├── rails_info_panel_spec.rb │ │ ├── redis_panel_spec.rb │ │ ├── sql_panel_spec.rb │ │ ├── templates_panel_spec.rb │ │ └── timer_panel_spec.rb └── bug_spec.rb ├── rcov.opts ├── spec.opts └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | pkg 3 | TODO -------------------------------------------------------------------------------- /History.txt: -------------------------------------------------------------------------------- 1 | == HEAD 2 | 3 | * New features 4 | 5 | * Can use LoggerPanel on ruby stdlib Logger in non-rails app (Tim Connor) 6 | 7 | * Bug fixes 8 | 9 | * Fix profile, explain and select in the queries tab, fixes issue #22 (ebertech) 10 | 11 | * Minor fixes 12 | 13 | * Explicitly require 'digest/sha1' (Jérémy Lecour) 14 | * Eliminate unreachable code in params signature validation (Tim Connor) 15 | 16 | * Compatibilty 17 | 18 | * Make Redis panel compatible with latest redis-rb gem, without breaking older redis-rb versions (Luke Melia) 19 | * Fix issues with Ruby 1.9.2 and with mysql2 (George Ogota) 20 | 21 | * Other 22 | 23 | * Refactoring and code cleanup (Tim Connor) 24 | * Testing cleanup - better isolation of Rails vs. non-Rails in tests (Tim Connor) 25 | 26 | == 0.3.0 / 2010-05-28 27 | 28 | * New features 29 | 30 | * Log panel includes log level and timestamp (Tim Connor) 31 | * Sphinx panel (George Chatzigeorgiou) 32 | * Backtraces for Redis panel (Luke Melia & Joey Aghion) 33 | 34 | * Minor fixes 35 | 36 | * Don't "enable" rack bug if you hit cancel on the bookmarklet prompt (Mischa Fierer) 37 | 38 | * Compatibilty 39 | 40 | * backtrace filtering now supports more than just Rails (Alex Chaffee) 41 | * compatibility with current rack-test (Luke Melia & Joey Aghion) 42 | * update Sinatra sample app (Tim Conner) 43 | 44 | == 0.2.1 45 | 46 | * The beginning of recorded history 47 | -------------------------------------------------------------------------------- /MIT-LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 Bryan Helmkamp 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Rack::Bug [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/brynary/rack-bug) 2 | ========= 3 | 4 | * Repository: [http://github.com/brynary/rack-bug](http://github.com/brynary/rack-bug) 5 | 6 | Description 7 | ----------- 8 | 9 | Rack::Bug adds a diagnostics toolbar to Rack apps. When enabled, it injects a floating div 10 | allowing exploration of logging, database queries, template rendering times, etc. 11 | 12 | Features 13 | -------- 14 | 15 | * Password-based security 16 | * IP-based security 17 | * Rack::Bug instrumentation/reporting is broken up into panels. 18 | * Panels in default configuration: 19 | * Rails Info 20 | * Timer 21 | * Request Variables 22 | * SQL 23 | * Active Record 24 | * Cache 25 | * Templates 26 | * Log 27 | * Memory 28 | * Other bundled panels: 29 | * Redis 30 | * Sphinx 31 | * The API for adding your own panels is simple and powerful 32 | 33 | Rails quick start 34 | --------------------------- 35 | 36 | script/plugin install git://github.com/brynary/rack-bug.git 37 | 38 | In config/environments/development.rb, add: 39 | 40 | config.middleware.use "Rack::Bug", 41 | :secret_key => "someverylongandveryhardtoguesspreferablyrandomstring" 42 | 43 | Add the bookmarklet to your browser: 44 | 45 | open http://RAILS_APP/__rack_bug__/bookmarklet.html 46 | 47 | Using with non-Rails Rack apps 48 | ------------------------------ 49 | 50 | Just 'use Rack::Bug' as any other middleware. See the SampleApp in the spec/fixtures folder for an example Sinatra app. 51 | 52 | If you wish to use the logger panel define the LOGGER constant that is a ruby Logger or ActiveSupport::BufferedLogger 53 | 54 | Configuring custom panels 55 | ------------------------- 56 | 57 | Specify the set of panels you want, in the order you want them to appear: 58 | 59 | require "rack/bug" 60 | 61 | ActionController::Dispatcher.middleware.use Rack::Bug, 62 | :secret_key => "someverylongandveryhardtoguesspreferablyrandomstring", 63 | :panel_classes => [ 64 | Rack::Bug::TimerPanel, 65 | Rack::Bug::RequestVariablesPanel, 66 | Rack::Bug::RedisPanel, 67 | Rack::Bug::TemplatesPanel, 68 | Rack::Bug::LogPanel, 69 | Rack::Bug::MemoryPanel 70 | ] 71 | 72 | 73 | Running Rack::Bug in staging or production 74 | ------------------------------------------ 75 | 76 | We have have found that Rack::Bug is fast enough to run in production for specific troubleshooting efforts. 77 | 78 | ### Configuration #### 79 | 80 | Add the middleware configuration to an initializer or the appropriate environment files, taking the rest of this section into consideration. 81 | 82 | ### Security #### 83 | 84 | Restrict access to particular IP addresses: 85 | 86 | require "ipaddr" 87 | 88 | ActionController::Dispatcher.middleware.use "Rack::Bug" 89 | :secret_key => "someverylongandveryhardtoguesspreferablyrandomstring", 90 | :ip_masks => [IPAddr.new("2.2.2.2/0")] 91 | 92 | Restrict access using a password: 93 | 94 | ActionController::Dispatcher.middleware.use "Rack::Bug", 95 | :secret_key => "someverylongandveryhardtoguesspreferablyrandomstring", 96 | :password => "yourpassword" 97 | 98 | 99 | Authors 100 | ------- 101 | 102 | - Maintained by [Bryan Helmkamp](mailto:bryan@brynary.com) 103 | - Contributions from Luke Melia, Joey Aghion, Tim Connor, and more 104 | 105 | Thanks 106 | ------ 107 | Inspiration for Rack::Bug is primarily from the Django debug toolbar. Additional ideas from Rails footnotes, Rack's ShowException middleware, Oink, and Rack::Cache 108 | 109 | Thanks to Weplay.com for supporting the development of Rack::Bug 110 | 111 | Development 112 | ----------- 113 | For development, you'll need to install the following gems: rspec, rack-test, webrat, sinatra 114 | 115 | License 116 | ------- 117 | 118 | See MIT-LICENSE.txt in this directory. 119 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "rubygems" 2 | require "spec/rake/spectask" 3 | 4 | Spec::Rake::SpecTask.new do |t| 5 | t.spec_opts = ['--options', "\"#{File.dirname(__FILE__)}/spec/spec.opts\""] 6 | end 7 | 8 | desc "Run all specs in spec directory with RCov" 9 | Spec::Rake::SpecTask.new(:rcov) do |t| 10 | t.spec_opts = ['--options', "\"#{File.dirname(__FILE__)}/spec/spec.opts\""] 11 | t.rcov = true 12 | t.rcov_opts = lambda do 13 | IO.readlines(File.dirname(__FILE__) + "/spec/rcov.opts").map {|l| l.chomp.split " "}.flatten 14 | end 15 | end 16 | 17 | desc "Run the specs" 18 | task :default => :spec 19 | 20 | desc 'Removes trailing whitespace' 21 | task :whitespace do 22 | sh %{find . -name '*.rb' -exec sed -i '' 's/ *$//g' {} \\;} 23 | end 24 | -------------------------------------------------------------------------------- /Thorfile: -------------------------------------------------------------------------------- 1 | module GemHelpers 2 | 3 | def generate_gemspec 4 | $LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), "lib"))) 5 | require "rack/bug" 6 | 7 | Gem::Specification.new do |s| 8 | s.name = "rack-bug" 9 | s.version = Rack::Bug::VERSION 10 | s.author = "Bryan Helmkamp" 11 | s.email = "bryan@brynary.com" 12 | s.homepage = "http://github.com/brynary/rack-bug" 13 | s.summary = "Debugging toolbar for Rack applications implemented as middleware" 14 | # s.description = "TODO" 15 | s.rubyforge_project = "rack-bug" 16 | 17 | require "git" 18 | repo = Git.open(".") 19 | 20 | s.files = normalize_files(repo.ls_files.keys - repo.lib.ignored_files) 21 | s.test_files = normalize_files(Dir['spec/**/*.rb'] - repo.lib.ignored_files) 22 | 23 | s.has_rdoc = true 24 | s.extra_rdoc_files = %w[README.md MIT-LICENSE.txt] 25 | 26 | s.add_dependency "rack", ">= 1.0" 27 | s.add_development_dependency "webrat" 28 | s.add_development_dependency "rspec" 29 | s.add_development_dependency "sinatra" 30 | s.add_development_dependency "git" 31 | end 32 | end 33 | 34 | def normalize_files(array) 35 | # only keep files, no directories, and sort 36 | array.select do |path| 37 | File.file?(path) 38 | end.sort 39 | end 40 | 41 | # Adds extra space when outputting an array. This helps create better version 42 | # control diffs, because otherwise it is all on the same line. 43 | def prettyify_array(gemspec_ruby, array_name) 44 | gemspec_ruby.gsub(/s\.#{array_name.to_s} = \[.+?\]/) do |match| 45 | leadin, files = match[0..-2].split("[") 46 | leadin + "[\n #{files.split(",").join(",\n ")}\n ]" 47 | end 48 | end 49 | 50 | def read_gemspec 51 | @read_gemspec ||= eval(File.read("rack-bug.gemspec")) 52 | end 53 | 54 | def sh(command) 55 | puts command 56 | system command 57 | end 58 | end 59 | 60 | class Default < Thor 61 | include GemHelpers 62 | 63 | desc "gemspec", "Regenerate rack-bug.gemspec" 64 | def gemspec 65 | File.open("rack-bug.gemspec", "w") do |file| 66 | gemspec_ruby = generate_gemspec.to_ruby 67 | gemspec_ruby = prettyify_array(gemspec_ruby, :files) 68 | gemspec_ruby = prettyify_array(gemspec_ruby, :test_files) 69 | gemspec_ruby = prettyify_array(gemspec_ruby, :extra_rdoc_files) 70 | 71 | file.write gemspec_ruby 72 | end 73 | 74 | puts "Wrote gemspec to rack-bug.gemspec" 75 | read_gemspec.validate 76 | end 77 | 78 | desc "build", "Build a rack-bug gem" 79 | def build 80 | sh "gem build rack-bug.gemspec" 81 | FileUtils.mkdir_p "pkg" 82 | FileUtils.mv read_gemspec.file_name, "pkg" 83 | end 84 | 85 | desc "install", "Install the latest built gem" 86 | def install 87 | sh "gem install --local pkg/#{read_gemspec.file_name}" 88 | end 89 | 90 | desc "release", "Release the current branch to GitHub and Gemcutter" 91 | def release 92 | gemspec 93 | build 94 | Release.new.tag 95 | Release.new.gem 96 | end 97 | end 98 | 99 | class Release < Thor 100 | include GemHelpers 101 | 102 | desc "tag", "Tag the gem on the origin server" 103 | def tag 104 | release_tag = "v#{read_gemspec.version}" 105 | sh "git tag -a #{release_tag} -m 'Tagging #{release_tag}'" 106 | sh "git push origin #{release_tag}" 107 | end 108 | 109 | desc "gem", "Push the gem to Gemcutter" 110 | def gem 111 | sh "gem push pkg/#{read_gemspec.file_name}" 112 | end 113 | end 114 | -------------------------------------------------------------------------------- /lib/rack/bug.rb: -------------------------------------------------------------------------------- 1 | require "ipaddr" 2 | require "digest" 3 | require "rack" 4 | require "digest/sha1" 5 | require "rack/bug/autoloading" 6 | 7 | class Rack::Bug 8 | include Options 9 | 10 | VERSION = "0.3.0" 11 | 12 | class SecurityError < StandardError 13 | end 14 | 15 | def self.enable 16 | Thread.current["rack-bug.enabled"] = true 17 | end 18 | 19 | def self.disable 20 | Thread.current["rack-bug.enabled"] = false 21 | end 22 | 23 | def self.enabled? 24 | Thread.current["rack-bug.enabled"] == true 25 | end 26 | 27 | def initialize(app, options = {}, &block) 28 | @app = asset_server(app) 29 | initialize_options options 30 | instance_eval(&block) if block_given? 31 | 32 | @toolbar = Toolbar.new(RedirectInterceptor.new(@app)) 33 | end 34 | 35 | 36 | def call(env) 37 | env.replace @default_options.merge(env) 38 | @env = env 39 | @original_request = Rack::Request.new(@env) 40 | 41 | if toolbar_requested? && ip_authorized? && password_authorized? && toolbar_xhr? 42 | @toolbar.call(env) 43 | else 44 | @app.call(env) 45 | end 46 | end 47 | 48 | private 49 | 50 | def toolbar_xhr? 51 | !@original_request.xhr? || @original_request.path =~ /^\/__rack_bug__/ 52 | end 53 | 54 | def asset_server(app) 55 | RackStaticBugAvoider.new(app, Rack::Static.new(app, :urls => ["/__rack_bug__"], :root => public_path)) 56 | end 57 | 58 | def public_path 59 | ::File.expand_path(::File.dirname(__FILE__) + "/bug/public") 60 | end 61 | 62 | def toolbar_requested? 63 | @original_request.cookies["rack_bug_enabled"] 64 | end 65 | 66 | def ip_authorized? 67 | return true unless options["rack-bug.ip_masks"] 68 | 69 | options["rack-bug.ip_masks"].any? do |ip_mask| 70 | ip_mask.include?(IPAddr.new(@original_request.ip)) 71 | end 72 | end 73 | 74 | def password_authorized? 75 | return true unless options["rack-bug.password"] 76 | 77 | expected_sha = Digest::SHA1.hexdigest ["rack_bug", options["rack-bug.password"]].join(":") 78 | actual_sha = @original_request.cookies["rack_bug_password"] 79 | 80 | actual_sha == expected_sha 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /lib/rack/bug/autoloading.rb: -------------------------------------------------------------------------------- 1 | class Rack::Bug 2 | autoload :FilteredBacktrace, "rack/bug/filtered_backtrace" 3 | autoload :Options, "rack/bug/options" 4 | autoload :Panel, "rack/bug/panel" 5 | autoload :PanelApp, "rack/bug/panel_app" 6 | autoload :ParamsSignature, "rack/bug/params_signature" 7 | autoload :RackStaticBugAvoider, "rack/bug/rack_static_bug_avoider" 8 | autoload :RedirectInterceptor, "rack/bug/redirect_interceptor" 9 | autoload :Render, "rack/bug/render" 10 | autoload :Toolbar, "rack/bug/toolbar" 11 | 12 | # Panels 13 | autoload :ActiveRecordPanel, "rack/bug/panels/active_record_panel" 14 | autoload :CachePanel, "rack/bug/panels/cache_panel" 15 | autoload :LogPanel, "rack/bug/panels/log_panel" 16 | autoload :MemoryPanel, "rack/bug/panels/memory_panel" 17 | autoload :RailsInfoPanel, "rack/bug/panels/rails_info_panel" 18 | autoload :RedisPanel, "rack/bug/panels/redis_panel" 19 | autoload :MongoPanel, "rack/bug/panels/mongo_panel" 20 | autoload :RequestVariablesPanel, "rack/bug/panels/request_variables_panel" 21 | autoload :SQLPanel, "rack/bug/panels/sql_panel" 22 | autoload :TemplatesPanel, "rack/bug/panels/templates_panel" 23 | autoload :TimerPanel, "rack/bug/panels/timer_panel" 24 | autoload :SphinxPanel, "rack/bug/panels/sphinx_panel" 25 | end 26 | -------------------------------------------------------------------------------- /lib/rack/bug/filtered_backtrace.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | class Bug 3 | module FilteredBacktrace 4 | 5 | def backtrace 6 | @backtrace 7 | end 8 | 9 | def has_backtrace? 10 | filtered_backtrace.any? 11 | end 12 | 13 | def filtered_backtrace 14 | @filtered_backtrace ||= @backtrace.map{|l| l.to_s.strip }.select do |line| 15 | root_for_backtrace_filtering.nil? || 16 | (line.index(root_for_backtrace_filtering) == 0) && !(line.index(root_for_backtrace_filtering("vendor")) == 0) 17 | end 18 | end 19 | 20 | def root_for_backtrace_filtering(sub_path = nil) 21 | if defined?(Rails) && Rails.respond_to?(:root) 22 | (sub_path ? Rails.root.join(sub_path) : Rails.root).to_s 23 | else 24 | root = if defined?(RAILS_ROOT) 25 | RAILS_ROOT 26 | elsif defined?(ROOT) 27 | ROOT 28 | elsif defined?(Sinatra::Application) 29 | Sinatra::Application.root 30 | else 31 | nil 32 | end 33 | sub_path ? ::File.join(root, sub_path) : root 34 | end.to_s 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/rack/bug/options.rb: -------------------------------------------------------------------------------- 1 | class Rack::Bug 2 | 3 | module Options 4 | class << self 5 | private 6 | def option_accessor(key) 7 | define_method(key) { || read_option(key) } 8 | define_method("#{key}=") { |value| write_option(key, value) } 9 | define_method("#{key}?") { || !! read_option(key) } 10 | end 11 | end 12 | 13 | option_accessor :secret_key 14 | option_accessor :ip_masks 15 | option_accessor :password 16 | option_accessor :panel_classes 17 | option_accessor :intercept_redirects 18 | 19 | # The underlying options Hash. During initialization (or outside of a 20 | # request), this is a default values Hash. During a request, this is the 21 | # Rack environment Hash. The default values Hash is merged in underneath 22 | # the Rack environment before each request is processed. 23 | def options 24 | @env || @default_options 25 | end 26 | 27 | # Set multiple options. 28 | def options=(hash={}) 29 | hash.each { |key,value| write_option(key, value) } 30 | end 31 | 32 | # Set an option. When +option+ is a Symbol, it is set in the Rack 33 | # Environment as "rack-cache.option". When +option+ is a String, it 34 | # exactly as specified. The +option+ argument may also be a Hash in 35 | # which case each key/value pair is merged into the environment as if 36 | # the #set method were called on each. 37 | def set(option, value=self, &block) 38 | if block_given? 39 | write_option option, block 40 | elsif value == self 41 | self.options = option.to_hash 42 | else 43 | write_option option, value 44 | end 45 | end 46 | 47 | private 48 | 49 | def read_option(key) 50 | options[option_name(key)] 51 | end 52 | 53 | def write_option(key, value) 54 | options[option_name(key)] = value 55 | end 56 | 57 | def option_name(key) 58 | case key 59 | when Symbol ; "rack-bug.#{key}" 60 | when String ; key 61 | else raise ArgumentError 62 | end 63 | end 64 | 65 | def initialize_options(options={}) 66 | @default_options = { 67 | 'rack-bug.ip_masks' => [IPAddr.new("127.0.0.1")], 68 | 'rack-bug.password' => nil, 69 | 'rack-bug.verbose' => nil, 70 | 'rack-bug.secret_key' => nil, 71 | 'rack-bug.intercept_redirects' => false, 72 | 'rack-bug.panels' => [], 73 | 'rack-bug.panel_classes' => [ 74 | RailsInfoPanel, 75 | TimerPanel, 76 | RequestVariablesPanel, 77 | SQLPanel, 78 | ActiveRecordPanel, 79 | CachePanel, 80 | TemplatesPanel, 81 | LogPanel, 82 | MemoryPanel 83 | ] 84 | } 85 | self.options = options 86 | end 87 | 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /lib/rack/bug/panel.rb: -------------------------------------------------------------------------------- 1 | require "erb" 2 | 3 | module Rack 4 | class Bug 5 | 6 | # Panels are also Rack middleware 7 | class Panel 8 | include Render 9 | include ERB::Util 10 | 11 | attr_reader :request 12 | 13 | def initialize(app) 14 | if panel_app 15 | @app = Rack::Cascade.new([panel_app, app]) 16 | else 17 | @app = app 18 | end 19 | end 20 | 21 | def call(env) 22 | before(env) 23 | status, headers, body = @app.call(env) 24 | @request = Request.new(env) 25 | after(env, status, headers, body) 26 | env["rack-bug.panels"] << self 27 | return [status, headers, body] 28 | end 29 | 30 | def panel_app 31 | nil 32 | end 33 | 34 | def has_content? 35 | true 36 | end 37 | 38 | def before(env) 39 | end 40 | 41 | def after(env, status, headers, body) 42 | end 43 | 44 | def render(template) 45 | end 46 | 47 | end 48 | 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/rack/bug/panel_app.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | class Bug 3 | 4 | class PanelApp 5 | include Rack::Bug::Render 6 | 7 | attr_reader :request 8 | 9 | def call(env) 10 | @request = Rack::Request.new(env) 11 | dispatch 12 | end 13 | 14 | def render_template(*args) 15 | Rack::Response.new([super]).to_a 16 | end 17 | 18 | def params 19 | @request.GET 20 | end 21 | 22 | def not_found 23 | [404, {}, []] 24 | end 25 | 26 | def validate_params 27 | ParamsSignature.new(request).validate! 28 | end 29 | 30 | end 31 | 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/rack/bug/panels/active_record_panel.rb: -------------------------------------------------------------------------------- 1 | require "rack/bug/panels/active_record_panel/activerecord_extensions" 2 | 3 | module Rack 4 | class Bug 5 | 6 | class ActiveRecordPanel < Panel 7 | 8 | def self.record(class_name) 9 | return unless Rack::Bug.enabled? 10 | records[class_name] += 1 11 | end 12 | 13 | def self.reset 14 | Thread.current["rack.bug.active_records"] = Hash.new { 0 } 15 | end 16 | 17 | def self.records 18 | Thread.current["rack.bug.active_records"] ||= Hash.new { 0 } 19 | end 20 | 21 | def self.total 22 | records.inject(0) do |memo, (key, value)| 23 | memo + value 24 | end 25 | end 26 | 27 | def name 28 | "active_record" 29 | end 30 | 31 | def heading 32 | "#{self.class.total} AR Objects" 33 | end 34 | 35 | def content 36 | records = self.class.records.to_a.sort_by { |key, value| value }.reverse 37 | result = render_template "panels/active_record", :records => records 38 | self.class.reset 39 | result 40 | end 41 | 42 | end 43 | 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/rack/bug/panels/active_record_panel/activerecord_extensions.rb: -------------------------------------------------------------------------------- 1 | if defined?(ActiveRecord) 2 | ActiveRecord::Base.class_eval do 3 | 4 | if instance_methods.include?("after_initialize") 5 | def after_initialize_with_rack_bug 6 | Rack::Bug::ActiveRecordPanel.record(self.class.base_class.name) 7 | after_initialize_without_rack_bug 8 | end 9 | 10 | alias_method_chain :after_initialize, :rack_bug 11 | else 12 | def after_initialize 13 | Rack::Bug::ActiveRecordPanel.record(self.class.base_class.name) 14 | end 15 | end 16 | 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/rack/bug/panels/cache_panel.rb: -------------------------------------------------------------------------------- 1 | require "rack/bug/panels/cache_panel/memcache_extension" 2 | require "rack/bug/panels/cache_panel/dalli_extension" 3 | 4 | module Rack 5 | class Bug 6 | 7 | class CachePanel < Panel 8 | autoload :PanelApp, "rack/bug/panels/cache_panel/panel_app" 9 | autoload :Stats, "rack/bug/panels/cache_panel/stats" 10 | 11 | def self.record(method, *keys, &block) 12 | return block.call unless Rack::Bug.enabled? 13 | 14 | start_time = Time.now 15 | result = block.call 16 | total_time = Time.now - start_time 17 | hit = result.nil? ? false : true 18 | stats.record_call(method, total_time * 1_000, hit, *keys) 19 | return result 20 | end 21 | 22 | def self.reset 23 | Thread.current["rack.bug.cache"] = Stats.new 24 | end 25 | 26 | def self.stats 27 | Thread.current["rack.bug.cache"] ||= Stats.new 28 | end 29 | 30 | def panel_app 31 | PanelApp.new 32 | end 33 | 34 | def name 35 | "cache" 36 | end 37 | 38 | def heading 39 | "Cache: %.2fms (#{self.class.stats.queries.size} calls)" % self.class.stats.time 40 | end 41 | 42 | def content 43 | result = render_template "panels/cache", :stats => self.class.stats 44 | self.class.reset 45 | return result 46 | end 47 | 48 | end 49 | 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/rack/bug/panels/cache_panel/dalli_extension.rb: -------------------------------------------------------------------------------- 1 | begin 2 | 3 | require 'dalli' 4 | 5 | Dalli::Client.class_eval do 6 | def perform_with_rack_bug(op, *args) 7 | Rack::Bug::CachePanel.record(op, args.first) do 8 | perform_without_rack_bug(op, *args) 9 | end 10 | end 11 | 12 | alias_method_chain :perform, :rack_bug 13 | end 14 | 15 | rescue NameError, LoadError 16 | end -------------------------------------------------------------------------------- /lib/rack/bug/panels/cache_panel/memcache_extension.rb: -------------------------------------------------------------------------------- 1 | if defined?(Memcached) 2 | Memcached.class_eval do 3 | 4 | def set_with_rack_bug(key, value, timeout=0, marshal=true) 5 | Rack::Bug::CachePanel.record(:set, key) do 6 | set_without_rack_bug(key, value, timeout, marshal) 7 | end 8 | end 9 | 10 | def add_with_rack_bug(key, value, timeout=0, marshal=true) 11 | Rack::Bug::CachePanel.record(:add, key) do 12 | add_without_rack_bug(key, value, timeout, marshal) 13 | end 14 | end 15 | 16 | def increment_with_rack_bug(key, offset=1) 17 | Rack::Bug::CachePanel.record(:incr, key) do 18 | increment_without_rack_bug(key, offset) 19 | end 20 | end 21 | 22 | def decrement_with_rack_bug(key, offset=1) 23 | Rack::Bug::CachePanel.record(:decr, key) do 24 | decrement_without_rack_bug(key, offset) 25 | end 26 | end 27 | 28 | def replace_with_rack_bug(key, value, timeout=0, marshal=true) 29 | Rack::Bug::CachePanel.record(:replace, key) do 30 | replace_without_rack_bug(key, value, timeout, marshal) 31 | end 32 | end 33 | 34 | def append_with_rack_bug(key, value) 35 | Rack::Bug::CachePanel.record(:append, key) do 36 | append_without_rack_bug(key, value) 37 | end 38 | end 39 | 40 | def prepend_with_rack_bug(key, value) 41 | Rack::Bug::CachePanel.record(:prepend, key) do 42 | prepend_without_rack_bug(key, value) 43 | end 44 | end 45 | 46 | def delete_with_rack_bug(key) 47 | Rack::Bug::CachePanel.record(:delete, key) do 48 | delete_without_rack_bug(key) 49 | end 50 | end 51 | 52 | def get_with_rack_bug(keys, marshal=true) 53 | if keys.is_a? Array 54 | Rack::Bug::CachePanel.record(:get_multi, *keys) do 55 | get_without_rack_bug(keys, marshal) 56 | end 57 | else 58 | Rack::Bug::CachePanel.record(:get, keys) do 59 | get_without_rack_bug(keys, marshal) 60 | end 61 | end 62 | end 63 | 64 | alias_method_chain :decrement, :rack_bug 65 | alias_method_chain :get, :rack_bug 66 | alias_method_chain :increment, :rack_bug 67 | alias_method_chain :set, :rack_bug 68 | alias_method_chain :add, :rack_bug 69 | alias_method_chain :replace, :rack_bug 70 | alias_method_chain :delete, :rack_bug 71 | alias_method_chain :prepend, :rack_bug 72 | alias_method_chain :append, :rack_bug 73 | end 74 | end 75 | 76 | if defined?(MemCache) 77 | MemCache.class_eval do 78 | 79 | def decr_with_rack_bug(key, amount = 1) 80 | Rack::Bug::CachePanel.record(:decr, key) do 81 | decr_without_rack_bug(key, amount) 82 | end 83 | end 84 | 85 | def get_with_rack_bug(key, raw = false) 86 | Rack::Bug::CachePanel.record(:get, key) do 87 | get_without_rack_bug(key, raw) 88 | end 89 | end 90 | 91 | def get_multi_with_rack_bug(*keys) 92 | Rack::Bug::CachePanel.record(:get_multi, *keys) do 93 | get_multi_without_rack_bug(*keys) 94 | end 95 | end 96 | 97 | def incr_with_rack_bug(key, amount = 1) 98 | Rack::Bug::CachePanel.record(:incr, key) do 99 | incr_without_rack_bug(key, amount) 100 | end 101 | end 102 | 103 | def set_with_rack_bug(key, value, expiry = 0, raw = false) 104 | Rack::Bug::CachePanel.record(:set, key) do 105 | set_without_rack_bug(key, value, expiry, raw) 106 | end 107 | end 108 | 109 | def add_with_rack_bug(key, value, expiry = 0, raw = false) 110 | Rack::Bug::CachePanel.record(:add, key) do 111 | add_without_rack_bug(key, value, expiry, raw) 112 | end 113 | end 114 | 115 | def delete_with_rack_bug(key, expiry = 0) 116 | Rack::Bug::CachePanel.record(:delete, key) do 117 | delete_without_rack_bug(key, expiry) 118 | end 119 | end 120 | 121 | alias_method_chain :decr, :rack_bug 122 | alias_method_chain :get, :rack_bug 123 | alias_method_chain :get_multi, :rack_bug 124 | alias_method_chain :incr, :rack_bug 125 | alias_method_chain :set, :rack_bug 126 | alias_method_chain :add, :rack_bug 127 | alias_method_chain :delete, :rack_bug 128 | end 129 | end 130 | -------------------------------------------------------------------------------- /lib/rack/bug/panels/cache_panel/panel_app.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | class Bug 3 | class CachePanel 4 | 5 | class PanelApp < ::Rack::Bug::PanelApp 6 | 7 | def dispatch 8 | case request.path_info 9 | when "/__rack_bug__/view_cache" then view_cache 10 | when "/__rack_bug__/delete_cache" then delete_cache 11 | when "/__rack_bug__/delete_cache_list" then delete_cache_list 12 | else not_found 13 | end 14 | end 15 | 16 | def ok 17 | Rack::Response.new(["OK"]).to_a 18 | end 19 | 20 | def view_cache 21 | validate_params 22 | render_template "panels/view_cache", :key => params["key"], :value => Rails.cache.read(params["key"]) 23 | end 24 | 25 | def delete_cache 26 | validate_params 27 | raise "Rails not found... can't delete key" unless defined?(Rails) 28 | Rails.cache.delete(params["key"]) 29 | ok 30 | end 31 | 32 | def delete_cache_list 33 | validate_params 34 | raise "Rails not found... can't delete key" unless defined?(Rails) 35 | 36 | params.each do |key, value| 37 | next unless key =~ /^keys_/ 38 | Rails.cache.delete(value) 39 | end 40 | 41 | ok 42 | end 43 | 44 | end 45 | 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/rack/bug/panels/cache_panel/stats.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | class Bug 3 | class CachePanel 4 | 5 | class Stats 6 | class Query 7 | attr_reader :method, :time, :hit, :keys 8 | 9 | def initialize(method, time, hit, keys) 10 | @method = method 11 | @time = time 12 | @hit = hit 13 | @keys = keys 14 | end 15 | 16 | def display_time 17 | "%.2fms" % time 18 | end 19 | 20 | def display_keys 21 | if keys.size == 1 22 | keys.first 23 | else 24 | keys.join(", ") 25 | end 26 | end 27 | end 28 | 29 | attr_reader :calls 30 | attr_reader :keys 31 | attr_reader :queries 32 | 33 | def initialize 34 | @queries = [] 35 | @misses = 36 | @calls = 0 37 | @time = 0.0 38 | @keys = [] 39 | end 40 | 41 | def record_call(method, time, hit, *keys) 42 | @queries << Query.new(method, time, hit, keys) 43 | @calls += 1 44 | @time += time 45 | @keys += keys 46 | end 47 | 48 | def display_time 49 | "%.2fms" % time 50 | end 51 | 52 | def time 53 | @queries.inject(0) do |memo, query| 54 | memo + query.time 55 | end 56 | end 57 | 58 | def gets 59 | count_queries(:get) 60 | end 61 | 62 | def sets 63 | count_queries(:set) 64 | end 65 | 66 | def deletes 67 | count_queries(:delete) 68 | end 69 | 70 | def get_multis 71 | count_queries(:get_multi) 72 | end 73 | 74 | def hits 75 | @queries.select { |q| [:get, :get_multi].include?(q.method) && q.hit }.size 76 | end 77 | 78 | def misses 79 | @queries.select { |q| [:get, :get_multi].include?(q.method) && !q.hit }.size 80 | end 81 | 82 | def count_queries(method) 83 | @queries.select { |q| q.method == method }.size 84 | end 85 | 86 | def queries_to_param 87 | params = {} 88 | @queries.each_with_index do |query, index| 89 | params["keys_#{index}"] = query.keys.first 90 | end 91 | params 92 | end 93 | end 94 | 95 | end 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /lib/rack/bug/panels/log_panel.rb: -------------------------------------------------------------------------------- 1 | require "rack/bug/panels/log_panel/logger_extension" 2 | 3 | module Rack 4 | class Bug 5 | 6 | class LogPanel < Panel 7 | class LogEntry 8 | attr_reader :level, :time, :message 9 | LEVELS = ['DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'] 10 | 11 | def initialize(level, time, message) 12 | @level = LEVELS[level] 13 | @time = time 14 | @message = message 15 | end 16 | 17 | def cleaned_message 18 | @message.to_s.gsub(/\e\[[;\d]+m/, "") 19 | end 20 | end 21 | 22 | def self.record(message, log_level) 23 | return unless Rack::Bug.enabled? 24 | return unless message 25 | Thread.current["rack.bug.logs.start"] ||= Time.now 26 | timestamp = ((Time.now - Thread.current["rack.bug.logs.start"]) * 1000).to_i 27 | logs << LogEntry.new(log_level, timestamp, message) 28 | end 29 | 30 | def self.reset 31 | Thread.current["rack.bug.logs"] = [] 32 | Thread.current["rack.bug.logs.start"] = nil 33 | end 34 | 35 | def self.logs 36 | Thread.current["rack.bug.logs"] ||= [] 37 | end 38 | 39 | def name 40 | "log" 41 | end 42 | 43 | def heading 44 | "Log" 45 | end 46 | 47 | def content 48 | result = render_template "panels/log", :logs => self.class.logs 49 | self.class.reset 50 | return result 51 | end 52 | 53 | end 54 | 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/rack/bug/panels/log_panel/logger_extension.rb: -------------------------------------------------------------------------------- 1 | module Rack::Bug::LoggerExtension 2 | def self.included(target) 3 | target.send :alias_method, :add_without_rack_bug, :add 4 | target.send :alias_method, :add, :add_with_rack_bug 5 | end 6 | 7 | def add_with_rack_bug(*args, &block) 8 | logger_return = add_without_rack_bug(*args, &block) 9 | logged_message = logger_return 10 | logged_message = args[1] || args[2] unless logged_message.is_a?(String) 11 | Rack::Bug::LogPanel.record(logged_message, args[0]) 12 | return logger_return 13 | end 14 | end 15 | 16 | if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger 17 | logger = Rails.logger 18 | elsif defined?(LOGGER) 19 | logger = LOGGER 20 | end 21 | 22 | if logger 23 | logger.class.send :include, Rack::Bug::LoggerExtension 24 | end -------------------------------------------------------------------------------- /lib/rack/bug/panels/memory_panel.rb: -------------------------------------------------------------------------------- 1 | # 2 | module Rack 3 | class Bug 4 | 5 | class MemoryPanel < Panel 6 | 7 | def before(env) 8 | @original_memory = `ps -o rss= -p #{$$}`.to_i 9 | end 10 | 11 | def after(env, status, headers, body) 12 | @total_memory = `ps -o rss= -p #{$$}`.to_i 13 | @memory_increase = @total_memory - @original_memory 14 | end 15 | 16 | def heading 17 | "#{@memory_increase} KB Δ, #{@total_memory} KB total" 18 | end 19 | 20 | def has_content? 21 | false 22 | end 23 | 24 | end 25 | 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/rack/bug/panels/mongo_panel.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | class Bug 3 | 4 | class MongoPanel < Panel 5 | require "rack/bug/panels/mongo_panel/mongo_extension" 6 | 7 | autoload :Stats, "rack/bug/panels/mongo_panel/stats" 8 | 9 | def self.record(command, &block) 10 | return block.call unless Rack::Bug.enabled? 11 | 12 | start_time = Time.now 13 | result = block.call 14 | total_time = Time.now - start_time 15 | stats.record_call(total_time * 1_000, command) 16 | return result 17 | end 18 | 19 | def self.reset 20 | Thread.current["rack.bug.mongo"] = Stats.new 21 | end 22 | 23 | def self.stats 24 | Thread.current["rack.bug.mongo"] ||= Stats.new 25 | end 26 | 27 | def name 28 | "mongo" 29 | end 30 | 31 | def heading 32 | "Mongo: %.2fms (#{self.class.stats.queries.size} calls)" % self.class.stats.time 33 | end 34 | 35 | def content 36 | result = render_template "panels/mongo", :stats => self.class.stats 37 | self.class.reset 38 | return result 39 | end 40 | 41 | end 42 | 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/rack/bug/panels/mongo_panel/mongo_extension.rb: -------------------------------------------------------------------------------- 1 | require 'mongo' 2 | 3 | if defined?(Mongo) 4 | Mongo::Connection.class_eval do 5 | 6 | def send_message_with_rack_bug(operation, message, log_message=nil) 7 | Rack::Bug::MongoPanel.record(log_message || message) do 8 | send_message_without_rack_bug(operation, message, log_message) 9 | end 10 | end 11 | alias_method_chain :send_message, :rack_bug 12 | 13 | def send_message_with_safe_check_with_rack_bug(operation, message, db_name, log_message=nil, last_error_params=false) 14 | Rack::Bug::MongoPanel.record(log_message || message) do 15 | send_message_with_safe_check_without_rack_bug(operation, message, db_name, log_message, last_error_params) 16 | end 17 | end 18 | alias_method_chain :send_message_with_safe_check, :rack_bug 19 | 20 | def receive_message_with_rack_bug(operation, message, log_message=nil, socket=nil) 21 | Rack::Bug::MongoPanel.record(log_message || "A logger must be configured to catch receive message commands") do 22 | receive_message_without_rack_bug(operation, message, log_message, socket) 23 | end 24 | end 25 | alias_method_chain :receive_message, :rack_bug 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/rack/bug/panels/mongo_panel/stats.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | class Bug 3 | class MongoPanel 4 | 5 | class Stats 6 | class Query 7 | attr_reader :time 8 | attr_reader :command 9 | 10 | def initialize(time, command) 11 | @time = time 12 | @command = command 13 | end 14 | 15 | def display_time 16 | "%.2fms" % time 17 | end 18 | end 19 | 20 | attr_reader :calls 21 | attr_reader :queries 22 | 23 | def initialize 24 | @queries = [] 25 | @calls = 0 26 | @time = 0.0 27 | end 28 | 29 | def record_call(time, command) 30 | @queries << Query.new(time, command) 31 | @calls += 1 32 | @time += time 33 | end 34 | 35 | def display_time 36 | "%.2fms" % time 37 | end 38 | 39 | def time 40 | @queries.inject(0) do |memo, query| 41 | memo + query.time 42 | end 43 | end 44 | end 45 | 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/rack/bug/panels/rails_info_panel.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | class Bug 3 | 4 | class RailsInfoPanel < Panel 5 | 6 | def name 7 | "rails_info" 8 | end 9 | 10 | def heading 11 | return unless (defined?(Rails) && defined?(Rails::Info)) 12 | "Rails #{Rails.version}" 13 | end 14 | 15 | def content 16 | return unless (defined?(Rails) && defined?(Rails::Info)) 17 | render_template "panels/rails_info" 18 | end 19 | 20 | end 21 | 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/rack/bug/panels/redis_panel.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | class Bug 3 | 4 | class RedisPanel < Panel 5 | require "rack/bug/panels/redis_panel/redis_extension" 6 | 7 | autoload :Stats, "rack/bug/panels/redis_panel/stats" 8 | 9 | def self.record(redis_command_args, backtrace, &block) 10 | return block.call unless Rack::Bug.enabled? 11 | 12 | start_time = Time.now 13 | result = block.call 14 | total_time = Time.now - start_time 15 | stats.record_call(total_time * 1_000, redis_command_args, backtrace) 16 | return result 17 | end 18 | 19 | def self.reset 20 | Thread.current["rack.bug.redis"] = Stats.new 21 | end 22 | 23 | def self.stats 24 | Thread.current["rack.bug.redis"] ||= Stats.new 25 | end 26 | 27 | def name 28 | "redis" 29 | end 30 | 31 | def heading 32 | "Redis: %.2fms (#{self.class.stats.queries.size} calls)" % self.class.stats.time 33 | end 34 | 35 | def content 36 | result = render_template "panels/redis", :stats => self.class.stats 37 | self.class.reset 38 | return result 39 | end 40 | 41 | end 42 | 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/rack/bug/panels/redis_panel/redis_extension.rb: -------------------------------------------------------------------------------- 1 | require 'redis' 2 | 3 | if defined?(Redis) 4 | Redis.class_eval do 5 | if Redis.methods.include?('call_command') # older versions of redis-rb 6 | def call_command_with_rack_bug(*argv) 7 | Rack::Bug::RedisPanel.record(argv, Kernel.caller) do 8 | call_command_without_rack_bug(*argv) 9 | end 10 | end 11 | 12 | alias_method_chain :call_command, :rack_bug 13 | 14 | elsif defined?(Redis::Client) # newer versions of redis-rb 15 | 16 | Redis::Client.class_eval do 17 | def call_with_rack_bug(*argv) 18 | Rack::Bug::RedisPanel.record(argv, Kernel.caller) do 19 | call_without_rack_bug(*argv) 20 | end 21 | end 22 | 23 | alias_method_chain :call, :rack_bug 24 | 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/rack/bug/panels/redis_panel/stats.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | class Bug 3 | class RedisPanel 4 | 5 | class Stats 6 | class Query 7 | include Rack::Bug::FilteredBacktrace 8 | 9 | attr_reader :time 10 | attr_reader :command 11 | 12 | def initialize(time, command_args, backtrace) 13 | @time = time 14 | @command = command_args.inspect 15 | @backtrace = backtrace 16 | end 17 | 18 | def display_time 19 | "%.2fms" % time 20 | end 21 | end 22 | 23 | attr_reader :calls 24 | attr_reader :queries 25 | 26 | def initialize 27 | @queries = [] 28 | @calls = 0 29 | @time = 0.0 30 | end 31 | 32 | def record_call(time, command_args, backtrace) 33 | @queries << Query.new(time, command_args, backtrace) 34 | @calls += 1 35 | @time += time 36 | end 37 | 38 | def display_time 39 | "%.2fms" % time 40 | end 41 | 42 | def time 43 | @queries.inject(0) do |memo, query| 44 | memo + query.time 45 | end 46 | end 47 | 48 | end 49 | 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/rack/bug/panels/request_variables_panel.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | class Bug 3 | 4 | class RequestVariablesPanel < Panel 5 | 6 | def name 7 | "request_variables" 8 | end 9 | 10 | def before(env) 11 | @env = env 12 | end 13 | 14 | def heading 15 | "Rack Env" 16 | end 17 | 18 | def content 19 | sections = {} 20 | sections["GET"] = sort(@request.GET) if @request.GET.any? 21 | sections["POST"] = sort(@request.GET) if @request.POST.any? 22 | sections["Session"] = sort(@request.env["rack.session"]) if @request.env["rack.session"] && @request.env["rack.session"].any? 23 | sections["Cookies"] = sort(@request.env["rack.request.cookie_hash"]) if @request.env["rack.request.cookie_hash"] && @request.env["rack.request.cookie_hash"].any? 24 | server, rack = split_and_filter_env(@env) 25 | sections["SERVER VARIABLES"] = sort(server) 26 | sections["Rack ENV"] = sort(rack) 27 | render_template "panels/request_variables", :sections => sections 28 | end 29 | 30 | private 31 | def sort(hash) 32 | hash.sort_by { |k, v| k.to_s } 33 | end 34 | 35 | def split_and_filter_env(env) 36 | server, rack = {}, {} 37 | env.each do |k,v| 38 | if k.index("rack.") == 0 39 | rack[k] = v 40 | elsif k.index("rack-bug.") == 0 41 | #don't output the rack-bug variables - especially secret_key 42 | else 43 | server[k] = v 44 | end 45 | end 46 | return server, rack 47 | end 48 | 49 | end 50 | 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/rack/bug/panels/sphinx_panel.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | class Bug 3 | 4 | class SphinxPanel < Panel 5 | require "rack/bug/panels/sphinx_panel/sphinx_extension" 6 | 7 | autoload :Stats, "rack/bug/panels/sphinx_panel/stats" 8 | 9 | def self.record(*sphinx_command_args, &block) 10 | return block.call unless Rack::Bug.enabled? 11 | 12 | start_time = Time.now 13 | result = block.call 14 | total_time = Time.now - start_time 15 | stats.record_call(total_time * 1_000, sphinx_command_args) 16 | return result 17 | end 18 | 19 | def self.reset 20 | Thread.current["rack.bug.sphinx"] = Stats.new 21 | end 22 | 23 | def self.stats 24 | Thread.current["rack.bug.sphinx"] ||= Stats.new 25 | end 26 | 27 | def name 28 | "sphinx" 29 | end 30 | 31 | def heading 32 | "Sphinx: %.2fms (#{self.class.stats.queries.size} calls)" % self.class.stats.time 33 | end 34 | 35 | def content 36 | result = render_template "panels/sphinx", :stats => self.class.stats 37 | self.class.reset 38 | return result 39 | end 40 | 41 | end 42 | 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/rack/bug/panels/sphinx_panel/sphinx_extension.rb: -------------------------------------------------------------------------------- 1 | require 'riddle' 2 | 3 | if defined?(Riddle) 4 | Riddle::Client.class_eval do 5 | def request_with_rack_bug(command, messages) 6 | Rack::Bug::SphinxPanel.record(command, messages) do 7 | request_without_rack_bug(command, messages) 8 | end 9 | end 10 | 11 | alias_method_chain :request, :rack_bug 12 | end 13 | end 14 | 15 | if defined?(Sphinx::Client) 16 | Sphinx::Client.class_eval do 17 | def PerformRequest_with_rack_bug(command, request, additional = nil) 18 | Rack::Bug::SphinxPanel.record(command, request) do 19 | PerformRequest_without_rack_bug(command, request, additional) 20 | end 21 | end 22 | 23 | alias_method_chain :PerformRequest, :rack_bug 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/rack/bug/panels/sphinx_panel/stats.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | class Bug 3 | class SphinxPanel 4 | 5 | class Stats 6 | class Query 7 | attr_reader :time 8 | attr_reader :command 9 | 10 | def initialize(time, *command_args) 11 | @time = time 12 | if command_args.flatten.first == :search 13 | @command = "search: " + decode_message(command_args.first.flatten.last).collect{|k,v| "#{k} => #{v}"}.join(", ") 14 | else 15 | @command = command_args.flatten.first.to_s + ": No more info is available for this Sphinx request type" 16 | end 17 | end 18 | 19 | def display_time 20 | "%.2fms" % time 21 | end 22 | 23 | def decode_message(m) 24 | @m = m.clone 25 | params = ActiveSupport::OrderedHash.new 26 | 27 | params[:offset] = consume_int 28 | params[:limit] = consume_int 29 | params[:match_mode] = consume_int 30 | params[:rank_mode] = consume_int 31 | params[:sort_mode] = consume_int 32 | params[:sort_by] = consume_string 33 | params[:query] = consume_string 34 | wl = consume_int 35 | weights = [] 36 | wl.times do weights << consume_int end 37 | params[:weights] = weights 38 | 39 | params[:index] = consume_string 40 | 41 | consume_string 42 | 43 | params[:id_range] = [consume_64int, consume_64int] 44 | params 45 | end 46 | 47 | def consume_int 48 | i = @m.unpack("N").first 49 | @m = @m.slice(4, @m.length - 4) 50 | i 51 | end 52 | 53 | def consume_64int 54 | i = @m.unpack("NN").first 55 | @m = @m.slice(8, @m.length - 8) 56 | i 57 | end 58 | 59 | def consume_string 60 | len = consume_int 61 | s = @m.slice(0, len) 62 | @m = @m.slice(len, @m.length - len) 63 | s 64 | end 65 | end 66 | 67 | attr_reader :calls 68 | attr_reader :queries 69 | 70 | def initialize 71 | @queries = [] 72 | @calls = 0 73 | @time = 0.0 74 | end 75 | 76 | def record_call(time, *command_args) 77 | 78 | @queries << Query.new(time, command_args) 79 | @calls += 1 80 | @time += time 81 | end 82 | 83 | def display_time 84 | "%.2fms" % time 85 | end 86 | 87 | def time 88 | @queries.inject(0) do |memo, query| 89 | memo + query.time 90 | end 91 | end 92 | end 93 | 94 | end 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /lib/rack/bug/panels/sql_panel.rb: -------------------------------------------------------------------------------- 1 | require "digest" 2 | 3 | module Rack 4 | class Bug 5 | 6 | class SQLPanel < Panel 7 | require "rack/bug/panels/sql_panel/sql_extension" 8 | 9 | autoload :PanelApp, "rack/bug/panels/sql_panel/panel_app" 10 | autoload :Query, "rack/bug/panels/sql_panel/query" 11 | 12 | def panel_app 13 | PanelApp.new 14 | end 15 | 16 | def self.record(sql, backtrace = [], &block) 17 | return block.call unless Rack::Bug.enabled? 18 | 19 | start_time = Time.now 20 | result = block.call 21 | queries << Query.new(sql, Time.now - start_time, backtrace) 22 | 23 | return result 24 | end 25 | 26 | def self.reset 27 | Thread.current["rack.test.queries"] = [] 28 | end 29 | 30 | def self.queries 31 | Thread.current["rack.test.queries"] ||= [] 32 | end 33 | 34 | def self.total_time 35 | (queries.inject(0) { |memo, query| memo + query.time}) * 1_000 36 | end 37 | 38 | def name 39 | "sql" 40 | end 41 | 42 | def heading 43 | "#{self.class.queries.size} Queries (%.2fms)" % self.class.total_time 44 | end 45 | 46 | def content 47 | result = render_template "panels/sql", :queries => self.class.queries 48 | self.class.reset 49 | return result 50 | end 51 | 52 | end 53 | 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/rack/bug/panels/sql_panel/panel_app.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | class Bug 3 | class SQLPanel 4 | 5 | class PanelApp < ::Rack::Bug::PanelApp 6 | 7 | def dispatch 8 | case request.path_info 9 | when "/__rack_bug__/explain_sql" then explain_sql 10 | when "/__rack_bug__/profile_sql" then profile_sql 11 | when "/__rack_bug__/execute_sql" then execute_sql 12 | else not_found 13 | end 14 | end 15 | 16 | def explain_sql 17 | validate_params 18 | query = Query.new(params["query"], params["time"].to_f) 19 | render_template "panels/explain_sql", :result => query.explain, :query => query.sql, :time => query.time 20 | end 21 | 22 | def profile_sql 23 | validate_params 24 | query = Query.new(params["query"], params["time"].to_f) 25 | render_template "panels/profile_sql", :result => query.profile, :query => query.sql, :time => query.time 26 | end 27 | 28 | def execute_sql 29 | validate_params 30 | query = Query.new(params["query"], params["time"].to_f) 31 | render_template "panels/execute_sql", :result => query.execute, :query => query.sql, :time => query.time 32 | end 33 | 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/rack/bug/panels/sql_panel/query.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | class Bug 3 | class SQLPanel 4 | 5 | class Query 6 | include Rack::Bug::FilteredBacktrace 7 | 8 | attr_reader :sql 9 | attr_reader :time 10 | 11 | def initialize(sql, time, backtrace = []) 12 | @sql = sql 13 | @time = time 14 | @backtrace = backtrace 15 | end 16 | 17 | def human_time 18 | "%.2fms" % (@time * 1_000) 19 | end 20 | 21 | def inspectable? 22 | sql.strip =~ /^SELECT /i 23 | end 24 | 25 | def with_profiling 26 | self.class.execute("SET PROFILING=1") 27 | result = yield 28 | self.class.execute("SET PROFILING=0") 29 | return result 30 | end 31 | 32 | def explain 33 | self.class.execute "EXPLAIN #{@sql}" 34 | end 35 | 36 | def profile 37 | with_profiling do 38 | execute 39 | self.class.execute <<-SQL 40 | SELECT * 41 | FROM information_schema.profiling 42 | WHERE query_id = (SELECT query_id FROM information_schema.profiling ORDER BY query_id DESC LIMIT 1) 43 | SQL 44 | end 45 | end 46 | 47 | def execute 48 | self.class.execute(@sql) 49 | end 50 | 51 | def valid_hash?(secret_key, possible_hash) 52 | hash = Digest::SHA1.hexdigest [secret_key, @sql].join(":") 53 | possible_hash == hash 54 | end 55 | 56 | def self.execute(sql) 57 | ActiveRecord::Base.connection.execute(sql) 58 | end 59 | end 60 | 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /lib/rack/bug/panels/sql_panel/sql_extension.rb: -------------------------------------------------------------------------------- 1 | if defined?(ActiveRecord) && defined?(ActiveRecord::ConnectionAdapters) 2 | ActiveRecord::ConnectionAdapters::AbstractAdapter.class_eval do 3 | def log_with_rack_bug(sql, name, &block) 4 | Rack::Bug::SQLPanel.record(sql, Kernel.caller) do 5 | log_without_rack_bug(sql, name, &block) 6 | end 7 | end 8 | 9 | alias_method_chain :log, :rack_bug 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/rack/bug/panels/templates_panel.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | class Bug 3 | 4 | class TemplatesPanel < Panel 5 | require "rack/bug/panels/templates_panel/actionview_extension" 6 | 7 | autoload :Trace, "rack/bug/panels/templates_panel/trace" 8 | autoload :Rendering, "rack/bug/panels/templates_panel/rendering" 9 | 10 | def self.record(template) 11 | return yield unless Rack::Bug.enabled? 12 | 13 | template_trace.start(template) 14 | begin 15 | result = yield 16 | ensure 17 | template_trace.finished(template) 18 | end 19 | return result 20 | end 21 | 22 | def self.reset 23 | Thread.current["rack.bug.template_trace"] = Trace.new 24 | end 25 | 26 | def self.template_trace 27 | Thread.current["rack.bug.template_trace"] ||= Trace.new 28 | end 29 | 30 | def name 31 | "templates" 32 | end 33 | 34 | def heading 35 | "Templates: %.2fms" % (self.class.template_trace.total_time * 1_000) 36 | end 37 | 38 | def content 39 | result = render_template "panels/templates", :template_trace => self.class.template_trace 40 | self.class.reset 41 | return result 42 | end 43 | 44 | end 45 | 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/rack/bug/panels/templates_panel/actionview_extension.rb: -------------------------------------------------------------------------------- 1 | if defined?(ActionView) && defined?(ActionView::Template) 2 | ActionView::Template.class_eval do 3 | 4 | def render_template_with_rack_bug(*args, &block) 5 | Rack::Bug::TemplatesPanel.record(path_without_format_and_extension) do 6 | render_template_without_rack_bug(*args, &block) 7 | end 8 | end 9 | 10 | alias_method_chain :render_template, :rack_bug 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/rack/bug/panels/templates_panel/rendering.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | class Bug 3 | class TemplatesPanel 4 | 5 | class Rendering 6 | attr_accessor :name 7 | attr_accessor :start_time 8 | attr_accessor :end_time 9 | attr_accessor :parent 10 | attr_reader :children 11 | 12 | 13 | def initialize(name) 14 | @name = name 15 | @children = [] 16 | end 17 | 18 | def add(rendering) 19 | @children << rendering 20 | rendering.parent = self 21 | end 22 | 23 | def time 24 | @end_time - @start_time 25 | end 26 | 27 | def exclusive_time 28 | time - child_time 29 | end 30 | 31 | def child_time 32 | children.inject(0.0) { |memo, c| memo + c.time } 33 | end 34 | 35 | def time_summary 36 | if children.any? 37 | "%.2fms, %.2f exclusive" % [time * 1_000, exclusive_time * 1_000] 38 | else 39 | "%.2fms" % (time * 1_000) 40 | end 41 | end 42 | def html 43 | <<-HTML 44 |
  • 45 |

    #{name} (#{time_summary})

    46 | 47 | #{children_html} 48 |
  • 49 | HTML 50 | end 51 | 52 | def children_html 53 | return "" unless children.any? 54 | 55 | <<-HTML 56 | 57 | HTML 58 | end 59 | 60 | def joined_children_html 61 | children.map { |c| c.html }.join 62 | end 63 | end 64 | 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /lib/rack/bug/panels/templates_panel/trace.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | class Bug 3 | class TemplatesPanel 4 | 5 | class Trace 6 | 7 | def start(template_name) 8 | rendering = Rendering.new(template_name) 9 | rendering.start_time = Time.now 10 | @current.add(rendering) 11 | @current = rendering 12 | end 13 | 14 | def finished(template_name) 15 | @current.end_time = Time.now 16 | @current = @current.parent 17 | end 18 | 19 | def initialize 20 | @current = root 21 | end 22 | 23 | def total_time 24 | root.child_time 25 | end 26 | 27 | def root 28 | @root ||= Rendering.new("root") 29 | end 30 | end 31 | 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/rack/bug/panels/timer_panel.rb: -------------------------------------------------------------------------------- 1 | require "benchmark" 2 | 3 | module Rack 4 | class Bug 5 | 6 | class TimerPanel < Panel 7 | 8 | def name 9 | "timer" 10 | end 11 | 12 | def call(env) 13 | status, headers, body = nil 14 | @times = Benchmark.measure do 15 | status, headers, body = @app.call(env) 16 | end 17 | 18 | @measurements = [ 19 | ["User CPU time", "%.2fms" % (@times.utime * 1_000)], 20 | ["System CPU time", "%.2fms" % (@times.stime * 1_000)], 21 | ["Total CPU time", "%.2fms" % (@times.total * 1_000)], 22 | ["Elapsed time", "%.2fms" % (@times.real * 1_000)] 23 | ] 24 | 25 | env["rack-bug.panels"] << self 26 | return [status, headers, body] 27 | end 28 | 29 | def heading 30 | "%.2fms" % (@times.real * 1_000) 31 | end 32 | 33 | def content 34 | render_template "panels/timer", :measurements => @measurements 35 | end 36 | 37 | end 38 | 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/rack/bug/params_signature.rb: -------------------------------------------------------------------------------- 1 | require "digest" 2 | 3 | module Rack 4 | class Bug 5 | 6 | class ParamsSignature 7 | extend ERB::Util 8 | 9 | def self.sign(request, hash) 10 | parts = [] 11 | 12 | hash.keys.sort.each do |key| 13 | parts << "#{key}=#{u(hash[key])}" 14 | end 15 | 16 | signature = new(request).signature(hash) 17 | parts << "hash=#{u(signature)}" 18 | 19 | parts.join("&") 20 | end 21 | 22 | attr_reader :request 23 | 24 | def initialize(request) 25 | @request = request 26 | end 27 | 28 | def secret_key 29 | @request.env['rack-bug.secret_key'] 30 | end 31 | 32 | def secret_key_blank? 33 | secret_key.nil? || secret_key == "" 34 | end 35 | 36 | def validate! 37 | if secret_key_blank? 38 | raise SecurityError.new("Missing secret key") 39 | elsif request.params["hash"] != signature(request.params) 40 | raise SecurityError.new("Invalid query hash.") 41 | end 42 | end 43 | 44 | def signature(params) 45 | Digest::SHA1.hexdigest(signature_base(params)) 46 | end 47 | 48 | def signature_base(params) 49 | signature = [] 50 | signature << secret_key 51 | 52 | params.keys.sort.each do |key| 53 | next if key == "hash" 54 | signature << params[key].to_s 55 | end 56 | 57 | signature.join(":") 58 | end 59 | 60 | end 61 | 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /lib/rack/bug/public/__rack_bug__/bookmarklet.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |


    6 | 7 | Toggle Rack::Bug 8 | 9 | 10 | -------------------------------------------------------------------------------- /lib/rack/bug/public/__rack_bug__/bookmarklet.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Secure Hash Algorithm (SHA1) 4 | * http://www.webtoolkit.info/ 5 | * 6 | **/ 7 | 8 | document.SHA1 = function(msg) { 9 | function rotate_left(n,s) { 10 | var t4 = ( n<>>(32-s)); 11 | return t4; 12 | }; 13 | 14 | function lsb_hex(val) { 15 | var str=""; 16 | var i; 17 | var vh; 18 | var vl; 19 | 20 | for( i=0; i<=6; i+=2 ) { 21 | vh = (val>>>(i*4+4))&0x0f; 22 | vl = (val>>>(i*4))&0x0f; 23 | str += vh.toString(16) + vl.toString(16); 24 | } 25 | return str; 26 | }; 27 | 28 | function cvt_hex(val) { 29 | var str=""; 30 | var i; 31 | var v; 32 | 33 | for( i=7; i>=0; i-- ) { 34 | v = (val>>>(i*4))&0x0f; 35 | str += v.toString(16); 36 | } 37 | return str; 38 | }; 39 | 40 | 41 | function Utf8Encode(string) { 42 | string = string.replace(/\r\n/g,"\n"); 43 | var utftext = ""; 44 | 45 | for (var n = 0; n < string.length; n++) { 46 | 47 | var c = string.charCodeAt(n); 48 | 49 | if (c < 128) { 50 | utftext += String.fromCharCode(c); 51 | } 52 | else if((c > 127) && (c < 2048)) { 53 | utftext += String.fromCharCode((c >> 6) | 192); 54 | utftext += String.fromCharCode((c & 63) | 128); 55 | } 56 | else { 57 | utftext += String.fromCharCode((c >> 12) | 224); 58 | utftext += String.fromCharCode(((c >> 6) & 63) | 128); 59 | utftext += String.fromCharCode((c & 63) | 128); 60 | } 61 | 62 | } 63 | 64 | return utftext; 65 | }; 66 | 67 | var blockstart; 68 | var i, j; 69 | var W = new Array(80); 70 | var H0 = 0x67452301; 71 | var H1 = 0xEFCDAB89; 72 | var H2 = 0x98BADCFE; 73 | var H3 = 0x10325476; 74 | var H4 = 0xC3D2E1F0; 75 | var A, B, C, D, E; 76 | var temp; 77 | 78 | msg = Utf8Encode(msg); 79 | 80 | var msg_len = msg.length; 81 | 82 | var word_array = new Array(); 83 | for( i=0; i>>29 ); 111 | word_array.push( (msg_len<<3)&0x0ffffffff ); 112 | 113 | 114 | for ( blockstart=0; blockstart div { 217 | max-height:200px; 218 | max-width: 800px; 219 | overflow:auto; 220 | } -------------------------------------------------------------------------------- /lib/rack/bug/public/__rack_bug__/bug.js: -------------------------------------------------------------------------------- 1 | var _$ = window.$; 2 | jQuery.noConflict(); 3 | jQuery(function($) { 4 | $.rackBug = function(data, klass) { 5 | $.rackBug.init(); 6 | }; 7 | $.extend($.rackBug, { 8 | init: function() { 9 | var current = null; 10 | $('#rack_bug ul.panels li a').click(function() { 11 | current = $('#rack_bug #' + this.className); 12 | 13 | if (current.is(':visible')) { 14 | $(document).trigger('close.rackBug'); 15 | } else { 16 | $('#rack_bug .panel_content').hide(); 17 | current.show(); 18 | $.rackBug.open(); 19 | } 20 | return false; 21 | }); 22 | $('#rack_bug a.remote_call').click(function() { 23 | $('#rack_bug_debug_window').load(this.href, null, function() { 24 | $('#rack_bug_debug_window a.back').click(function() { 25 | $(this).parent().hide(); 26 | return false; 27 | }); 28 | }); 29 | $('#rack_bug_debug_window').show(); 30 | return false; 31 | }); 32 | $('#rack_bug a.reveal_backtrace').click(function() { 33 | $(this).parents('tr').find('ul.backtrace').toggle(); 34 | return false; 35 | }); 36 | $('#rack_bug a.rack_bug_close').click(function() { 37 | $(document).trigger('close.rackBug'); 38 | return false; 39 | }); 40 | $('#rb_debug_button').click(function(){ 41 | $('#rack_bug').toggleClass('rb_top').toggleClass('rb_bottom'); 42 | return false; 43 | }); 44 | $.tablesorter.addParser({ 45 | id: 'ms', 46 | is: function(s) { 47 | return /ms$/.test(s); 48 | }, 49 | format: function(s) { 50 | return $.tablesorter.formatFloat(s.replace(new RegExp(/[^0-9.]/g),"")); 51 | }, 52 | type: "numeric" 53 | }); 54 | }, 55 | open: function() { 56 | $(document).bind('keydown.rackBug', function(e) { 57 | if (e.keyCode == 27) { 58 | $.rackBug.close(); 59 | } 60 | }); 61 | $('table.sortable').tablesorter(); 62 | }, 63 | toggle_content: function(elem) { 64 | if (elem.is(':visible')) { 65 | elem.hide(); 66 | } else { 67 | elem.show(); 68 | } 69 | }, 70 | close: function() { 71 | $(document).trigger('close.rackBug'); 72 | return false; 73 | } 74 | }); 75 | $(document).bind('close.rackBug', function() { 76 | $(document).unbind('keydown.rackBug'); 77 | $('.panel_content').hide(); 78 | }); 79 | }); 80 | 81 | jQuery(function() { 82 | jQuery.rackBug(); 83 | }); 84 | $ = _$; 85 | -------------------------------------------------------------------------------- /lib/rack/bug/public/__rack_bug__/jquery.tablesorter.min.js: -------------------------------------------------------------------------------- 1 | (function($){$.extend({tablesorter:new function(){var parsers=[],widgets=[];this.defaults={cssHeader:"header",cssAsc:"headerSortUp",cssDesc:"headerSortDown",sortInitialOrder:"asc",sortMultiSortKey:"shiftKey",sortForce:null,sortAppend:null,textExtraction:"simple",parsers:{},widgets:[],widgetZebra:{css:["even","odd"]},headers:{},widthFixed:false,cancelSelection:true,sortList:[],headerList:[],dateFormat:"us",decimal:'.',debug:false};function benchmark(s,d){log(s+","+(new Date().getTime()-d.getTime())+"ms");}this.benchmark=benchmark;function log(s){if(typeof console!="undefined"&&typeof console.debug!="undefined"){console.log(s);}else{alert(s);}}function buildParserCache(table,$headers){if(table.config.debug){var parsersDebug="";}var rows=table.tBodies[0].rows;if(table.tBodies[0].rows[0]){var list=[],cells=rows[0].cells,l=cells.length;for(var i=0;i1){arr=arr.concat(checkCellColSpan(table,headerArr,row++));}else{if(table.tHead.length==1||(cell.rowSpan>1||!r[row+1])){arr.push(cell);}}}return arr;};function checkHeaderMetadata(cell){if(($.metadata)&&($(cell).metadata().sorter===false)){return true;};return false;}function checkHeaderOptions(table,i){if((table.config.headers[i])&&(table.config.headers[i].sorter===false)){return true;};return false;}function applyWidget(table){var c=table.config.widgets;var l=c.length;for(var i=0;i');$("tr:first td",table.tBodies[0]).each(function(){colgroup.append($('').css('width',$(this).width()));});$(table).prepend(colgroup);};}function updateHeaderSortCount(table,sortList){var c=table.config,l=sortList.length;for(var i=0;ib)?1:0));};function sortTextDesc(a,b){return((ba)?1:0));};function sortNumeric(a,b){return a-b;};function sortNumericDesc(a,b){return b-a;};function getCachedSortType(parsers,i){return parsers[i].type;};this.construct=function(settings){return this.each(function(){if(!this.tHead||!this.tBodies)return;var $this,$document,$headers,cache,config,shiftDown=0,sortOrder;this.config={};config=$.extend(this.config,$.tablesorter.defaults,settings);$this=$(this);$headers=buildHeaders(this);this.config.parsers=buildParserCache(this,$headers);cache=buildCache(this);var sortCSS=[config.cssDesc,config.cssAsc];fixColumnWidth(this);$headers.click(function(e){$this.trigger("sortStart");var totalRows=($this[0].tBodies[0]&&$this[0].tBodies[0].rows.length)||0;if(!this.sortDisabled&&totalRows>0){var $cell=$(this);var i=this.column;this.order=this.count++%2;if(!e[config.sortMultiSortKey]){config.sortList=[];if(config.sortForce!=null){var a=config.sortForce;for(var j=0;j0){$this.trigger("sorton",[config.sortList]);}applyWidget(this);});};this.addParser=function(parser){var l=parsers.length,a=true;for(var i=0;i @response.location) 21 | new_headers = { "Content-Type" => "text/html", "Content-Length" => new_body.size.to_s } 22 | @response = Rack::Response.new(new_body, 200, new_headers) 23 | end 24 | 25 | end 26 | end 27 | end -------------------------------------------------------------------------------- /lib/rack/bug/render.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require "erb" 3 | 4 | module Rack 5 | class Bug 6 | 7 | module Render 8 | include ERB::Util 9 | 10 | def signed_params(hash) 11 | ParamsSignature.sign(request, hash) 12 | end 13 | 14 | module CompiledTemplates 15 | end 16 | include CompiledTemplates 17 | 18 | def render_template(filename, local_assigns = {}) 19 | compile(filename, local_assigns) 20 | render_symbol = method_name(filename, local_assigns) 21 | send(render_symbol, local_assigns) 22 | end 23 | 24 | def compile(filename, local_assigns) 25 | render_symbol = method_name(filename, local_assigns) 26 | 27 | if !CompiledTemplates.instance_methods.include?(render_symbol.to_s) 28 | compile!(filename, local_assigns) 29 | end 30 | end 31 | 32 | def compile!(filename, local_assigns) 33 | render_symbol = method_name(filename, local_assigns) 34 | locals_code = local_assigns.keys.map { |key| "#{key} = local_assigns[:#{key}];" }.join 35 | 36 | source = <<-end_src 37 | def #{render_symbol}(local_assigns) 38 | #{locals_code} 39 | #{compiled_source(filename)} 40 | end 41 | end_src 42 | 43 | CompiledTemplates.module_eval(source, filename, 0) 44 | end 45 | 46 | def compiled_source(filename) 47 | ::ERB.new(::File.read(::File.dirname(__FILE__) + "/../bug/views/#{filename}.html.erb"), nil, "-").src 48 | end 49 | 50 | def method_name(filename, local_assigns) 51 | if local_assigns && local_assigns.any? 52 | method_name = method_name_without_locals(filename).dup 53 | method_name << "_locals_#{local_assigns.keys.map { |k| k.to_s }.sort.join('_')}" 54 | else 55 | method_name = method_name_without_locals(filename) 56 | end 57 | method_name.to_sym 58 | end 59 | 60 | def method_name_without_locals(filename) 61 | filename.split("/").join("_") 62 | end 63 | 64 | end 65 | 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /lib/rack/bug/toolbar.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | class Bug 3 | class Toolbar 4 | include Render 5 | 6 | MIME_TYPES = ["text/html", "application/xhtml+xml"] 7 | 8 | def initialize(app) 9 | @app = app 10 | end 11 | 12 | def call(env) 13 | @env = env 14 | @env["rack-bug.panels"] = [] 15 | 16 | Rack::Bug.enable 17 | status, headers, body = builder.call(@env) 18 | Rack::Bug.disable 19 | 20 | @response = Rack::Response.new(body, status, headers) 21 | 22 | inject_toolbar if response_type_okay_to_modify? 23 | 24 | return @response.to_a 25 | end 26 | 27 | def response_type_okay_to_modify? 28 | content_type, charset = @response.content_type.split(";") 29 | @response.ok? && MIME_TYPES.include?(content_type) 30 | end 31 | 32 | def builder 33 | builder = Rack::Builder.new 34 | 35 | @env["rack-bug.panel_classes"].each do |panel_class| 36 | builder.use panel_class 37 | end 38 | 39 | builder.run @app 40 | 41 | return builder 42 | end 43 | 44 | def inject_toolbar 45 | full_body = @response.body.join 46 | full_body.sub! /<\/body>/, render + "" 47 | 48 | @response["Content-Length"] = full_body.size.to_s 49 | 50 | # Ensure that browser does 51 | @response["Etag"] = "" 52 | @response["Cache-Control"] = "no-cache" 53 | 54 | @response.body = [full_body] 55 | end 56 | 57 | def render 58 | render_template("toolbar", :panels => @env["rack-bug.panels"].reverse) 59 | end 60 | 61 | end 62 | 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/rack/bug/views/error.html.erb: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | 12 |
    13 |
    14 |

    There was an error within Rack::Bug!

    15 |
    16 |
    17 | -------------------------------------------------------------------------------- /lib/rack/bug/views/panels/active_record.html.erb: -------------------------------------------------------------------------------- 1 |

    ActiveRecord Objects

    2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | <% records.each do |class_name, count| %> 11 | 12 | 13 | 14 | 15 | <% end %> 16 | 17 |
    CountClass
    <%= count %><%= class_name %>
    -------------------------------------------------------------------------------- /lib/rack/bug/views/panels/cache.html.erb: -------------------------------------------------------------------------------- 1 |

    Cache Usage

    2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
    Total Calls<%= stats.calls %>Total Time<%= stats.display_time %>Hits<%= stats.hits %>Misses<%= stats.misses %>
    gets<%= stats.gets %>sets<%= stats.sets %>deletes<%= stats.deletes %>get_multis<%= stats.get_multis %>
    40 | 41 | <% if stats.queries.any? %> 42 |

    Breakdown

    43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 55 | 56 | 57 | 58 | <% i = 1 %> 59 | <% stats.queries.each do |query| %> 60 | 61 | 62 | 63 | 64 | 65 | 69 | 70 | <% i += 1 %> 71 | <% end %> 72 | 73 |
    Time (ms)TypeParametersFunction 51 | 52 | Delete All 53 | 54 |
    <%= query.display_time %><%= query.method %><%= query.display_keys %> 66 | query.keys.first) %>" class="remote_call">View | 67 | query.keys.first) %>" class="rb_delete_cache">Delete 68 |
    74 | <% end %> 75 | 76 | -------------------------------------------------------------------------------- /lib/rack/bug/views/panels/execute_sql.html.erb: -------------------------------------------------------------------------------- 1 | « Back 2 | 3 |

    SQL Results

    4 | 5 |
    6 |
    Executed SQL
    7 |
    <%=h query %>
    8 | 9 |
    Time
    10 |
    <%=h "%.2f" % (time * 1_000) %>ms
    11 |
    12 | 13 | 14 | 15 | 16 | <% if defined?(Mysql2) || defined?(PGresult) %> 17 | <% result.fields.each do |field| %> 18 | 19 | <% end %> 20 | <% else %> 21 | <% result.fetch_fields.each do |field| %> 22 | 23 | <% end %> 24 | <% end %> 25 | 26 | 27 | 28 | <% i = 1 %> 29 | <% result.each do |row| %> 30 | 31 | <% row.each do |value| %> 32 | 33 | <% end %> 34 | 35 | <% i += 1 %> 36 | <% end %> 37 | 38 |
    <%= field.upcase %><%= field.name.upcase %>
    <%= value %>
    39 | -------------------------------------------------------------------------------- /lib/rack/bug/views/panels/explain_sql.html.erb: -------------------------------------------------------------------------------- 1 | « Back 2 | 3 |

    SQL Explained

    4 | 5 |
    6 |
    Executed SQL
    7 |
    <%=h query %>
    8 | 9 |
    Time
    10 |
    <%=h "%.2f" % (time * 1_000) %>ms
    11 |
    12 | 13 | 14 | 15 | 16 | <% if defined?(Mysql2) || defined?(PGresult) %> 17 | <% result.fields.each do |field| %> 18 | 19 | <% end %> 20 | <% else %> 21 | <% result.fetch_fields.each do |field| %> 22 | 23 | <% end %> 24 | <% end %> 25 | 26 | 27 | 28 | <% i = 1 %> 29 | <% result.each do |row| %> 30 | 31 | <% row.each do |value| %> 32 | 33 | <% end %> 34 | 35 | <% i += 1 %> 36 | <% end %> 37 | 38 |
    <%= field.upcase %><%= field.name.upcase %>
    <%= value %>
    39 | -------------------------------------------------------------------------------- /lib/rack/bug/views/panels/log.html.erb: -------------------------------------------------------------------------------- 1 |

    Log Messages

    2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | <% i = 1 %> 12 | <% logs.each do |entry| %> 13 | 14 | 15 | 16 | 17 | 18 | <% i += 1 %> 19 | <% end %> 20 | 21 |
    LevelTimeMessage
    <%= entry.level %><%= entry.time %>ms<%= entry.cleaned_message %>
    -------------------------------------------------------------------------------- /lib/rack/bug/views/panels/mongo.html.erb: -------------------------------------------------------------------------------- 1 |

    Mongo Usage

    2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
    Total Calls<%= stats.calls %>Total Time<%= stats.display_time %>
    11 | 12 | <% if stats.queries.any? %> 13 |

    Breakdown

    14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | <% i = 1 %> 23 | <% stats.queries.each do |query| %> 24 | 25 | 26 | 27 | 28 | <% i += 1 %> 29 | <% end %> 30 | 31 |
    Time (ms)Command
    <%= query.display_time %><%= query.command %>
    32 | <% end %> 33 | -------------------------------------------------------------------------------- /lib/rack/bug/views/panels/profile_sql.html.erb: -------------------------------------------------------------------------------- 1 | « Back 2 | 3 |

    SQL Profiled

    4 | 5 |
    6 |
    Executed SQL
    7 |
    <%=h query %>
    8 | 9 |
    Time
    10 |
    <%=h "%.2f" % (time * 1_000) %>ms
    11 |
    12 | 13 | 14 | 15 | 16 | <% if defined?(Mysql2) || defined?(PGresult) %> 17 | <% result.fields.each do |field| %> 18 | 19 | <% end %> 20 | <% else %> 21 | <% result.fetch_fields.each do |field| %> 22 | 23 | <% end %> 24 | <% end %> 25 | 26 | 27 | 28 | <% i = 1 %> 29 | <% result.each do |row| %> 30 | 31 | <% row.each do |value| %> 32 | 33 | <% end %> 34 | 35 | <% i += 1 %> 36 | <% end %> 37 | 38 |
    <%= field.upcase %><%= field.name.upcase %>
    <%= value %>
    39 | -------------------------------------------------------------------------------- /lib/rack/bug/views/panels/rails_info.html.erb: -------------------------------------------------------------------------------- 1 |

    Rails Environment

    2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | <% i = 1 %> 11 | <% Rails::Info.properties.each do |key, val| %> 12 | 13 | 14 | 15 | 16 | <% i += 1 %> 17 | <% end %> 18 | 19 |
    VariableValue
    <%=h key %>
    <%=h val %>
    -------------------------------------------------------------------------------- /lib/rack/bug/views/panels/redis.html.erb: -------------------------------------------------------------------------------- 1 |

    Redis Usage

    2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
    Total Calls<%= stats.calls %>Total Time<%= stats.display_time %>
    11 | 12 | <% if stats.queries.any? %> 13 |

    Breakdown

    14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | <% i = 1 %> 24 | <% stats.queries.each do |query| %> 25 | 26 | 27 | 28 | 29 | 30 | <% if query.has_backtrace? %> 31 | 32 | 33 | 40 | 41 | <% end %> 42 | <% i += 1 %> 43 | <% end %> 44 | 45 |
    Time (ms)Command
    <%= query.display_time %><%= query.command %><%= "Show Backtrace" if query.has_backtrace? %>
    34 |
      35 | <% query.filtered_backtrace.each do |line| %> 36 |
    • <%=h line %>
    • 37 | <% end %> 38 |
    39 |
    46 | <% end %> 47 | -------------------------------------------------------------------------------- /lib/rack/bug/views/panels/request_variables.html.erb: -------------------------------------------------------------------------------- 1 | <% ["GET", "POST", "Session", "Cookies", "SERVER VARIABLES", "Rack ENV"].each do |header| 2 | next unless sections.has_key?(header) 3 | %> 4 |

    <%=header%>

    5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | <% i = 1 %> 14 | <% sections[header].each do |key, val| %> 15 | 16 | 17 | 24 | 25 | <% i += 1 %> 26 | <% end %> 27 | 28 |
    VariableValue
    <%=h key %>
    18 | <% if val.is_a?(Hash) %> 19 | <%=h val.inspect %> 20 | <% else %> 21 | <%=h val.to_s %> 22 | <% end %> 23 |
    29 | <% end %> -------------------------------------------------------------------------------- /lib/rack/bug/views/panels/sphinx.html.erb: -------------------------------------------------------------------------------- 1 |

    Sphinx Usage

    2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
    Total Calls<%= stats.calls %>Total Time<%= stats.display_time %>
    11 | 12 | <% if stats.queries.any? %> 13 |

    Breakdown

    14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | <% i = 1 %> 23 | <% stats.queries.each do |query| %> 24 | 25 | 26 | 27 | 28 | <% i += 1 %> 29 | <% end %> 30 | 31 |
    Time (ms)Command
    <%= query.display_time %><%= query.command %>
    32 | <% end %> -------------------------------------------------------------------------------- /lib/rack/bug/views/panels/sql.html.erb: -------------------------------------------------------------------------------- 1 |

    SQL Queries

    2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | <% i = 1 %> 13 | <% queries.each do |query| %> 14 | 15 | 16 | 37 | 38 | <% i += 1 %> 39 | <% end %> 40 | 41 |
    Time (ms)Query
    <%= query.human_time %> 17 | <%= query.sql %> 18 |
    19 | <% if query.has_backtrace? %> 20 | Show Backtrace 21 | <% end %> 22 | <% if query.inspectable? && query.has_backtrace? %> 23 | | 24 | <% end %> 25 | <% if query.inspectable? %> 26 | query.sql, "time" => query.time) %>" class="remote_call">SELECT | 27 | query.sql, "time" => query.time) %>" class="remote_call">EXPLAIN | 28 | query.sql, "time" => query.time) %>" class="remote_call">Profile 29 | <% end %> 30 |
    31 | 36 |
    -------------------------------------------------------------------------------- /lib/rack/bug/views/panels/templates.html.erb: -------------------------------------------------------------------------------- 1 |

    Templates

    2 | 3 |
      4 | <% template_trace.root.children.each do |child| %> 5 | <%= child.html %> 6 | <% end %> 7 |
    8 | -------------------------------------------------------------------------------- /lib/rack/bug/views/panels/timer.html.erb: -------------------------------------------------------------------------------- 1 |

    Resource Usage

    2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | <% i = 1 %> 11 | <% measurements.each do |key, val| %> 12 | 13 | 14 | 15 | 16 | <% i += 1 %> 17 | <% end %> 18 | 19 |
    KeyValue
    <%=h key %><%=h val %>
    -------------------------------------------------------------------------------- /lib/rack/bug/views/panels/view_cache.html.erb: -------------------------------------------------------------------------------- 1 | « Back 2 | 3 |

    Cache Read

    4 | 5 |
    6 |
    Key
    7 |
    <%=h key %>
    8 | 9 |
    Time
    10 |
    <%=h "%.2f" % (0.0 * 1_000) %>ms
    11 |
    12 | 13 |

    14 | <% if value.is_a?(String )%> 15 | <%=h value %> 16 | <% else %> 17 | <%=h value.inspect %> 18 | <% end %> 19 |

    -------------------------------------------------------------------------------- /lib/rack/bug/views/redirect.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

    Redirect

    6 | 7 |

    Location: <%= redirect_to %>

    8 | 9 |

    10 | Rack::Bug has intercepted a redirect to the above URL for debug viewing 11 | purposes. You can click the above link to continue with the redirect as 12 | normal. If you'd like to disable this feature, set the Rack::Bug 13 | internal_redirects option to false. 14 |

    15 | 16 | 17 | -------------------------------------------------------------------------------- /lib/rack/bug/views/toolbar.html.erb: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 12 | 13 |
    14 |
    15 |
      16 |
    • Rack::Bug
    • 17 | 18 | <% panels.each do |panel| %> 19 |
    • 20 | <% if panel.has_content? %> 21 | 22 | <%= panel.heading %> 23 | 24 | <% else %> 25 | <%= panel.heading %> 26 | <% end %> 27 |
    • 28 | <% end %> 29 |
    30 |
    31 | 32 | <% panels.each do |panel| %> 33 | <% if panel.has_content? %> 34 |
    35 | Close 36 | <%= panel.content %> 37 |
    38 | <% end %> 39 | <% end %> 40 | 41 |
    42 |
    43 | -------------------------------------------------------------------------------- /rack-bug.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | Gem::Specification.new do |s| 4 | s.name = %q{rack-bug} 5 | s.version = "0.3.0" 6 | 7 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 8 | s.authors = ["Bryan Helmkamp"] 9 | s.date = %q{2010-09-03} 10 | s.email = %q{bryan@brynary.com} 11 | s.extra_rdoc_files = [ 12 | "README.md", 13 | "MIT-LICENSE.txt" 14 | ] 15 | s.files = [ 16 | ".gitignore", 17 | "History.txt", 18 | "MIT-LICENSE.txt", 19 | "README.md", 20 | "Rakefile", 21 | "Thorfile", 22 | "lib/rack/bug.rb", 23 | "lib/rack/bug/autoloading.rb", 24 | "lib/rack/bug/filtered_backtrace.rb", 25 | "lib/rack/bug/options.rb", 26 | "lib/rack/bug/panel.rb", 27 | "lib/rack/bug/panel_app.rb", 28 | "lib/rack/bug/panels/active_record_panel.rb", 29 | "lib/rack/bug/panels/active_record_panel/activerecord_extensions.rb", 30 | "lib/rack/bug/panels/cache_panel.rb", 31 | "lib/rack/bug/panels/cache_panel/memcache_extension.rb", 32 | "lib/rack/bug/panels/cache_panel/panel_app.rb", 33 | "lib/rack/bug/panels/cache_panel/stats.rb", 34 | "lib/rack/bug/panels/log_panel.rb", 35 | "lib/rack/bug/panels/log_panel/logger_extension.rb", 36 | "lib/rack/bug/panels/memory_panel.rb", 37 | "lib/rack/bug/panels/rails_info_panel.rb", 38 | "lib/rack/bug/panels/redis_panel.rb", 39 | "lib/rack/bug/panels/redis_panel/redis_extension.rb", 40 | "lib/rack/bug/panels/redis_panel/stats.rb", 41 | "lib/rack/bug/panels/request_variables_panel.rb", 42 | "lib/rack/bug/panels/sphinx_panel.rb", 43 | "lib/rack/bug/panels/sphinx_panel/sphinx_extension.rb", 44 | "lib/rack/bug/panels/sphinx_panel/stats.rb", 45 | "lib/rack/bug/panels/sql_panel.rb", 46 | "lib/rack/bug/panels/sql_panel/panel_app.rb", 47 | "lib/rack/bug/panels/sql_panel/query.rb", 48 | "lib/rack/bug/panels/sql_panel/sql_extension.rb", 49 | "lib/rack/bug/panels/templates_panel.rb", 50 | "lib/rack/bug/panels/templates_panel/actionview_extension.rb", 51 | "lib/rack/bug/panels/templates_panel/rendering.rb", 52 | "lib/rack/bug/panels/templates_panel/trace.rb", 53 | "lib/rack/bug/panels/timer_panel.rb", 54 | "lib/rack/bug/params_signature.rb", 55 | "lib/rack/bug/public/__rack_bug__/bookmarklet.html", 56 | "lib/rack/bug/public/__rack_bug__/bookmarklet.js", 57 | "lib/rack/bug/public/__rack_bug__/bug.css", 58 | "lib/rack/bug/public/__rack_bug__/bug.js", 59 | "lib/rack/bug/public/__rack_bug__/jquery-1.3.2.js", 60 | "lib/rack/bug/public/__rack_bug__/jquery.tablesorter.min.js", 61 | "lib/rack/bug/public/__rack_bug__/spinner.gif", 62 | "lib/rack/bug/rack_static_bug_avoider.rb", 63 | "lib/rack/bug/redirect_interceptor.rb", 64 | "lib/rack/bug/render.rb", 65 | "lib/rack/bug/toolbar.rb", 66 | "lib/rack/bug/views/error.html.erb", 67 | "lib/rack/bug/views/panels/active_record.html.erb", 68 | "lib/rack/bug/views/panels/cache.html.erb", 69 | "lib/rack/bug/views/panels/execute_sql.html.erb", 70 | "lib/rack/bug/views/panels/explain_sql.html.erb", 71 | "lib/rack/bug/views/panels/log.html.erb", 72 | "lib/rack/bug/views/panels/profile_sql.html.erb", 73 | "lib/rack/bug/views/panels/rails_info.html.erb", 74 | "lib/rack/bug/views/panels/redis.html.erb", 75 | "lib/rack/bug/views/panels/request_variables.html.erb", 76 | "lib/rack/bug/views/panels/sphinx.html.erb", 77 | "lib/rack/bug/views/panels/sql.html.erb", 78 | "lib/rack/bug/views/panels/templates.html.erb", 79 | "lib/rack/bug/views/panels/timer.html.erb", 80 | "lib/rack/bug/views/panels/view_cache.html.erb", 81 | "lib/rack/bug/views/redirect.html.erb", 82 | "lib/rack/bug/views/toolbar.html.erb", 83 | "rack-bug.gemspec", 84 | "spec/fixtures/config.ru", 85 | "spec/fixtures/dummy_panel.rb", 86 | "spec/fixtures/sample_app.rb", 87 | "spec/rack/bug/panels/active_record_panel_spec.rb", 88 | "spec/rack/bug/panels/cache_panel_spec.rb", 89 | "spec/rack/bug/panels/log_panel_spec.rb", 90 | "spec/rack/bug/panels/memory_panel_spec.rb", 91 | "spec/rack/bug/panels/rails_info_panel_spec.rb", 92 | "spec/rack/bug/panels/redis_panel_spec.rb", 93 | "spec/rack/bug/panels/sql_panel_spec.rb", 94 | "spec/rack/bug/panels/templates_panel_spec.rb", 95 | "spec/rack/bug/panels/timer_panel_spec.rb", 96 | "spec/rack/bug_spec.rb", 97 | "spec/rcov.opts", 98 | "spec/spec.opts", 99 | "spec/spec_helper.rb" 100 | ] 101 | s.homepage = %q{http://github.com/brynary/rack-bug} 102 | s.require_paths = ["lib"] 103 | s.rubyforge_project = %q{rack-bug} 104 | s.rubygems_version = %q{1.3.7} 105 | s.summary = %q{Debugging toolbar for Rack applications implemented as middleware} 106 | s.test_files = [ 107 | "spec/fixtures/dummy_panel.rb", 108 | "spec/fixtures/sample_app.rb", 109 | "spec/rack/bug/panels/active_record_panel_spec.rb", 110 | "spec/rack/bug/panels/cache_panel_spec.rb", 111 | "spec/rack/bug/panels/log_panel_spec.rb", 112 | "spec/rack/bug/panels/memory_panel_spec.rb", 113 | "spec/rack/bug/panels/rails_info_panel_spec.rb", 114 | "spec/rack/bug/panels/redis_panel_spec.rb", 115 | "spec/rack/bug/panels/sql_panel_spec.rb", 116 | "spec/rack/bug/panels/templates_panel_spec.rb", 117 | "spec/rack/bug/panels/timer_panel_spec.rb", 118 | "spec/rack/bug_spec.rb", 119 | "spec/spec_helper.rb" 120 | ] 121 | 122 | if s.respond_to? :specification_version then 123 | current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION 124 | s.specification_version = 3 125 | 126 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then 127 | s.add_runtime_dependency(%q, [">= 1.0"]) 128 | s.add_development_dependency(%q, [">= 0"]) 129 | s.add_development_dependency(%q, [">= 0"]) 130 | s.add_development_dependency(%q, [">= 0"]) 131 | s.add_development_dependency(%q, [">= 0"]) 132 | else 133 | s.add_dependency(%q, [">= 1.0"]) 134 | s.add_dependency(%q, [">= 0"]) 135 | s.add_dependency(%q, [">= 0"]) 136 | s.add_dependency(%q, [">= 0"]) 137 | s.add_dependency(%q, [">= 0"]) 138 | end 139 | else 140 | s.add_dependency(%q, [">= 1.0"]) 141 | s.add_dependency(%q, [">= 0"]) 142 | s.add_dependency(%q, [">= 0"]) 143 | s.add_dependency(%q, [">= 0"]) 144 | s.add_dependency(%q, [">= 0"]) 145 | end 146 | end 147 | -------------------------------------------------------------------------------- /spec/custom_matchers.rb: -------------------------------------------------------------------------------- 1 | module CustomMatchers 2 | def have_row(container, key, value = nil) 3 | simple_matcher("contain row") do |response| 4 | if value 5 | response.should have_selector("#{container} tr", :content => key) do |row| 6 | row.should contain(value) 7 | end 8 | else 9 | response.should have_selector("#{container} tr", :content => key) 10 | end 11 | end 12 | end 13 | 14 | def have_heading(text) 15 | simple_matcher("have heading") do |response| 16 | response.should have_selector("#rack_bug_toolbar li") do |heading| 17 | heading.should contain(text) 18 | end 19 | end 20 | end 21 | end -------------------------------------------------------------------------------- /spec/fixtures/config.ru: -------------------------------------------------------------------------------- 1 | require "rubygems" 2 | 3 | $LOAD_PATH.unshift File.dirname(__FILE__) 4 | require "sample_app" 5 | 6 | #Example usage, but moved inside sample app for easier testing 7 | #use Rack::Bug, :password => "secret" 8 | run SampleApp -------------------------------------------------------------------------------- /spec/fixtures/dummy_panel.rb: -------------------------------------------------------------------------------- 1 | class DummyPanel < Rack::Bug::Panel 2 | end 3 | -------------------------------------------------------------------------------- /spec/fixtures/sample_app.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.dirname(__FILE__) + '/../../lib' 2 | require "rack/bug" 3 | 4 | require "sinatra/base" 5 | require 'logger' 6 | 7 | RAILS_ENV = "development" unless defined?(RAILS_ENV) 8 | log_to = RAILS_ENV == "test" ? StringIO.new : STDOUT 9 | LOGGER = Logger.new(log_to) 10 | 11 | class SampleApp < Sinatra::Base 12 | use Rack::Bug#, :intercept_redirects => true, :password => 'secret' 13 | set :environment, 'test' 14 | 15 | configure :test do 16 | set :raise_errors, true 17 | end 18 | 19 | get "/redirect" do 20 | redirect "/" 21 | end 22 | 23 | get "/error" do 24 | raise "Error!" 25 | end 26 | 27 | get "/" do 28 | if params[:content_type] 29 | headers["Content-Type"] = params[:content_type] 30 | end 31 | LOGGER.error "I am a log message" 32 | <<-HTML 33 | 34 | 35 | 36 | 37 |

    Hello

    38 |

    Page with bookmarklet for enabling Rack::Bug

    39 |

    Page with a redirect - turn on intercept_redirects to see Rack::Bug catch it

    40 |

    Page with an error to check rack-bug not rescuing errors

    41 | 42 | 43 | HTML 44 | end 45 | 46 | end 47 | -------------------------------------------------------------------------------- /spec/rack/bug/panels/active_record_panel_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper') 2 | 3 | class Rack::Bug 4 | describe ActiveRecordPanel do 5 | before do 6 | ActiveRecordPanel.reset 7 | rack_env "rack-bug.panel_classes", [ActiveRecordPanel] 8 | end 9 | 10 | describe "heading" do 11 | it "displays the total number of instantiated AR objects" do 12 | ActiveRecordPanel.record("User") 13 | ActiveRecordPanel.record("Group") 14 | response = get_via_rack "/" 15 | response.should have_heading("2 AR Objects") 16 | end 17 | end 18 | 19 | describe "content" do 20 | it "displays the count of instantiated objects for each class" do 21 | ActiveRecordPanel.record("User") 22 | ActiveRecordPanel.record("User") 23 | ActiveRecordPanel.record("Group") 24 | response = get_via_rack "/" 25 | response.should have_row("#active_record", "User", "2") 26 | response.should have_row("#active_record", "Group", "1") 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/rack/bug/panels/cache_panel_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper') 2 | 3 | class Rack::Bug 4 | describe CachePanel do 5 | before do 6 | CachePanel.reset 7 | rack_env "rack-bug.panel_classes", [CachePanel] 8 | unless defined?(Rails) 9 | @added_rails = true 10 | Object.const_set :Rails, Module.new 11 | end 12 | end 13 | 14 | after do 15 | Object.send :remove_const, :Rails if @added_rails 16 | end 17 | 18 | describe "heading" do 19 | it "displays the total memcache time" do 20 | response = get_via_rack "/" 21 | response.should have_heading("Cache: 0.00ms") 22 | end 23 | end 24 | 25 | describe "content" do 26 | describe "usage table" do 27 | it "displays the total number of memcache calls" do 28 | CachePanel.record(:get, "user:1") { } 29 | response = get_via_rack "/" 30 | 31 | # This causes a bus error: 32 | # response.should have_selector("th:content('Total Calls') + td", :content => "1") 33 | 34 | response.should have_row("#cache_usage", "Total Calls", "1") 35 | end 36 | 37 | it "displays the total memcache time" do 38 | response = get_via_rack "/" 39 | response.should have_row("#cache_usage", "Total Time", "0.00ms") 40 | end 41 | 42 | it "dispays the number of memcache hits" do 43 | CachePanel.record(:get, "user:1") { } 44 | response = get_via_rack "/" 45 | response.should have_row("#cache_usage", "Hits", "0") 46 | end 47 | 48 | it "displays the number of memcache misses" do 49 | CachePanel.record(:get, "user:1") { } 50 | response = get_via_rack "/" 51 | response.should have_row("#cache_usage", "Misses", "1") 52 | end 53 | 54 | it "displays the number of memcache gets" do 55 | CachePanel.record(:get, "user:1") { } 56 | response = get_via_rack "/" 57 | response.should have_row("#cache_usage", "gets", "1") 58 | end 59 | 60 | it "displays the number of memcache sets" do 61 | CachePanel.record(:set, "user:1") { } 62 | response = get_via_rack "/" 63 | response.should have_row("#cache_usage", "sets", "1") 64 | end 65 | 66 | it "displays the number of memcache deletes" do 67 | CachePanel.record(:delete, "user:1") { } 68 | response = get_via_rack "/" 69 | response.should have_row("#cache_usage", "deletes", "1") 70 | end 71 | 72 | it "displays the number of memcache get_multis" do 73 | CachePanel.record(:get_multi, "user:1", "user:2") { } 74 | response = get_via_rack "/" 75 | response.should have_row("#cache_usage", "get_multis", "1") 76 | end 77 | end 78 | 79 | describe "breakdown" do 80 | it "displays each memcache operation" do 81 | CachePanel.record(:get, "user:1") { } 82 | response = get_via_rack "/" 83 | response.should have_row("#cache_breakdown", "get") 84 | end 85 | 86 | it "displays the time for each memcache call" do 87 | CachePanel.record(:get, "user:1") { } 88 | response = get_via_rack "/" 89 | response.should have_row("#cache_breakdown", "user:1", TIME_MS_REGEXP) 90 | end 91 | 92 | it "displays the keys for each memcache call" do 93 | CachePanel.record(:get, "user:1") { } 94 | response = get_via_rack "/" 95 | response.should have_row("#cache_breakdown", "user:1", "get") 96 | end 97 | end 98 | end 99 | 100 | describe "expire_all" do 101 | before do 102 | rack_env "rack-bug.secret_key", 'abc' 103 | end 104 | 105 | it "expires the cache keys" do 106 | Rails.stub!(:cache => mock("cache")) 107 | Rails.cache.should_receive(:delete).with("user:1") 108 | Rails.cache.should_receive(:delete).with("user:2") 109 | Rails.cache.should_receive(:delete).with("user:3") 110 | Rails.cache.should_receive(:delete).with("user:4") 111 | 112 | get_via_rack "/__rack_bug__/delete_cache_list", 113 | :keys_1 => "user:1", :keys_2 => "user:2", :keys_3 => "user:3", :keys_4 => "user:4", 114 | :hash => "c367b76e0199c308862a3afd8fba32b8715e7976" 115 | end 116 | 117 | it "returns OK" do 118 | Rails.stub!(:cache => mock("cache", :delete => nil)) 119 | response = get_via_rack "/__rack_bug__/delete_cache_list", 120 | :keys_1 => "user:1", :keys_2 => "user:2", :keys_3 => "user:3", :keys_4 => "user:4", 121 | :hash => "c367b76e0199c308862a3afd8fba32b8715e7976" 122 | response.should contain("OK") 123 | end 124 | end 125 | 126 | describe "expire" do 127 | before do 128 | rack_env "rack-bug.secret_key", 'abc' 129 | end 130 | 131 | it "expires the cache key" do 132 | Rails.stub!(:cache => mock("cache")) 133 | Rails.cache.should_receive(:delete).with("user:1") 134 | get_via_rack "/__rack_bug__/delete_cache", :key => "user:1", 135 | :hash => "f87215442d312d8e42cf51e6b66fc3eb3d59ac74" 136 | end 137 | 138 | it "returns OK" do 139 | Rails.stub!(:cache => mock("cache", :delete => nil)) 140 | response = get_via_rack "/__rack_bug__/delete_cache", :key => "user:1", 141 | :hash => "f87215442d312d8e42cf51e6b66fc3eb3d59ac74" 142 | response.should contain("OK") 143 | end 144 | end 145 | 146 | describe "view_cache" do 147 | before do 148 | rack_env "rack-bug.secret_key", 'abc' 149 | end 150 | 151 | it "renders the cache key" do 152 | Rails.stub!(:cache => mock("cache", :read => "cache body")) 153 | response = get_via_rack "/__rack_bug__/view_cache", :key => "user:1", 154 | :hash => "f87215442d312d8e42cf51e6b66fc3eb3d59ac74" 155 | response.should contain("cache body") 156 | end 157 | 158 | it "renders non-String cache values properly" do 159 | Rails.stub!(:cache => mock("cache", :read => [1, 2])) 160 | response = get_via_rack "/__rack_bug__/view_cache", :key => "user:1", 161 | :hash => "f87215442d312d8e42cf51e6b66fc3eb3d59ac74" 162 | response.should contain("[1, 2]") 163 | end 164 | end 165 | 166 | end 167 | end 168 | -------------------------------------------------------------------------------- /spec/rack/bug/panels/log_panel_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper') 2 | 3 | class Rack::Bug 4 | describe LogPanel do 5 | before do 6 | LogPanel.reset 7 | rack_env "rack-bug.panel_classes", [LogPanel] 8 | end 9 | 10 | describe "heading" do 11 | it "displays 'Log'" do 12 | response = get_via_rack "/" 13 | response.should have_heading("Log") 14 | end 15 | end 16 | 17 | describe "content" do 18 | it "displays recorded log lines" do 19 | LogPanel.record("This is a logged message", 0) 20 | response = get_via_rack "/" 21 | response.should contain("This is a logged message") 22 | response.should contain("DEBUG") 23 | end 24 | end 25 | 26 | describe "Extended Logger" do 27 | it "does still return true/false for #add if class Logger" do 28 | #AS::BufferedLogger returns the added string, Logger returns true/false 29 | LOGGER.add(0, "foo").should == true 30 | end 31 | end 32 | 33 | 34 | describe "With no logger defined" do 35 | it "does not err out" do 36 | logger = LOGGER 37 | Object.send :remove_const, :LOGGER 38 | lambda{ load("rack/bug/panels/log_panel/logger_extension.rb") }.should_not raise_error 39 | ::LOGGER = logger 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/rack/bug/panels/memory_panel_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper') 3 | 4 | class Rack::Bug 5 | describe MemoryPanel do 6 | before do 7 | rack_env "rack-bug.panel_classes", [MemoryPanel] 8 | end 9 | 10 | describe "heading" do 11 | it "displays the total memory" do 12 | response = get_via_rack "/" 13 | response.should have_heading(/\d+ KB total/) 14 | end 15 | 16 | it "displays the memory change during the request" do 17 | response = get_via_rack "/" 18 | response.should have_heading(/\d+ KB Δ/) 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/rack/bug/panels/mongo_panel_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper') 2 | $LOADED_FEATURES << "mongo.rb" #avoid dependency on mongo 3 | 4 | class Rack::Bug 5 | describe MongoPanel do 6 | before do 7 | MongoPanel.reset 8 | rack_env "rack-bug.panel_classes", [MongoPanel] 9 | end 10 | 11 | describe "heading" do 12 | it "displays the total mongo time" do 13 | response = get_via_rack "/" 14 | response.should have_heading("Mongo: 0.00ms") 15 | end 16 | end 17 | 18 | describe "content" do 19 | describe "usage table" do 20 | it "displays the total number of mongo calls" do 21 | MongoPanel.record("db.user.user.find()") { } 22 | response = get_via_rack "/" 23 | 24 | # This causes a bus error: 25 | # response.should have_selector("th:content('Total Calls') + td", :content => "1") 26 | 27 | response.should have_row("#mongo_usage", "Total Calls", "1") 28 | end 29 | 30 | it "displays the total mongo time" do 31 | response = get_via_rack "/" 32 | response.should have_row("#mongo_usage", "Total Time", "0.00ms") 33 | end 34 | end 35 | 36 | describe "breakdown" do 37 | it "displays each mongo operation" do 38 | MongoPanel.record("db.user.user.find()") { } 39 | response = get_via_rack "/" 40 | response.should have_row("#mongo_breakdown", "db.user.user.find()") 41 | end 42 | 43 | it "displays the time for mongo call" do 44 | MongoPanel.record("db.user.user.find()") { } 45 | response = get_via_rack "/" 46 | response.should have_row("#mongo_breakdown", "db.user.user.find()", TIME_MS_REGEXP) 47 | end 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /spec/rack/bug/panels/rails_info_panel_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper') 2 | 3 | class Rack::Bug 4 | describe RailsInfoPanel do 5 | before do 6 | rack_env "rack-bug.panel_classes", [RailsInfoPanel] 7 | 8 | unless defined?(Rails) 9 | @added_rails = true 10 | Object.const_set :Rails, Module.new 11 | Rails::Info = Class.new do 12 | def self.properties 13 | [] 14 | end 15 | end 16 | end 17 | end 18 | 19 | after do 20 | Object.send :remove_const, :Rails if @added_rails 21 | end 22 | 23 | describe "heading" do 24 | it "displays the Rails version" do 25 | Rails.stub!(:version => "v2.3.0") 26 | response = get_via_rack "/" 27 | response.should have_heading("Rails v2.3.0") 28 | end 29 | end 30 | 31 | describe "content" do 32 | it "displays the Rails::Info properties" do 33 | Rails.stub!(:version => "v2.3.0") 34 | Rails::Info.stub!(:properties => [["Name", "Value"]]) 35 | response = get_via_rack "/" 36 | response.should have_row("#rails_info", "Name", "Value") 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/rack/bug/panels/redis_panel_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper') 2 | $LOADED_FEATURES << "redis.rb" #avoid dependency on redis 3 | 4 | class Rack::Bug 5 | describe RedisPanel do 6 | before do 7 | RedisPanel.reset 8 | rack_env "rack-bug.panel_classes", [RedisPanel] 9 | end 10 | 11 | describe "heading" do 12 | it "displays the total redis time" do 13 | response = get_via_rack "/" 14 | response.should have_heading("Redis: 0.00ms") 15 | end 16 | end 17 | 18 | describe "content" do 19 | describe "usage table" do 20 | it "displays the total number of redis calls" do 21 | RedisPanel.record(["get, user:1"], Kernel.caller) { } 22 | response = get_via_rack "/" 23 | 24 | # This causes a bus error: 25 | # response.should have_selector("th:content('Total Calls') + td", :content => "1") 26 | 27 | response.should have_row("#redis_usage", "Total Calls", "1") 28 | end 29 | 30 | it "displays the total redis time" do 31 | response = get_via_rack "/" 32 | response.should have_row("#redis_usage", "Total Time", "0.00ms") 33 | end 34 | end 35 | 36 | describe "breakdown" do 37 | it "displays each redis operation" do 38 | RedisPanel.record(["get, user:1"], Kernel.caller) { } 39 | response = get_via_rack "/" 40 | response.should have_row("#redis_breakdown", "get") 41 | end 42 | 43 | it "displays the time for redis call" do 44 | RedisPanel.record(["get, user:1"], Kernel.caller) { } 45 | response = get_via_rack "/" 46 | response.should have_row("#redis_breakdown", "user:1", TIME_MS_REGEXP) 47 | end 48 | 49 | it "displays the arguments for each redis call" do 50 | RedisPanel.record(["get, user:1"], Kernel.caller) { } 51 | response = get_via_rack "/" 52 | response.should have_row("#redis_breakdown", "user:1", "get") 53 | end 54 | 55 | it "displays a link to show the backtrace when it's available" do 56 | RedisPanel.record(["get, user:1"], Kernel.caller) { } 57 | response = get_via_rack "/" 58 | response.should have_row("#redis_breakdown", "user:1", "Show Backtrace") 59 | end 60 | 61 | it "does not display a link to show the backtrace when it's not available" do 62 | RedisPanel.record(["get, user:1"], []) { } 63 | response = get_via_rack "/" 64 | response.should_not contain("Show Backtrace") 65 | end 66 | end 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /spec/rack/bug/panels/sql_panel_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper') 2 | 3 | class Rack::Bug 4 | describe SQLPanel do 5 | before do 6 | SQLPanel.reset 7 | rack_env "rack-bug.panel_classes", [SQLPanel] 8 | 9 | unless defined?(ActiveRecord) 10 | @added_rails = true 11 | Object.const_set :ActiveRecord, Module.new 12 | ActiveRecord.const_set :Base, Class.new 13 | end 14 | end 15 | 16 | after do 17 | Object.send :remove_const, :ActiveRecord if @added_active_record 18 | end 19 | 20 | describe "heading" do 21 | it "displays the total SQL query count" do 22 | SQLPanel.record("SELECT NOW();") { } 23 | response = get_via_rack "/" 24 | response.should have_heading("1 Queries") 25 | end 26 | 27 | it "displays the total SQL time" do 28 | SQLPanel.record("SELECT NOW();") { } 29 | response = get_via_rack "/" 30 | response.should have_heading(/Queries \(\d+\.\d{2}ms\)/) 31 | end 32 | end 33 | 34 | describe "content" do 35 | it "displays each executed SQL query" do 36 | SQLPanel.record("SELECT NOW();") { } 37 | response = get_via_rack "/" 38 | response.should have_row("#sql", "SELECT NOW();") 39 | end 40 | 41 | it "displays the time of each executed SQL query" do 42 | SQLPanel.record("SELECT NOW();") { } 43 | response = get_via_rack "/" 44 | response.should have_row("#sql", "SELECT NOW();", TIME_MS_REGEXP) 45 | end 46 | end 47 | 48 | def stub_result(results = [[]]) 49 | columns = results.first 50 | fields = columns.map { |c| stub("field", :name => c) } 51 | rows = results[1..-1] 52 | 53 | result = stub("result", :fetch_fields => fields) 54 | result.stub!(:each).and_yield(*rows) 55 | return result 56 | end 57 | 58 | def expect_query(sql, results) 59 | conn = stub("connection") 60 | ActiveRecord::Base.stub!(:connection => conn) 61 | conn.should_receive(:execute).with(sql).and_return(stub_result(results)) 62 | end 63 | 64 | describe "execute_sql" do 65 | it "displays the query results" do 66 | rack_env "rack-bug.secret_key", "abc" 67 | expect_query "SELECT username FROM users", 68 | [["username"], 69 | ["bryan"]] 70 | 71 | response = get_via_rack "/__rack_bug__/execute_sql", {:query => "SELECT username FROM users", 72 | :hash => "6f286f55b75716e5c91f16d77d09fa73b353ebc1"}, {:xhr => true} 73 | response.should contain("SELECT username FROM users") 74 | response.should be_ok 75 | end 76 | 77 | it "is forbidden when the hash is missing or wrong" do 78 | rack_env "rack-bug.secret_key", 'abc' 79 | 80 | lambda { 81 | get_via_rack "/__rack_bug__/execute_sql", {:query => "SELECT username FROM users", 82 | :hash => "foobar"}, {:xhr => true} 83 | }.should raise_error(SecurityError) 84 | end 85 | 86 | it "is not available when the rack-bug.secret_key is nil" do 87 | rack_env "rack-bug.secret_key", nil 88 | 89 | lambda { 90 | get_via_rack "/__rack_bug__/execute_sql", {:query => "SELECT username FROM users", 91 | :hash => "6f286f55b75716e5c91f16d77d09fa73b353ebc1"}, {:xhr => true} 92 | }.should raise_error(SecurityError) 93 | end 94 | 95 | it "is not available when the rack-bug.secret_key is an empty string" do 96 | rack_env "rack-bug.secret_key", "" 97 | 98 | lambda { 99 | get_via_rack "/__rack_bug__/execute_sql", {:query => "SELECT username FROM users", 100 | :hash => "6f286f55b75716e5c91f16d77d09fa73b353ebc1"}, {:xhr => true} 101 | }.should raise_error(SecurityError) 102 | end 103 | end 104 | 105 | describe "explain_sql" do 106 | it "displays the query explain plan" do 107 | rack_env "rack-bug.secret_key", "abc" 108 | expect_query "EXPLAIN SELECT username FROM users", 109 | [["table"], 110 | ["users"]] 111 | 112 | response = get_via_rack "/__rack_bug__/explain_sql", :query => "SELECT username FROM users", 113 | :hash => "6f286f55b75716e5c91f16d77d09fa73b353ebc1" 114 | response.should contain("SELECT username FROM users") 115 | response.should be_ok 116 | end 117 | 118 | it "is forbidden when the hash is missing or wrong" do 119 | rack_env "rack-bug.secret_key", 'abc' 120 | 121 | lambda { 122 | get_via_rack "/__rack_bug__/explain_sql", :query => "SELECT username FROM users", 123 | :hash => "foobar" 124 | }.should raise_error(SecurityError) 125 | end 126 | 127 | it "is not available when the rack-bug.secret_key is nil" do 128 | rack_env "rack-bug.secret_key", nil 129 | 130 | lambda { 131 | get_via_rack "/__rack_bug__/explain_sql", :query => "SELECT username FROM users", 132 | :hash => "6f286f55b75716e5c91f16d77d09fa73b353ebc1" 133 | }.should raise_error(SecurityError) 134 | end 135 | 136 | it "is not available when the rack-bug.secret_key is an empty string" do 137 | rack_env "rack-bug.secret_key", "" 138 | 139 | lambda { 140 | get_via_rack "/__rack_bug__/explain_sql", :query => "SELECT username FROM users", 141 | :hash => "6f286f55b75716e5c91f16d77d09fa73b353ebc1" 142 | }.should raise_error(SecurityError) 143 | end 144 | end 145 | end 146 | end 147 | -------------------------------------------------------------------------------- /spec/rack/bug/panels/templates_panel_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper') 2 | 3 | class Rack::Bug 4 | describe TemplatesPanel do 5 | before do 6 | TemplatesPanel.reset 7 | rack_env "rack-bug.panel_classes", [TemplatesPanel] 8 | end 9 | 10 | describe "heading" do 11 | it "displays the total rendering time" do 12 | response = get_via_rack "/" 13 | response.should have_heading("Templates: 0.00ms") 14 | end 15 | end 16 | 17 | describe "content" do 18 | it "displays the template paths" do 19 | TemplatesPanel.record("users/show") { } 20 | response = get_via_rack "/" 21 | response.should contain("users/show") 22 | end 23 | 24 | it "displays the template children" do 25 | TemplatesPanel.record("users/show") do 26 | TemplatesPanel.record("users/toolbar") { } 27 | end 28 | 29 | response = get_via_rack "/" 30 | response.should have_selector("li", :content => "users/show") do |li| 31 | li.should contain("users/toolbar") 32 | end 33 | end 34 | 35 | context "for templates that rendered templates" do 36 | it "displays the total time" do 37 | TemplatesPanel.record("users/show") do 38 | TemplatesPanel.record("users/toolbar") { } 39 | end 40 | 41 | response = get_via_rack "/" 42 | response.should have_selector("li", :content => "users/show") do |li| 43 | li.should contain(TIME_MS_REGEXP) 44 | end 45 | end 46 | 47 | it "displays the exclusive time" do 48 | TemplatesPanel.record("users/show") do 49 | TemplatesPanel.record("users/toolbar") { } 50 | end 51 | 52 | response = get_via_rack "/" 53 | response.should have_selector("li", :content => "users/show") do |li| 54 | li.should contain(/\d\.\d{2} exclusive/) 55 | end 56 | end 57 | end 58 | 59 | context "for leaf templates" do 60 | it "does not display the exclusive time" do 61 | TemplatesPanel.record("users/show") { } 62 | 63 | response = get_via_rack "/" 64 | response.should contain("users/show") do |li| 65 | li.should_not contain("exclusive") 66 | end 67 | end 68 | end 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /spec/rack/bug/panels/timer_panel_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper') 2 | 3 | class Rack::Bug 4 | describe TimerPanel do 5 | before do 6 | rack_env "rack-bug.panel_classes", [TimerPanel] 7 | end 8 | 9 | describe "heading" do 10 | it "displays the elapsed time" do 11 | response = get_via_rack "/" 12 | response.should have_heading(TIME_MS_REGEXP) 13 | end 14 | end 15 | 16 | describe "content" do 17 | it "displays the user CPU time" do 18 | response = get_via_rack "/" 19 | response.should have_row("#timer", "User CPU time", TIME_MS_REGEXP) 20 | end 21 | 22 | it "displays the system CPU time" do 23 | response = get_via_rack "/" 24 | response.should have_row("#timer", "System CPU time", TIME_MS_REGEXP) 25 | end 26 | 27 | it "displays the total CPU time" do 28 | response = get_via_rack "/" 29 | response.should have_row("#timer", "Total CPU time", TIME_MS_REGEXP) 30 | end 31 | 32 | it "displays the elapsed time" do 33 | response = get_via_rack "/" 34 | response.should have_row("#timer", "Elapsed time", TIME_MS_REGEXP) 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/rack/bug_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') 2 | 3 | describe Rack::Bug do 4 | it "inserts the Rack::Bug toolbar" do 5 | response = get "/" 6 | response.should have_selector("div#rack_bug") 7 | end 8 | 9 | it "updates the Content-Length" do 10 | response = get "/" 11 | response["Content-Length"].should == response.body.size.to_s 12 | end 13 | 14 | it "serves the Rack::Bug assets under /__rack_bug__/" do 15 | response = get "/__rack_bug__/bug.css" 16 | response.should be_ok 17 | end 18 | 19 | it "modifies HTML responses with a charset" do 20 | response = get "/", :content_type => "application/xhtml+xml; charset=utf-8" 21 | response.should have_selector("div#rack_bug") 22 | end 23 | 24 | it "does not modify XMLHttpRequest responses" do 25 | response = get "/", {}, { :xhr => true } 26 | response.should_not have_selector("div#rack_bug") 27 | end 28 | 29 | it "modifies XHTML responses" do 30 | response = get "/", :content_type => "application/xhtml+xml" 31 | response.should have_selector("div#rack_bug") 32 | end 33 | 34 | it "does not modify non-HTML responses" do 35 | response = get "/", :content_type => "text/csv" 36 | response.should_not have_selector("div#rack_bug") 37 | end 38 | 39 | it "does not modify server errors" do 40 | app.disable :raise_errors 41 | response = get "/error" 42 | app.enable :raise_errors 43 | response.should_not have_selector("div#rack_bug") 44 | end 45 | 46 | context "redirected when not configured to intercept redirects" do 47 | it "passes the redirect unmodified" do 48 | response = get "/redirect" 49 | response.status.should == 302 50 | end 51 | 52 | it "does not show the interception page" do 53 | response = get "/redirect" 54 | response.body.should_not contain("Location: /") 55 | end 56 | 57 | it "does not insert the toolbar" do 58 | header 'cookie', "" 59 | response = get "/redirect" 60 | response.should_not have_selector("div#rack_bug") 61 | end 62 | 63 | it "does not insert the toolbar if even toolbar requested" do 64 | response = get "/redirect" 65 | response.should_not have_selector("div#rack_bug") 66 | end 67 | end 68 | 69 | context "redirected when configured to intercept redirects" do 70 | it "shows the interception page" do 71 | response = get "/redirect", {}, "rack-bug.intercept_redirects" => true 72 | response.should have_selector("div#rack_bug") 73 | end 74 | 75 | it "should provide a link to the target URL" do 76 | response = get "/redirect", {}, "rack-bug.intercept_redirects" => true 77 | response.should have_selector("a[href='/']") 78 | end 79 | 80 | it "inserts the toolbar if requested" do 81 | response = get "/redirect", {}, "rack-bug.intercept_redirects" => true 82 | response.should have_selector("div#rack_bug") 83 | end 84 | 85 | it "does not inserts the toolbar if not requested" do 86 | header 'cookie', "" 87 | response = get "/redirect", {}, "rack-bug.intercept_redirects" => true 88 | response.should_not have_selector("div#rack_bug") 89 | end 90 | end 91 | 92 | context "configured with an IP address restriction" do 93 | before do 94 | rack_env "rack-bug.ip_masks", [IPAddr.new("127.0.0.1/255.255.255.0")] 95 | end 96 | 97 | it "inserts the Rack::Bug toolbar when the IP matches" do 98 | response = get_via_rack "/", {}, "REMOTE_ADDR" => "127.0.0.2" 99 | response.should have_selector("div#rack_bug") 100 | end 101 | 102 | it "is disabled when the IP doesn't match" do 103 | response = get_via_rack "/", {}, "REMOTE_ADDR" => "128.0.0.1" 104 | response.should_not have_selector("div#rack_bug") 105 | end 106 | 107 | it "doesn't use any panels" do 108 | DummyPanel.should_not_receive(:new) 109 | rack_env "rack-bug.panel_classes", [DummyPanel] 110 | get_via_rack "/", {}, "REMOTE_ADDR" => "128.0.0.1" 111 | end 112 | end 113 | 114 | context "configured with a password" do 115 | before do 116 | rack_env "rack-bug.password", "secret" 117 | end 118 | 119 | it "inserts the Rack::Bug toolbar when the password matches" do 120 | sha = "545049d1c5e2a6e0dfefd37f9a9e0beb95241935" 121 | set_cookie ["rack_bug_enabled=1", "rack_bug_password=#{sha}"] 122 | response = get_via_rack "/" 123 | response.should have_selector("div#rack_bug") 124 | end 125 | 126 | it "is disabled when the password doesn't match" do 127 | response = get_via_rack "/" 128 | response.should_not have_selector("div#rack_bug") 129 | end 130 | 131 | it "doesn't use any panels" do 132 | DummyPanel.should_not_receive(:new) 133 | rack_env "rack-bug.panel_classes", [DummyPanel] 134 | get_via_rack "/" 135 | end 136 | end 137 | end 138 | -------------------------------------------------------------------------------- /spec/rcov.opts: -------------------------------------------------------------------------------- 1 | -x gems,spec\/ -------------------------------------------------------------------------------- /spec/spec.opts: -------------------------------------------------------------------------------- 1 | --color 2 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require "rubygems" 2 | require "spec" 3 | require "webrat" 4 | require "rack/test" 5 | 6 | RAILS_ENV = "test" 7 | 8 | $LOAD_PATH.unshift File.dirname(File.dirname(__FILE__)) + '/lib' 9 | $LOAD_PATH.unshift File.dirname(File.dirname(__FILE__)) 10 | 11 | require "rack/bug" 12 | require "spec/fixtures/sample_app" 13 | require "spec/fixtures/dummy_panel" 14 | require "spec/custom_matchers" 15 | 16 | Spec::Runner.configure do |config| 17 | TIME_MS_REGEXP = /\d+\.\d{2}ms/ 18 | 19 | config.include Rack::Test::Methods 20 | config.include Webrat::Matchers 21 | config.include CustomMatchers 22 | 23 | config.before do 24 | # This allows specs to record data outside the request 25 | Rack::Bug.enable 26 | 27 | # Set the cookie that triggers Rack::Bug under normal conditions 28 | set_cookie "rack_bug_enabled=1" 29 | end 30 | 31 | def app 32 | SampleApp 33 | end 34 | 35 | def rack_env(key, value) 36 | @rack_env ||= {} 37 | @rack_env[key] = value 38 | end 39 | 40 | def get_via_rack(uri, params = {}, env = {}, &block) 41 | env = env.merge(@rack_env) if @rack_env 42 | get(uri, params, env, &block) 43 | end 44 | end 45 | --------------------------------------------------------------------------------