newbamboo-merb_exceptions 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ == 0.0.1 2008-05-01
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
data/License.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 FIXME full name
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,111 @@
1
+ merb_exceptions
2
+ ===============
3
+ A simple Merb plugin to ease exception notifications.
4
+
5
+ The notifier currently supports two interfaces, Email Alerts and Web Hooks. Emails are formatted as plain text and sent using your Merb environment's mail settings. Web hooks as sent as post requests.
6
+
7
+ Instead of a messy port of a rails plugin this is a complete rewrite from scratch and so is able to take full advantage of Merb's exception handling functionality.
8
+
9
+ Getting Going
10
+ -------------
11
+ Once you have the Gem installed you will need to add it as a dependency in your projects `init.rb` file
12
+
13
+ dependency 'merb_exceptions'
14
+
15
+ Configuration goes in `config/plugins.yml` file (which you may need to create). See 'Settings' below for a full description of the options.
16
+
17
+ :exceptions:
18
+ :app_name: My App Name
19
+ :email_from: exceptions@myapp.com
20
+ :web_hooks: http://localhost:4000/exceptions
21
+ :email_addresses:
22
+ - user@myapp.com
23
+ - hello@exceptions.com
24
+ :environments:
25
+ - staging
26
+ - production
27
+
28
+ The plugin doesn't modify any existing code or do anything particularly magical so you will need to explicitly include it. Just include this module in your `ExceptionsController`.
29
+
30
+ include MerbExceptions::ControllerExtensions
31
+
32
+ If you have specified any email addresses and are not already requiring merb-mailer then you need to do so. It also needs configuration.
33
+
34
+ dependency 'merb-mailer'
35
+
36
+ Settings
37
+ --------
38
+ `web_hooks`, `email_addresses`, and `environments` can either be a single string or an array of strings.
39
+
40
+ `app_name`: Used to customise emails (default "My App")
41
+
42
+ `email_from`: Exceptions are sent from this address
43
+
44
+ `web_hooks`: Each url is sent a post request. See 'Web Hooks' for more info.
45
+
46
+ `email_addresses`: Each email address is sent an exception notification using Merb's built in mailer settings.
47
+
48
+ `environments`: Notifications will only be sent for environments in this list, defaults to `production`
49
+
50
+ Advanced usage
51
+ --------------
52
+ Including `MerbExceptions::ControllerExtensions` creates an `internal_server_error` action which renders the default exception page and delivers the exception. If you need to rescue any other exceptions or customize the behavior in any way you can write your own actions in `ExceptionsController` and make a call to `render_and_notify`.
53
+
54
+ For example to be notified of 404's:
55
+
56
+ def not_found
57
+ render_and_notify :format => :html
58
+ end
59
+
60
+ `render_and_notify` - passes any provided options directly to Merb's render method and then sends the notification after rendering.
61
+
62
+ `notify_of_exceptions` - if you need to handle the render yourself for some reason then you can call this method directly. It sends notifications without any rendering logic. Note though that if you are sending lots of notifications this could delay sending a response back to the user so try to avoid using it where possible.
63
+
64
+ Web hooks
65
+ ---------
66
+ Web hooks are a great way to push your data beyond your app to the outside world. For each address on your `web_hooks` list we will send a HTTP:POST request with the following parameters for you to consume.
67
+
68
+ `request_url`
69
+ `request_controller`
70
+ `request_action`
71
+ `request_params`
72
+ `request_status_code`
73
+ `exception_name`
74
+ `exception_message`
75
+ `exception_backtrace`
76
+ `merb_exception_class`
77
+ `original_exception_class`
78
+ `environment`
79
+
80
+ Requirements
81
+ ------------
82
+ * Edge Merb
83
+
84
+ Install
85
+ -------
86
+ * `rake install_gem`
87
+
88
+ Licence
89
+ -------
90
+ (The MIT License)
91
+
92
+ Copyright (c) 2008 New Bamboo
93
+
94
+ Permission is hereby granted, free of charge, to any person obtaining
95
+ a copy of this software and associated documentation files (the
96
+ 'Software'), to deal in the Software without restriction, including
97
+ without limitation the rights to use, copy, modify, merge, publish,
98
+ distribute, sublicense, and/or sell copies of the Software, and to
99
+ permit persons to whom the Software is furnished to do so, subject to
100
+ the following conditions:
101
+
102
+ The above copyright notice and this permission notice shall be
103
+ included in all copies or substantial portions of the Software.
104
+
105
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
106
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
107
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
108
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
109
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
110
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
111
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require 'config/requirements'
2
+ require 'config/hoe' # setup Hoe + all gem configuration
3
+ require 'spec/rake/spectask'
4
+
5
+ Dir['tasks/**/*.rake'].each { |rake| load rake }
6
+
7
+ Spec::Rake::SpecTask.new('spec') do |t|
8
+ t.spec_opts << '--format' << 'specdoc' << '--colour'
9
+ t.spec_opts << '--loadby' << 'random'
10
+ t.spec_files = Dir["spec/**/*_spec.rb"]
11
+ end
@@ -0,0 +1,13 @@
1
+ unless defined?(Merb::Plugins)
2
+ raise %q{merb_exceptions says, "Something's not right, bub. You should run me inside Merb."}
3
+ end
4
+
5
+ $:.unshift(File.dirname(__FILE__)) unless
6
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
7
+
8
+ require "merb_exceptions/notification"
9
+ require 'merb_exceptions/controller_extensions'
10
+
11
+ module MerbExceptions
12
+
13
+ end
@@ -0,0 +1,32 @@
1
+ module MerbExceptions
2
+ module ControllerExtensions
3
+
4
+ def self.included(mod)
5
+ mod.class_eval do
6
+ def internal_server_error
7
+ # These two variables are required by the default exception template
8
+ @exception = self.params[:exception]
9
+ @exception_name = @exception.name.split("_").map {|x| x.capitalize}.join(" ")
10
+ self.render_and_notify :layout=>false
11
+ end
12
+ end
13
+ end
14
+
15
+
16
+ def render_and_notify(opts={})
17
+ self.render_then_call(render(opts)) { notify_of_exceptions }
18
+ end
19
+
20
+ def notify_of_exceptions
21
+ exception = self.params[:exception]
22
+ request = self.request
23
+ orig_params = self.params[:original_params]
24
+ details = {}
25
+ details['exception'] = exception
26
+ details['params'] = orig_params
27
+ details['environment'] = request.env.merge( 'process' => $$ )
28
+ details['url'] = "#{request.protocol}#{request.env["HTTP_HOST"]}#{request.uri}"
29
+ MerbExceptions::Notification.new(details).deliver!
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,122 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+ require "erb"
4
+
5
+ module MerbExceptions
6
+ class Notification
7
+ attr_reader :details
8
+
9
+ def initialize(details = {})
10
+ @details = details
11
+ @config = {
12
+ :web_hooks => [],
13
+ :email_addresses => [],
14
+ :app_name => "Merb Application",
15
+ :email_from => "exceptions@app.com",
16
+ :environments => ['production']
17
+ }.merge(Merb::Plugins.config[:exceptions] || {})
18
+ end
19
+
20
+ def deliver!
21
+ deliver_web_hooks!
22
+ deliver_emails!
23
+ end
24
+
25
+ def deliver_web_hooks!
26
+ return unless should_deliver_notifications?
27
+ Merb.logger.info "DELIVERING EXCEPTION WEB HOOKS"
28
+ web_hooks.each do |address|
29
+ post_hook(address)
30
+ end
31
+ end
32
+
33
+ def deliver_emails!
34
+ return unless should_deliver_notifications?
35
+ Merb.logger.info "DELIVERING EXCEPTION EMAILS"
36
+ email_addresses.each do |address|
37
+ send_notification_email(address)
38
+ end
39
+ end
40
+
41
+ def web_hooks; option_as_array(:web_hooks); end
42
+
43
+ def email_addresses; option_as_array(:email_addresses); end
44
+
45
+ def environments; option_as_array(:environments); end
46
+
47
+ def exception
48
+ @details['exception']
49
+ end
50
+
51
+ def should_deliver_notifications?
52
+ environments.include? Merb.env
53
+ end
54
+
55
+ # this is a horrid hack because I can't find any easy way to get the exceptions class out of Merb
56
+ # I tried .exception but this seems to recursivly give you Merb exceptions not the original
57
+ def original_exception_class
58
+ exception.to_yaml.match(/^exception: !ruby\/exception:(.+)/)[1] rescue 'N/A'
59
+ end
60
+
61
+ def params
62
+ {
63
+ 'request_url' => details['url'],
64
+ 'request_controller' => details['params'][:controller],
65
+ 'request_action' => details['params'][:action],
66
+ 'request_params' => details['params'],
67
+ 'request_status_code' => exception.class::STATUS,
68
+ 'exception_name' => exception.name,
69
+ 'exception_message' => exception.message,
70
+ 'exception_backtrace' => (exception.backtrace or []).join("\n"),
71
+ 'merb_exception_class' => exception.class,
72
+ 'original_exception_class' => original_exception_class,
73
+ 'environment' => details['environment']
74
+
75
+ }
76
+ end
77
+
78
+ private
79
+
80
+ def post_hook(address)
81
+ Merb.logger.info "- hooking to #{address}"
82
+ uri = URI.parse(address)
83
+ uri.path = '/' if uri.path=='' # set a path if one isn't provided to keep Net::HTTP happy
84
+ Net::HTTP.post_form( uri, params ).body
85
+ end
86
+
87
+ def send_email(address, body)
88
+ Merb.logger.info "- emailing to #{address}"
89
+ email = Merb::Mailer.new({
90
+ :to => address,
91
+ :from => "#{@config[:app_name]} <#{@config[:email_from]}>",
92
+ :subject => "[#{@config[:app_name]} EXCEPTION] #{exception.message}",
93
+ :text => body
94
+ })
95
+ email.deliver!
96
+ end
97
+
98
+ def send_notification_email(address)
99
+ send_email(address, plain_text_mail_body)
100
+ end
101
+
102
+ def plain_text_mail_body
103
+ path = File.join(File.dirname(__FILE__), 'templates', 'email.erb')
104
+ template = ERB.new File.open(path,'r') { |f| f.read }
105
+ template.result(binding)
106
+ end
107
+
108
+ # Used so that we can accept either a single value or array (e.g. of
109
+ # webhooks) in our YAML file.
110
+ def option_as_array(option)
111
+ value = @config[option]
112
+ case value
113
+ when Array
114
+ value.reject { |e| e.nil? } # Don't accept nil values
115
+ when String
116
+ [value]
117
+ else
118
+ []
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,34 @@
1
+ Woops! Sorry but it looks like something went wrong.
2
+ With any luck the details below will aid you in solving the problem.
3
+
4
+
5
+ REQUEST
6
+ --------
7
+ URL: <%= params['request_url'] %>
8
+ Controller: <%= params['request_controller'] %>
9
+ Action: <%= params['request_action'] %>
10
+ Status Code: <%= params['request_status_code'] %>
11
+
12
+
13
+ EXCEPTION
14
+ ----------
15
+ Name: <%= params['exception_name'] %>
16
+ Merb Exception Class: <%= params['merb_exception_class'] %>
17
+ Original Exception Class: <%= params['original_exception_class'] %>
18
+ message: <%= params['exception_message'] %>
19
+
20
+
21
+ PARAMETERS
22
+ -----------
23
+ <% params['request_params'].each do |key,value| %><%= key %>: <%= value %>
24
+ <% end %>
25
+
26
+ BACKTRACE
27
+ ----------
28
+ <%= params['exception_backtrace'] %>
29
+
30
+
31
+ ENVIRONMENT
32
+ ------------
33
+ <% params['environment'].each do |key,value| %><%= key %>: <%= value %>
34
+ <% end %>
@@ -0,0 +1,9 @@
1
+ module MerbExceptions #:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 1
5
+ TINY = 0
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
@@ -0,0 +1,47 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+ require File.dirname(__FILE__) + '/../lib/merb_exceptions'
4
+
5
+ # Mock out Merb bits that a re required for testing
6
+ module Merb
7
+ class Plugins
8
+ def self.config; NotificationSpecHelper.merb_config || {}; end
9
+ end
10
+
11
+ class Logger
12
+ def info(msg=''); msg; end
13
+ end
14
+
15
+ class << self
16
+ def env; 'production'; end
17
+ def logger; Logger.new; end
18
+ end
19
+ end
20
+
21
+ module NotificationSpecHelper
22
+ def mock_details(opts={})
23
+ {
24
+ 'exception' => {},
25
+ 'params' => { :controller=>'errors', :action=>'show' },
26
+ 'environment' => { 'key1'=>'value1', 'key2'=>'value2' },
27
+ 'url' => 'http://www.my-app.com/errors/1'
28
+ }.merge(opts)
29
+ end
30
+
31
+ def default_config
32
+ {
33
+ :web_hooks => [],
34
+ :email_addresses => [],
35
+ :app_name => "Merb Application",
36
+ :environments => ['production']
37
+ }
38
+ end
39
+
40
+ def mock_merb_config(opts={})
41
+ NotificationSpecHelper.merb_config = {:exceptions => opts}
42
+ end
43
+
44
+ class << self
45
+ attr_accessor :merb_config
46
+ end
47
+ end
@@ -0,0 +1,91 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ include MerbExceptions
4
+ include NotificationSpecHelper
5
+
6
+ describe MerbExceptions::Notification do
7
+ describe "#new" do
8
+ it "should create a new notification without errors" do
9
+ lambda { Notification.new(mock_details) }.should_not raise_error
10
+ end
11
+
12
+ it "should set the detail values to those provided" do
13
+ Notification.new(mock_details).details.should == mock_details
14
+ end
15
+
16
+ it "should set config defaults when values aren't provided" do
17
+ config = Notification.new(mock_details).instance_variable_get(:@config)
18
+ config.should == default_config
19
+ end
20
+
21
+ it "should allow overriding of default configuration options through the Merb::Plugins.config hash" do
22
+ opts = { :app_name=>'Testy Mc Testerson', :email_addresses=>['test1@test.com', 'test2@test.com'] }
23
+ mock_merb_config(opts)
24
+ config = Notification.new(mock_details).instance_variable_get(:@config)
25
+ config.should == default_config.merge(opts)
26
+ end
27
+ end
28
+
29
+ describe ".deliver!" do
30
+ before :each do
31
+ @notification = Notification.new(mock_details)
32
+ @notification.stub!('deliver_emails!')
33
+ @notification.stub!('deliver_web_hooks!')
34
+ end
35
+
36
+ it "should deliver web hooks" do
37
+ @notification.should_receive('deliver_web_hooks!')
38
+ @notification.deliver!
39
+ end
40
+
41
+ it "should deliver emails" do
42
+ @notification.should_receive('deliver_emails!')
43
+ @notification.deliver!
44
+ end
45
+ end
46
+
47
+ describe ".deliver_web_hooks!" do
48
+ before :each do
49
+ mock_merb_config({ :web_hooks => ['http://www.test1.com', 'http://www.test2.com'] })
50
+ @notification = Notification.new(mock_details)
51
+ @notification.stub!(:post_hook)
52
+ end
53
+
54
+ it "should call post_hook for each url" do
55
+ @notification.should_receive(:post_hook).once.with('http://www.test1.com')
56
+ @notification.should_receive(:post_hook).once.with('http://www.test2.com')
57
+ @notification.deliver_web_hooks!
58
+ end
59
+ end
60
+
61
+ describe ".deliver_emails!" do
62
+ before :each do
63
+ mock_merb_config({ :email_addresses => ['user1@test.com', 'user2@test.com'] })
64
+ @notification = Notification.new(mock_details)
65
+ @notification.stub!(:send_notification_email)
66
+ end
67
+
68
+ it "should call send_notification_email for each address" do
69
+ @notification.should_receive(:send_notification_email).once.with('user1@test.com')
70
+ @notification.should_receive(:send_notification_email).once.with('user2@test.com')
71
+ @notification.deliver_emails!
72
+ end
73
+ end
74
+
75
+ describe ".should_deliver_notifications?" do
76
+ it "should return true if the current environment is on the config[:environments] list of one item" do
77
+ mock_merb_config({ :environments => 'production' })
78
+ Notification.new(mock_details).should_deliver_notifications?.should be_true
79
+ end
80
+
81
+ it "should return true if the current environment is on the config[:environments] list as an array" do
82
+ mock_merb_config({ :environments => ['staging', 'production'] })
83
+ Notification.new(mock_details).should_deliver_notifications?.should be_true
84
+ end
85
+
86
+ it "should return false if the current environment is not on the config[:environments] list" do
87
+ mock_merb_config({ :environments => ['staging', 'development'] })
88
+ Notification.new(mock_details).should_deliver_notifications?.should be_false
89
+ end
90
+ end
91
+ end
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: newbamboo-merb_exceptions
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Andy Kent
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-05-12 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Allows Merb to forward exceptions to emails or web hooks
17
+ email:
18
+ - andy@new-bamboo.co.uk
19
+ executables: []
20
+
21
+ extensions: []
22
+
23
+ extra_rdoc_files:
24
+ - History.txt
25
+ - License.txt
26
+ files:
27
+ - History.txt
28
+ - License.txt
29
+ - README.markdown
30
+ - Rakefile
31
+ - lib/merb_exceptions.rb
32
+ - lib/merb_exceptions/controller_extensions.rb
33
+ - lib/merb_exceptions/notification.rb
34
+ - lib/merb_exceptions/templates/email.erb
35
+ - lib/merb_exceptions/version.rb
36
+ - spec/spec_helper.rb
37
+ - spec/unit/notification_spec.rb
38
+ has_rdoc: true
39
+ homepage: http://github.com/newbamboo/merb_exceptions/
40
+ post_install_message:
41
+ rdoc_options:
42
+ - --main
43
+ - README.markdown
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: "0"
51
+ version:
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ version:
58
+ requirements: []
59
+
60
+ rubyforge_project:
61
+ rubygems_version: 1.0.1
62
+ signing_key:
63
+ specification_version: 2
64
+ summary: Allows Merb to forward exceptions to emails or web hooks
65
+ test_files:
66
+ - spec/unit/notification_spec.rb