flyingmachine-merb_whoops_notifier 1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2008 Corey Donohoe<atmos@atmos.org>, Joakim Kolsjö<trejje@gmail.com>,
2
+ Daniel Higginbotham<daniel@flyingmachinestudios.com>
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining
5
+ a copy of this software and associated documentation files (the
6
+ "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish,
8
+ distribute, sublicense, and/or sell copies of the Software, and to
9
+ permit persons to whom the Software is furnished to do so, subject to
10
+ the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,59 @@
1
+ merb_whoops_notifier
2
+ ---------------------------------------------
3
+ This is merb plugin for exception notification with whoops. It should work with
4
+ any merb app that's based on merb 1.0 and higher
5
+
6
+ This plugin: http://github.com/atmos/merb_whoops_notifier/tree/master
7
+ The original: http://github.com/thoughtbot/whoops_notifier/tree/master
8
+
9
+ Usage:
10
+
11
+ 1) Get your api key for your app from whoopsapp.com
12
+
13
+ 2) Add the api key to config/whoops.yml with a similar syntax as the following
14
+ ---
15
+ :development: &defaults
16
+ :api_key: ZOMGLOLROFLMAO
17
+
18
+ :rake:
19
+ <<: *defaults
20
+
21
+ :test:
22
+ <<: *defaults
23
+
24
+ :production:
25
+ :api_key: UBERSECRETSHIT
26
+
27
+
28
+ 3) Require whoops in init.rb
29
+ require 'merb_whoops_notifier'
30
+
31
+ 4) Add the following method to your Exceptions controller. Depending on your merb version you'll need to use the exceptions,standard_error, or internal_server error as the action name. Kinda weak, but the API changed a lot in 0.9.x
32
+
33
+ class Exceptions < Merb::Controller
34
+ if %w( staging production ).include?(Merb.env)
35
+ def standard_error
36
+ WhoopsNotifier.notify_whoops(request, session)
37
+ render
38
+ end
39
+ end
40
+ end
41
+
42
+ 5) Restart the server, trigger an error(in staging or prod) and check that it arrived at whoops :)
43
+
44
+
45
+ Filtersing your post environment
46
+ --------------------------------
47
+ If you have environmental variables set in your ruby process that should not be sent to whoops,
48
+ there's a mechanism for filtering those attributes now. Throw something like this in
49
+ config/init.rb
50
+
51
+ Merb::BootLoader.after_app_loads do
52
+ WhoopsNotifier.environment_filters = %w(^AWS ^EC2 SECRET PRIVATE KEY)
53
+ end
54
+
55
+ Each of these words will be compiled into a regex so you should be able to use anchors if needed.
56
+
57
+ Thanks to the following GitHubbers
58
+ ----------------------------------
59
+ joakimk, fairchild and cv.
data/Rakefile ADDED
@@ -0,0 +1,74 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require 'rubygems/specification'
4
+ require 'date'
5
+ require 'merb-core/version'
6
+ require 'merb-core/tasks/merb_rake_helper'
7
+ require 'spec/rake/spectask'
8
+
9
+ install_home = ENV['GEM_HOME'] ? "-i #{ENV['GEM_HOME']}" : ""
10
+
11
+ def sudo
12
+ windows = (PLATFORM =~ /win32|cygwin/) rescue nil
13
+ ENV['MERB_SUDO'] ||= "sudo"
14
+ sudo = windows ? "" : ENV['MERB_SUDO']
15
+ end
16
+
17
+ NAME = "merb_whoops_notifier"
18
+ GEM_VERSION = "1.0"
19
+ AUTHOR = "Daniel Higginbotham"
20
+ EMAIL = 'daniel@flyingmachinestudios.com'
21
+ HOMEPAGE = "http://github.com/flyingmachine/merb_whoops_notifier"
22
+ SUMMARY = "Merb plugin that provides whoops exception notification"
23
+
24
+ spec = Gem::Specification.new do |s|
25
+ s.rubyforge_project = 'merb'
26
+ s.name = NAME
27
+ s.version = GEM_VERSION
28
+ s.platform = Gem::Platform::RUBY
29
+ s.has_rdoc = true
30
+ s.extra_rdoc_files = ["README", "LICENSE", 'TODO']
31
+ s.summary = SUMMARY
32
+ s.description = s.summary
33
+ s.author = AUTHOR
34
+ s.email = EMAIL
35
+ s.homepage = HOMEPAGE
36
+ s.add_dependency('merb-core', '>= 1.0.9')
37
+ s.require_path = 'lib'
38
+ s.files = %w(LICENSE README Rakefile TODO) + Dir.glob("{lib,spec}/**/*")
39
+ end
40
+
41
+ Rake::GemPackageTask.new(spec) do |pkg|
42
+ pkg.gem_spec = spec
43
+ end
44
+
45
+ desc "install the plugin locally"
46
+ task :install => [:package] do
47
+ sh %{#{sudo} gem install #{install_home} pkg/#{NAME}-#{GEM_VERSION} --no-update-sources}
48
+ end
49
+
50
+ desc "create a gemspec file"
51
+ task :make_spec do
52
+ File.open("#{NAME}.gemspec", "w") do |file|
53
+ file.puts spec.to_ruby
54
+ end
55
+ end
56
+
57
+ namespace :jruby do
58
+ desc "Run :package and install the resulting .gem with jruby"
59
+ task :install => :package do
60
+ sh %{#{sudo} jruby -S gem install #{install_home} pkg/#{NAME}-#{GEM_VERSION}.gem --no-rdoc --no-ri}
61
+ end
62
+ end
63
+
64
+ Spec::Rake::SpecTask.new(:default) do |t|
65
+ t.spec_opts << %w(-fs --color) << %w(-O spec/spec.opts)
66
+ t.spec_opts << '--loadby' << 'random'
67
+ t.spec_files = Dir["spec/*_spec.rb"]
68
+ t.rcov = ENV.has_key?('NO_RCOV') ? ENV['NO_RCOV'] != 'true' : true
69
+ t.rcov_opts << '--exclude' << '.gem/'
70
+
71
+ t.rcov_opts << '--text-summary'
72
+ t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
73
+ t.rcov_opts << '--only-uncovered'
74
+ end
data/TODO ADDED
@@ -0,0 +1,2 @@
1
+ TODO:
2
+ See if there's any more rich information we could provide to whoops
@@ -0,0 +1,2 @@
1
+ namespace :merb_whoops_notifier do
2
+ end
@@ -0,0 +1,17 @@
1
+ require File.expand_path(File.dirname(__FILE__)+'/whoops_notifier')
2
+
3
+ module WhoopsMixin
4
+ def notify_whoops(request=nil, session=nil)
5
+ request ||= self.request
6
+ session ||= self.session
7
+
8
+ WhoopsNotifier.notify_whoops(request, session)
9
+ end
10
+
11
+ def warn_whoops(message, request=nil, session=nil, options={})
12
+ request ||= self.request
13
+ session ||= self.session
14
+
15
+ WhoopsNotifier.warn_whoops(message, request, session, options)
16
+ end
17
+ end
@@ -0,0 +1,144 @@
1
+ require 'net/http'
2
+
3
+ module WhoopsNotifier
4
+ class << self
5
+ attr_accessor :host, :logger
6
+
7
+ def configure
8
+ key = YAML.load_file(Merb.root / 'config' / 'whoops.yml')
9
+ if key
10
+ env = key[Merb.env.to_sym]
11
+ env ? @host = env[:host] : raise(ArgumentError, "No whoops host for Merb environment #{Merb.env}")
12
+ end
13
+ end
14
+
15
+ def logger
16
+ @logger || Merb.logger
17
+ end
18
+
19
+ def environment_filters
20
+ @environment_filters ||= %w(AWS_ACCESS_KEY AWS_SECRET_ACCESS_KEY AWS_ACCOUNT SSH_AUTH_SOCK)
21
+ end
22
+
23
+ def warn_whoops(message, request, session, options={})
24
+ return if request.nil?
25
+ params = request.params
26
+
27
+ data = {
28
+ :error_class => options[:error_class] || message,
29
+ :error_message => message,
30
+ :backtrace => caller,
31
+ :environment => ENV.to_hash
32
+ }
33
+
34
+ data[:request] = {
35
+ :params => params
36
+ }
37
+
38
+ data[:environment] = clean_whoops_environment(ENV.to_hash.merge(request.env))
39
+ data[:environment][:RAILS_ENV] = Merb.env
40
+
41
+ data[:session] = {
42
+ :key => session.instance_variable_get("@session_id"),
43
+ :data => session.to_hash
44
+ }
45
+
46
+ send_to_whoops :notice => default_notice_options.merge(data)
47
+ true
48
+ end
49
+
50
+ def notify_whoops(request, session)
51
+ return if request.nil?
52
+ params = request.params
53
+
54
+ request.exceptions.each do |exception|
55
+ data = {
56
+ :error_class => Extlib::Inflection.camelize(exception.class.name),
57
+ :error_message => "#{Extlib::Inflection.camelize(exception.class.name)}: #{exception.message}",
58
+ :backtrace => exception.backtrace,
59
+ :environment => ENV.to_hash
60
+ }
61
+
62
+ data[:request] = {
63
+ :params => params
64
+ }
65
+
66
+ data[:environment] = clean_whoops_environment(ENV.to_hash.merge(request.env))
67
+ data[:environment][:RAILS_ENV] = Merb.env
68
+
69
+ data[:session] = {
70
+ :key => session.instance_variable_get("@session_id"),
71
+ :data => session.to_hash
72
+ }
73
+
74
+ send_to_whoops :notice => default_notice_options.merge(data)
75
+ end
76
+ true
77
+ end
78
+
79
+ def send_to_whoops(data) #:nodoc:
80
+ url = URI.parse(WhoopsNotifier.host)
81
+
82
+ Net::HTTP.start(url.host, url.port) do |http|
83
+ headers = {
84
+ 'Content-type' => 'application/x-yaml',
85
+ 'Accept' => 'text/xml, application/xml'
86
+ }
87
+ http.read_timeout = 5 # seconds
88
+ http.open_timeout = 2 # seconds
89
+ # http.use_ssl = WhoopsNotifier.secure
90
+ response = begin
91
+ http.post("/error_reports", clean_non_serializable_data(data).to_yaml, headers)
92
+ rescue TimeoutError => e
93
+ logger.error "Timeout while contacting the Whoops server."
94
+ nil
95
+ end
96
+ case response
97
+ when Net::HTTPSuccess then
98
+ logger.info "Whoops Success: #{response.class}"
99
+ else
100
+ logger.error "Whoops Failure: #{response.class}\n#{response.body if response.respond_to? :body}"
101
+ end
102
+ end
103
+ end
104
+
105
+ def default_notice_options #:nodoc:
106
+ {
107
+ :error_message => 'Notification',
108
+ :backtrace => nil,
109
+ :request => {},
110
+ :session => {},
111
+ :environment => {}
112
+ }
113
+ end
114
+
115
+ def clean_non_serializable_data(notice) #:nodoc:
116
+ notice.select{|k,v| serializable?(v) }.inject({}) do |h, pair|
117
+ h[pair.first] = pair.last.is_a?(Hash) ? clean_non_serializable_data(pair.last) : pair.last
118
+ h
119
+ end
120
+ end
121
+
122
+ def serializable?(value) #:nodoc:
123
+ value.is_a?(Fixnum) ||
124
+ value.is_a?(Array) ||
125
+ value.is_a?(String) ||
126
+ value.is_a?(Hash) ||
127
+ value.is_a?(Bignum)
128
+ end
129
+
130
+ def stringify_keys(hash) #:nodoc:
131
+ hash.inject({}) do |h, pair|
132
+ h[pair.first.to_s] = pair.last.is_a?(Hash) ? stringify_keys(pair.last) : pair.last
133
+ h
134
+ end
135
+ end
136
+ def clean_whoops_environment(env) #:nodoc:
137
+ env.each do |k, v|
138
+ env[k] = "[FILTERED]" if WhoopsNotifier.environment_filters.any? do |filter|
139
+ k.to_s.match(/#{filter}/)
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,10 @@
1
+ require File.expand_path(File.dirname(__FILE__)+'/merb_whoops_notifier/whoops_notifier')
2
+ require File.expand_path(File.dirname(__FILE__)+'/merb_whoops_notifier/whoops_mixin')
3
+
4
+ # make sure we're running inside Merb
5
+ if defined?(Merb::Plugins)
6
+ Merb::BootLoader.after_app_loads do
7
+ WhoopsNotifier.configure
8
+ end
9
+ Merb::Plugins.add_rakefiles "merb_whoops_notifier/merbtasks"
10
+ end
@@ -0,0 +1,9 @@
1
+ ---
2
+ :development: &defaults
3
+ :host: http://localhost:3000/
4
+
5
+ :test:
6
+ <<: *defaults
7
+
8
+ :production:
9
+ <<: *defaults
@@ -0,0 +1,113 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe "WhoopsNotifier" do
4
+ include Merb::Spec::Helpers
5
+
6
+ before(:each) do
7
+ stub(Merb).env { :production }
8
+ stub(Merb).root { Dir.tmpdir }
9
+
10
+ @http = Net::HTTP.new('localhost')
11
+ @headers = { "Content-type" => "application/x-yaml", "Accept" => "text/xml, application/xml" }
12
+ host = "http://localhost:3000/"
13
+ @config = {:development => {:host=>host}, :production => {:host=>host}, :test => {:host=>host}}
14
+ end
15
+
16
+ it "should define a constant" do
17
+ WhoopsNotifier.should_not be_nil
18
+ end
19
+
20
+ describe ".configure" do
21
+ before(:each) do
22
+ mock(YAML).load_file(File.join(Merb.root / 'config' / 'whoops.yml')) { @config }
23
+ WhoopsNotifier.configure
24
+ end
25
+ it "should know the host key after configuring" do
26
+ WhoopsNotifier.host.should == 'http://localhost:3000/'
27
+ end
28
+ end
29
+
30
+ describe ".stringify_key" do
31
+ it "should turn string keys into symbols" do
32
+ WhoopsNotifier.stringify_keys({'foo' => 'bar', :baz => 'foo', :bar => 'foo'}).should == { 'foo' => 'bar', 'baz' => 'foo', 'bar' => 'foo'}
33
+ end
34
+ end
35
+
36
+
37
+ describe "notification" do
38
+ before(:each) do
39
+ stub(Net::HTTP).new('localhost', 3000, nil, nil, nil, nil) { @http }
40
+ mock(YAML).load_file(File.join(Merb.root / 'config' / 'whoops.yml')) { @config }
41
+ WhoopsNotifier.configure
42
+ end
43
+
44
+ describe ".default_notice_options" do
45
+ it "should return sane defaults" do
46
+ WhoopsNotifier.default_notice_options.should == {
47
+ :error_message => 'Notification',
48
+ :backtrace => nil,
49
+ :request => {},
50
+ :session => {},
51
+ :environment => {}
52
+ }
53
+ end
54
+ end
55
+
56
+ describe ".notify_whoops" do
57
+ describe "bad input" do
58
+ it "should handle nil input" do
59
+ WhoopsNotifier.notify_whoops(nil, nil).should be_nil
60
+ end
61
+ end
62
+
63
+ describe "good input" do
64
+ before(:each) do
65
+ mock(WhoopsNotifier).send_to_whoops(anything).times(2) { true }
66
+ @request = setup_merb_request
67
+ mock(@request).exceptions { [RuntimeError.new('ZOMG'), RuntimeError.new('ORLY')]}
68
+ mock(@request).params { {"q"=>"0017000000SmnJ0"} }
69
+ end
70
+ it "should return true" do
71
+ WhoopsNotifier.notify_whoops(@request, {}).should be_true
72
+ end
73
+ end
74
+ end
75
+
76
+ describe ".send_to_whoops" do
77
+ describe "any 2XX response" do
78
+ before(:each) do
79
+ response = Net::HTTPOK.new('1.1', 200, 'Wazzup?')
80
+ mock(@http).post("/error_reports", "--- {}\n\n", @headers) { response }
81
+ end
82
+ it "should log success" do
83
+ mock(WhoopsNotifier.logger).info "Whoops Success: Net::HTTPOK"
84
+ WhoopsNotifier.send_to_whoops({})
85
+ end
86
+ end
87
+ describe "any non 2XX response" do
88
+ before(:each) do
89
+ response = Net::HTTPInternalServerError.new('1.1', 500, 'Upstream unavailable')
90
+ mock(response).body { 'Upstream unavailable' }
91
+
92
+ stub(@http.instance_variable_get('@socket')).closed? { true }
93
+ mock(@http).post("/error_reports", "--- {}\n\n", @headers) { response }
94
+ end
95
+ it "should log failure" do
96
+ mock(WhoopsNotifier.logger).error "Whoops Failure: Net::HTTPInternalServerError\nUpstream unavailable"
97
+ WhoopsNotifier.send_to_whoops({})
98
+ end
99
+ end
100
+ describe "a timeout exception is thrown" do
101
+ before(:each) do
102
+ response = TimeoutError.new("It took too fucking long")
103
+ mock(@http).post("/error_reports", "--- {}\n\n", @headers) { raise response }
104
+ end
105
+ it "should log failure" do
106
+ mock(WhoopsNotifier.logger).error("Whoops Failure: NilClass\n")
107
+ mock(WhoopsNotifier.logger).error("Timeout while contacting the Whoops server.")
108
+ WhoopsNotifier.send_to_whoops({})
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
data/spec/spec.opts ADDED
File without changes
@@ -0,0 +1,33 @@
1
+ $TESTING=true
2
+ $:.push File.join(File.dirname(__FILE__), '..', 'lib')
3
+ require 'rr'
4
+ require 'merb-core'
5
+ require 'merb_whoops_notifier'
6
+ require 'tmpdir'
7
+ require 'pp'
8
+
9
+ Spec::Runner.configure do |config|
10
+ config.mock_with :rr
11
+ end
12
+
13
+ module Merb
14
+ module Spec
15
+ module Helpers
16
+ def setup_merb_request
17
+ rack_env = {"SERVER_NAME"=>"localhost",
18
+ "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",
19
+ "HTTP_ACCEPT_ENCODING"=>"gzip, deflate", "PATH_INFO"=>"/search/", "HTTP_CACHE_CONTROL"=>"max-age=0",
20
+ "HTTP_ACCEPT_LANGUAGE"=>"en-us", "HTTP_HOST"=>"localhost:4000", "SERVER_PROTOCOL"=>"HTTP/1.1", "SCRIPT_NAME"=>"",
21
+ "REQUEST_PATH"=>"/search/", "SERVER_SOFTWARE"=>"Mongrel 1.1.5", "REMOTE_ADDR"=>"127.0.0.1", "rack.streaming"=>true,
22
+ "rack.version"=>[0, 1], "rack.multithread"=>true, "HTTP_VERSION"=>"HTTP/1.1", "rack.multiprocess"=>false,
23
+ "REQUEST_URI"=>"/search/?q=0017000000SmnJ0", "SERVER_PORT"=>"4000", "QUERY_STRING"=>"q=0017000000SmnJ0",
24
+ "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",
25
+ "REQUEST_METHOD"=>"GET", "HTTP_CONNECTION"=>"keep-alive"}
26
+
27
+ request = Merb::Request.new(rack_env)
28
+ stub(request).env { rack_env }
29
+ request
30
+ end
31
+ end
32
+ end
33
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: flyingmachine-merb_whoops_notifier
3
+ version: !ruby/object:Gem::Version
4
+ version: "1.0"
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Higginbotham
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-04-03 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: merb-core
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.0.9
24
+ version:
25
+ description: Merb plugin that provides whoops exception notification
26
+ email: daniel@flyingmachinestudios.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - README
33
+ - LICENSE
34
+ - TODO
35
+ files:
36
+ - LICENSE
37
+ - README
38
+ - Rakefile
39
+ - TODO
40
+ - lib/merb_whoops_notifier
41
+ - lib/merb_whoops_notifier/merbtasks.rb
42
+ - lib/merb_whoops_notifier/whoops_mixin.rb
43
+ - lib/merb_whoops_notifier/whoops_notifier.rb
44
+ - lib/merb_whoops_notifier.rb
45
+ - spec/fixtures
46
+ - spec/fixtures/whoops.yml
47
+ - spec/merb_whoops_notifier_spec.rb
48
+ - spec/spec.opts
49
+ - spec/spec_helper.rb
50
+ has_rdoc: true
51
+ homepage: http://github.com/flyingmachine/merb_whoops_notifier
52
+ post_install_message:
53
+ rdoc_options: []
54
+
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: "0"
62
+ version:
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
68
+ version:
69
+ requirements: []
70
+
71
+ rubyforge_project: merb
72
+ rubygems_version: 1.2.0
73
+ signing_key:
74
+ specification_version: 2
75
+ summary: Merb plugin that provides whoops exception notification
76
+ test_files: []
77
+