diff --git a/.gitignore b/.gitignore index c937098..76b2fe3 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ log/* coverage pkg *.kpf +bin +vendor +*.gemspec diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..9c2574f --- /dev/null +++ b/Gemfile @@ -0,0 +1,17 @@ +bundle_path "vendor" +disable_system_gems + +merb_gem_version = '>=1.0.9' + +only :release do + gem "merb-core", merb_gem_version + gem 'toadhopper', '~>0.9.1' +end + +only :test do + gem 'rake' + gem 'rcov' + gem 'rr' + gem 'ruby-debug' + gem 'bundler' +end diff --git a/Rakefile b/Rakefile index 465c3e7..d3e66fd 100644 --- a/Rakefile +++ b/Rakefile @@ -3,19 +3,13 @@ require 'rake/gempackagetask' require 'rubygems/specification' require 'date' require 'merb-core/version' -require 'merb-core/tasks/merb_rake_helper' require 'spec/rake/spectask' +require 'bundler' install_home = ENV['GEM_HOME'] ? "-i #{ENV['GEM_HOME']}" : "" - -def sudo - windows = (PLATFORM =~ /win32|cygwin/) rescue nil - ENV['MERB_SUDO'] ||= "sudo" - sudo = windows ? "" : ENV['MERB_SUDO'] -end NAME = "merb_hoptoad_notifier" -GEM_VERSION = "1.0.9.1" +GEM_VERSION = "1.0.10" AUTHOR = "Corey Donohoe" EMAIL = 'atmos@atmos.org' HOMEPAGE = "http://github.com/atmos/merb_hoptoad_notifier" @@ -33,7 +27,13 @@ spec = Gem::Specification.new do |s| s.author = AUTHOR s.email = EMAIL s.homepage = HOMEPAGE - s.add_dependency('merb-core', '>= 1.0.9') + + manifest = Bundler::Environment.load(File.dirname(__FILE__) + '/Gemfile') + manifest.dependencies.each do |d| + next unless d.only && d.only.include?('release') + s.add_dependency(d.name, d.version) + end + s.require_path = 'lib' s.files = %w(LICENSE README Rakefile TODO) + Dir.glob("{lib,spec}/**/*") end @@ -42,11 +42,6 @@ Rake::GemPackageTask.new(spec) do |pkg| pkg.gem_spec = spec end -desc "install the plugin locally" -task :install => [:package] do - sh %{#{sudo} gem install #{install_home} pkg/#{NAME}-#{GEM_VERSION} --no-update-sources} -end - desc "create a gemspec file" task :make_spec do File.open("#{NAME}.gemspec", "w") do |file| @@ -54,20 +49,13 @@ task :make_spec do end end -namespace :jruby do - desc "Run :package and install the resulting .gem with jruby" - task :install => :package do - sh %{#{sudo} jruby -S gem install #{install_home} pkg/#{NAME}-#{GEM_VERSION}.gem --no-rdoc --no-ri} - end -end - Spec::Rake::SpecTask.new(:default) do |t| t.spec_opts << %w(-fs --color) << %w(-O spec/spec.opts) t.spec_opts << '--loadby' << 'random' t.spec_files = Dir["spec/*_spec.rb"] t.rcov = ENV.has_key?('NO_RCOV') ? ENV['NO_RCOV'] != 'true' : true t.rcov_opts << '--exclude' << '.gem/' - + t.rcov_opts << '--text-summary' t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse' t.rcov_opts << '--only-uncovered' diff --git a/lib/merb_hoptoad_notifier.rb b/lib/merb_hoptoad_notifier.rb index f0c39fc..af83a8c 100644 --- a/lib/merb_hoptoad_notifier.rb +++ b/lib/merb_hoptoad_notifier.rb @@ -1,10 +1,9 @@ -require File.expand_path(File.dirname(__FILE__)+'/merb_hoptoad_notifier/hoptoad_notifier') -require File.expand_path(File.dirname(__FILE__)+'/merb_hoptoad_notifier/hoptoad_mixin') - -# make sure we're running inside Merb if defined?(Merb::Plugins) + libdir = File.join(File.dirname(__FILE__), 'merb_hoptoad_notifier') + require 'toadhopper' + require File.join(libdir, 'hoptoad_notifier') + Merb::BootLoader.after_app_loads do HoptoadNotifier.configure end - Merb::Plugins.add_rakefiles "merb_hoptoad_notifier/merbtasks" -end \ No newline at end of file +end diff --git a/lib/merb_hoptoad_notifier/hoptoad_mixin.rb b/lib/merb_hoptoad_notifier/hoptoad_mixin.rb deleted file mode 100644 index ecadf32..0000000 --- a/lib/merb_hoptoad_notifier/hoptoad_mixin.rb +++ /dev/null @@ -1,17 +0,0 @@ -require File.expand_path(File.dirname(__FILE__)+'/hoptoad_notifier') - -module HoptoadMixin - def notify_hoptoad(request=nil, session=nil) - request ||= self.request - session ||= self.session - - HoptoadNotifier.notify_hoptoad(request, session) - end - - def warn_hoptoad(message, request=nil, session=nil, options={}) - request ||= self.request - session ||= self.session - - HoptoadNotifier.warn_hoptoad(message, request, session, options) - end -end diff --git a/lib/merb_hoptoad_notifier/hoptoad_notifier.rb b/lib/merb_hoptoad_notifier/hoptoad_notifier.rb index bd29f6e..6f27951 100644 --- a/lib/merb_hoptoad_notifier/hoptoad_notifier.rb +++ b/lib/merb_hoptoad_notifier/hoptoad_notifier.rb @@ -1,163 +1,40 @@ -require 'net/http' - module HoptoadNotifier class << self attr_accessor :api_key, :logger + end - def configure - key = YAML.load_file(Merb.root / 'config' / 'hoptoad.yml') - if key - env = key[Merb.env.to_sym] - env ? @api_key = env[:api_key] : raise(ArgumentError, "No hoptoad key for Merb environment #{Merb.env}") - end - end - - def logger - @logger || Merb.logger - end - - def environment_filters - @environment_filters ||= %w(AWS_ACCESS_KEY AWS_SECRET_ACCESS_KEY AWS_ACCOUNT SSH_AUTH_SOCK) - end - - def warn_hoptoad(message, request, session, options={}) - return if request.nil? - params = request.params - - data = { - :api_key => HoptoadNotifier.api_key, - :error_class => options[:error_class] || message, - :error_message => message, - :backtrace => caller, - :environment => ENV.to_hash - } - - data[:request] = { - :params => params - } - - data[:environment] = clean_hoptoad_environment(ENV.to_hash.merge(request.env)) - data[:environment][:RAILS_ENV] = Merb.env - - data[:session] = { - :key => session.instance_variable_get("@session_id"), - :data => session.to_hash - } - - send_to_hoptoad :notice => default_notice_options.merge(data) - true - end - - def notify_hoptoad(request, session) - return if request.nil? - params = request.params - - request.exceptions.each do |exception| - data = { - :api_key => HoptoadNotifier.api_key, - :error_class => Extlib::Inflection.camelize(exception.class.name), - :error_message => "#{Extlib::Inflection.camelize(exception.class.name)}: #{exception.message}", - :backtrace => exception.backtrace, - :environment => ENV.to_hash - } - - data[:request] = { - :params => params - } - - data[:environment] = clean_hoptoad_environment(ENV.to_hash.merge(request.env)) - data[:environment][:RAILS_ENV] = Merb.env - - data[:session] = { - :key => session.instance_variable_get("@session_id"), - :data => session.to_hash - } + def self.configure + key = YAML.load_file(Merb.root / 'config' / 'hoptoad.yml') - send_to_hoptoad :notice => default_notice_options.merge(data) - end - true - end - - def notify_hoptoad_exception(exception) - data = { - :api_key => HoptoadNotifier.api_key, - :error_class => Extlib::Inflection.camelize(exception.class.name), - :error_message => "#{Extlib::Inflection.camelize(exception.class.name)}: #{exception.message}", - :backtrace => exception.backtrace, - :environment => ENV.to_hash - } - - data[:environment][:RAILS_ENV] = Merb.env - - send_to_hoptoad :notice => default_notice_options.merge(data) - true + if key + env = key[Merb.env.to_sym] + env ? @api_key = env[:api_key] : raise(ArgumentError, "No hoptoad key for Merb environment #{Merb.env}") end - + end - def send_to_hoptoad(data) #:nodoc: - url = URI.parse("http://hoptoadapp.com:80/notices/") + def self.logger + @logger || Merb.logger + end - Net::HTTP.start(url.host, url.port) do |http| - headers = { - 'Content-type' => 'application/x-yaml', - 'Accept' => 'text/xml, application/xml' - } - http.read_timeout = 5 # seconds - http.open_timeout = 2 # seconds - # http.use_ssl = HoptoadNotifier.secure - response = begin - http.post(url.path, clean_non_serializable_data(data).to_yaml, headers) - rescue TimeoutError => e - logger.error "Timeout while contacting the Hoptoad server." - nil - end - case response - when Net::HTTPSuccess then - logger.info "Hoptoad Success: #{response.class}" - else - logger.error "Hoptoad Failure: #{response.class}\n#{response.body if response.respond_to? :body}" - end - end - end - - def default_notice_options #:nodoc: - { - :api_key => HoptoadNotifier.api_key, - :error_message => 'Notification', - :backtrace => nil, - :request => {}, - :session => {}, - :environment => {} + def self.notify_hoptoad(request, session) + request.exceptions.each do |exception| + options = { + :api_key => HoptoadNotifier.api_key, + :url => "#{request.protocol}://#{request.host}#{request.path}", + :component => request.params['controller'], + :action => request.params['action'], + :request => request, + :framework_env => Merb.env, + :notifier_name => 'Merb::HoptoadNotifier', + :notifier_version => '1.0.10', + :session => session.to_hash } - end - - def clean_non_serializable_data(notice) #:nodoc: - notice.select{|k,v| serializable?(v) }.inject({}) do |h, pair| - h[pair.first] = pair.last.is_a?(Hash) ? clean_non_serializable_data(pair.last) : pair.last - h - end - end - - def serializable?(value) #:nodoc: - value.is_a?(Fixnum) || - value.is_a?(Array) || - value.is_a?(String) || - value.is_a?(Hash) || - value.is_a?(Bignum) + dispatcher.post!(exception, options, {'X-Hoptoad-Client-Name' => 'Merb::HoptoadNotifier'}) end + true + end - def stringify_keys(hash) #:nodoc: - hash.inject({}) do |h, pair| - h[pair.first.to_s] = pair.last.is_a?(Hash) ? stringify_keys(pair.last) : pair.last - h - end - end - def clean_hoptoad_environment(env) #:nodoc: - env.each do |k, v| - env[k] = "[FILTERED]" if HoptoadNotifier.environment_filters.any? do |filter| - k.to_s.match(/#{filter}/) - end - end - end + def self.dispatcher + @dispatcher ||= ToadHopper.new(api_key) end end diff --git a/lib/merb_hoptoad_notifier/merbtasks.rb b/lib/merb_hoptoad_notifier/merbtasks.rb deleted file mode 100644 index 5884c44..0000000 --- a/lib/merb_hoptoad_notifier/merbtasks.rb +++ /dev/null @@ -1,2 +0,0 @@ -namespace :merb_hoptoad_notifier do -end \ No newline at end of file diff --git a/merb_hoptoad_notifier.gemspec b/merb_hoptoad_notifier.gemspec deleted file mode 100644 index a9698d5..0000000 --- a/merb_hoptoad_notifier.gemspec +++ /dev/null @@ -1,33 +0,0 @@ -# -*- encoding: utf-8 -*- - -Gem::Specification.new do |s| - s.name = %q{merb_hoptoad_notifier} - s.version = "1.0.9" - - s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= - s.authors = ["Corey Donohoe"] - s.date = %q{2009-03-07} - s.description = %q{Merb plugin that provides hoptoad exception notification} - s.email = %q{atmos@atmos.org} - s.extra_rdoc_files = ["README", "LICENSE", "TODO"] - s.files = ["LICENSE", "README", "Rakefile", "TODO", "lib/merb_hoptoad_notifier", "lib/merb_hoptoad_notifier/hoptoad_mixin.rb", "lib/merb_hoptoad_notifier/hoptoad_notifier.rb", "lib/merb_hoptoad_notifier/merbtasks.rb", "lib/merb_hoptoad_notifier.rb", "spec/fixtures", "spec/fixtures/hoptoad.yml", "spec/merb_hoptoad_notifier_spec.rb", "spec/spec.opts", "spec/spec_helper.rb"] - s.has_rdoc = true - s.homepage = %q{http://github.com/atmos/merb_hoptoad_notifier} - s.require_paths = ["lib"] - s.rubyforge_project = %q{merb} - s.rubygems_version = %q{1.3.1} - s.summary = %q{Merb plugin that provides hoptoad exception notification} - - if s.respond_to? :specification_version then - current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION - s.specification_version = 2 - - if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then - s.add_runtime_dependency(%q, [">= 1.0.9"]) - else - s.add_dependency(%q, [">= 1.0.9"]) - end - else - s.add_dependency(%q, [">= 1.0.9"]) - end -end diff --git a/spec/merb_hoptoad_notifier_spec.rb b/spec/merb_hoptoad_notifier_spec.rb index 4b3d122..a6ef84e 100644 --- a/spec/merb_hoptoad_notifier_spec.rb +++ b/spec/merb_hoptoad_notifier_spec.rb @@ -1,113 +1,15 @@ require File.dirname(__FILE__) + '/spec_helper' describe "HoptoadNotifier" do - include Merb::Spec::Helpers - before(:each) do - stub(Merb).env { :production } - stub(Merb).root { Dir.tmpdir } - - @http = Net::HTTP.new('hoptoadapp.com') - @headers = { "Content-type" => "application/x-yaml", "Accept" => "text/xml, application/xml" } - @config = {:development => {:api_key=>"ZOMGLOLROFLMAO"}, :production => {:api_key=>"UBERSECRETSHIT"}, :test => {:api_key=>"ZOMGLOLROFLMAO"}} - end - - it "should define a constant" do - HoptoadNotifier.should_not be_nil - end + config = { :development => { :api_key=> ENV['MY_HOPTOAD_API_KEY'] || 'blah' } } + mock(YAML).load_file(File.join(Merb.root / 'config' / 'hoptoad.yml')) { config } - describe ".configure" do - before(:each) do - mock(YAML).load_file(File.join(Merb.root / 'config' / 'hoptoad.yml')) { @config } - HoptoadNotifier.configure - end - it "should know the api key after configuring" do - HoptoadNotifier.api_key.should == 'UBERSECRETSHIT' - end - end - - describe ".stringify_key" do - it "should turn string keys into symbols" do - HoptoadNotifier.stringify_keys({'foo' => 'bar', :baz => 'foo', :bar => 'foo'}).should == { 'foo' => 'bar', 'baz' => 'foo', 'bar' => 'foo'} - end + HoptoadNotifier.configure end - - describe "notification" do - before(:each) do - stub(Net::HTTP).new('hoptoadapp.com', 80, nil, nil, nil, nil) { @http } - mock(YAML).load_file(File.join(Merb.root / 'config' / 'hoptoad.yml')) { @config } - HoptoadNotifier.configure - end - - describe ".default_notice_options" do - it "should return sane defaults" do - HoptoadNotifier.default_notice_options.should == { - :api_key => HoptoadNotifier.api_key, - :error_message => 'Notification', - :backtrace => nil, - :request => {}, - :session => {}, - :environment => {} - } - end - end - - describe ".notify_hoptoad" do - describe "bad input" do - it "should handle nil input" do - HoptoadNotifier.notify_hoptoad(nil, nil).should be_nil - end - end - - describe "good input" do - before(:each) do - mock(HoptoadNotifier).send_to_hoptoad(anything).times(2) { true } - @request = setup_merb_request - mock(@request).exceptions { [RuntimeError.new('ZOMG'), RuntimeError.new('ORLY')]} - mock(@request).params { {"q"=>"0017000000SmnJ0"} } - end - it "should return true" do - HoptoadNotifier.notify_hoptoad(@request, {}).should be_true - end - end - end - - describe ".send_to_hoptoad" do - describe "any 2XX response" do - before(:each) do - response = Net::HTTPOK.new('1.1', 200, 'Wazzup?') - mock(@http).post("/notices/", "--- {}\n\n", @headers) { response } - end - it "should log success" do - mock(HoptoadNotifier.logger).info "Hoptoad Success: Net::HTTPOK" - HoptoadNotifier.send_to_hoptoad({}) - end - end - describe "any non 2XX response" do - before(:each) do - response = Net::HTTPInternalServerError.new('1.1', 500, 'Upstream unavailable') - mock(response).body { 'Upstream unavailable' } - - stub(@http.instance_variable_get('@socket')).closed? { true } - mock(@http).post("/notices/", "--- {}\n\n", @headers) { response } - end - it "should log failure" do - mock(HoptoadNotifier.logger).error "Hoptoad Failure: Net::HTTPInternalServerError\nUpstream unavailable" - HoptoadNotifier.send_to_hoptoad({}) - end - end - describe "a timeout exception is thrown" do - before(:each) do - response = TimeoutError.new("It took too fucking long") - mock(@http).post("/notices/", "--- {}\n\n", @headers) { raise response } - end - it "should log failure" do - mock(HoptoadNotifier.logger).error("Hoptoad Failure: NilClass\n") - mock(HoptoadNotifier.logger).error("Timeout while contacting the Hoptoad server.") - HoptoadNotifier.send_to_hoptoad({}) - end - end + it "posts to hoptoad" do + HoptoadNotifier.notify_hoptoad(fake_request_with_exceptions, { :user_id => 42 }) end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 29d4d64..0bd0a54 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,4 +1,4 @@ -$TESTING=true +Bundler.require_env(:test) $:.push File.join(File.dirname(__FILE__), '..', 'lib') require 'rr' require 'merb-core' @@ -6,28 +6,21 @@ require 'tmpdir' require 'pp' -Spec::Runner.configure do |config| - config.mock_with :rr +class TestError < RuntimeError end -module Merb - module Spec - module Helpers - def setup_merb_request - rack_env = {"SERVER_NAME"=>"localhost", - "rack.run_once"=>false, "rack.url_scheme"=>"http", "HTTP_USER_AGENT"=>"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_5; en-us) AppleWebKit/526.1+ (KHTML, like Gecko) Version/3.1.2 Safari/525.20.1", - "HTTP_ACCEPT_ENCODING"=>"gzip, deflate", "PATH_INFO"=>"/search/", "HTTP_CACHE_CONTROL"=>"max-age=0", - "HTTP_ACCEPT_LANGUAGE"=>"en-us", "HTTP_HOST"=>"localhost:4000", "SERVER_PROTOCOL"=>"HTTP/1.1", "SCRIPT_NAME"=>"", - "REQUEST_PATH"=>"/search/", "SERVER_SOFTWARE"=>"Mongrel 1.1.5", "REMOTE_ADDR"=>"127.0.0.1", "rack.streaming"=>true, - "rack.version"=>[0, 1], "rack.multithread"=>true, "HTTP_VERSION"=>"HTTP/1.1", "rack.multiprocess"=>false, - "REQUEST_URI"=>"/search/?q=0017000000SmnJ0", "SERVER_PORT"=>"4000", "QUERY_STRING"=>"q=0017000000SmnJ0", - "GATEWAY_INTERFACE"=>"CGI/1.2", "HTTP_ACCEPT"=>"text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5", - "REQUEST_METHOD"=>"GET", "HTTP_CONNECTION"=>"keep-alive"} - - request = Merb::Request.new(rack_env) - stub(request).env { rack_env } - request - end +Spec::Runner.configure do |config| + config.mock_with :rr + def fake_request + Merb::Test::RequestHelper::FakeRequest.new + end + def fake_request_with_exceptions(params = { :controller => 'Application', :action => 'index' }) + request = Merb::Test::RequestHelper::FakeRequest.new(:params => params) + begin + raise(TestError, 'I like turtles') + rescue => e + request.exceptions = [ e ] end + request end end