atmos-merb_exceptions 0.9.4

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 New Bamboo
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.
@@ -0,0 +1,117 @@
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
+ Install gem from github
87
+
88
+ sudo gem install newbamboo-merb_exceptions --source=http://gems.github.com
89
+
90
+ or install from source with
91
+
92
+ rake install_gem
93
+
94
+ Licence
95
+ -------
96
+ (The MIT License)
97
+
98
+ Copyright (c) 2008 New Bamboo
99
+
100
+ Permission is hereby granted, free of charge, to any person obtaining
101
+ a copy of this software and associated documentation files (the
102
+ 'Software'), to deal in the Software without restriction, including
103
+ without limitation the rights to use, copy, modify, merge, publish,
104
+ distribute, sublicense, and/or sell copies of the Software, and to
105
+ permit persons to whom the Software is furnished to do so, subject to
106
+ the following conditions:
107
+
108
+ The above copyright notice and this permission notice shall be
109
+ included in all copies or substantial portions of the Software.
110
+
111
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
112
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
113
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
114
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
115
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
116
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
117
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,44 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+
4
+ PLUGIN = "merb_exceptions"
5
+ NAME = "merb_exceptions"
6
+ VERSION = "0.9.4"
7
+ AUTHOR = 'Andy Kent'
8
+ EMAIL = "andy@new-bamboo.co.uk"
9
+ HOMEPAGE = "http://merb-plugins.rubyforge.org/me/"
10
+ SUMMARY = "Allows Merb to forward exceptions to emails or web hooks"
11
+
12
+ spec = Gem::Specification.new do |s|
13
+ s.name = NAME
14
+ s.version = VERSION
15
+ s.platform = Gem::Platform::RUBY
16
+ s.has_rdoc = true
17
+ s.extra_rdoc_files = ["LICENSE", 'TODO']
18
+ s.summary = SUMMARY
19
+ s.description = s.summary
20
+ s.author = AUTHOR
21
+ s.email = EMAIL
22
+ s.homepage = HOMEPAGE
23
+ s.add_dependency('merb', '>= 0.9.4')
24
+ s.require_path = 'lib'
25
+ s.autorequire = PLUGIN
26
+ s.files = %w(LICENSE README.markdown Rakefile TODO) + Dir.glob("{lib,spec}/**/*")
27
+ end
28
+
29
+ Rake::GemPackageTask.new(spec) do |pkg|
30
+ pkg.gem_spec = spec
31
+ end
32
+
33
+ task :install => [:package] do
34
+ sh %{sudo gem install pkg/#{NAME}-#{VERSION} --no-update-sources}
35
+ end
36
+
37
+ namespace :jruby do
38
+
39
+ desc "Run :package and install the resulting .gem with jruby"
40
+ task :install => :package do
41
+ sh %{#{SUDO} jruby -S gem install pkg/#{NAME}-#{Merb::VERSION}.gem --no-rdoc --no-ri}
42
+ end
43
+
44
+ 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,30 @@
1
+ module MerbExceptions
2
+ module ControllerExtensions
3
+ module InstanceMethods
4
+ def internal_server_error
5
+ self.render_and_notify :layout=>false
6
+ end
7
+
8
+ def render_and_notify(opts={})
9
+ self.render_then_call(render(opts)) { notify_of_exceptions }
10
+ end
11
+
12
+ def notify_of_exceptions
13
+ exception = self.params[:exception]
14
+ request = self.request
15
+ orig_params = self.params[:original_params]
16
+ details = {}
17
+ details['exception'] = exception
18
+ details['params'] = orig_params
19
+ details['environment'] = request.env.merge( 'process' => $$ )
20
+ details['url'] = "#{request.protocol}#{request.env["HTTP_HOST"]}#{request.uri}"
21
+ MerbExceptions::Notification.new(details).deliver!
22
+ end
23
+ end
24
+
25
+ def self.included(receiver)
26
+ receiver.send :include, InstanceMethods
27
+ receiver.show_action(:internal_server_error)
28
+ end
29
+ end
30
+ 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[: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 = 9
5
+ TINY = 4
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
@@ -0,0 +1,47 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+ require 'merb-core'
4
+ require "merb-core/test"
5
+ require File.dirname(__FILE__) + '/../lib/merb_exceptions'
6
+
7
+ # Mock out Merb bits that a re required for testing
8
+ module Merb
9
+ #
10
+ class Logger
11
+ def info(msg=''); msg; end
12
+ end
13
+ #
14
+ class << self
15
+ def env; 'production'; end
16
+ def logger; Logger.new(File.expand_path(File.dirname(__FILE__)+'/../test.log')); end
17
+ end
18
+ end
19
+ #
20
+ module NotificationSpecHelper
21
+ def mock_details(opts={})
22
+ {
23
+ 'exception' => {},
24
+ 'params' => { :controller=>'errors', :action=>'show' },
25
+ 'environment' => { 'key1'=>'value1', 'key2'=>'value2' },
26
+ 'url' => 'http://www.my-app.com/errors/1'
27
+ }.merge(opts)
28
+ end
29
+
30
+ def default_config
31
+ {
32
+ :web_hooks => [],
33
+ :email_addresses => [],
34
+ :app_name => "Merb Application",
35
+ :environments => ['production'],
36
+ :email_from => "exceptions@app.com"
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,97 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ include MerbExceptions
4
+ include NotificationSpecHelper
5
+
6
+ module Merb
7
+ module Plugins
8
+ def self.config; NotificationSpecHelper.merb_config || {}; end
9
+ end
10
+ end
11
+
12
+ describe MerbExceptions::Notification do
13
+ describe "#new" do
14
+ it "should create a new notification without errors" do
15
+ lambda { Notification.new(mock_details) }.should_not raise_error
16
+ end
17
+
18
+ it "should set the detail values to those provided" do
19
+ Notification.new(mock_details).details.should == mock_details
20
+ end
21
+
22
+ it "should set config defaults when values aren't provided" do
23
+ config = Notification.new(mock_details).instance_variable_get(:@config)
24
+ config.should == default_config
25
+ end
26
+
27
+ it "should allow overriding of default configuration options through the Merb::Plugins.config hash" do
28
+ opts = { :app_name=>'Testy Mc Testerson', :email_addresses=>['test1@test.com', 'test2@test.com'] }
29
+ mock_merb_config(opts)
30
+ config = Notification.new(mock_details).instance_variable_get(:@config)
31
+ config.should == default_config.merge(opts)
32
+ end
33
+ end
34
+
35
+ describe ".deliver!" do
36
+ before :each do
37
+ @notification = Notification.new(mock_details)
38
+ @notification.stub!('deliver_emails!')
39
+ @notification.stub!('deliver_web_hooks!')
40
+ end
41
+
42
+ it "should deliver web hooks" do
43
+ @notification.should_receive('deliver_web_hooks!')
44
+ @notification.deliver!
45
+ end
46
+
47
+ it "should deliver emails" do
48
+ @notification.should_receive('deliver_emails!')
49
+ @notification.deliver!
50
+ end
51
+ end
52
+
53
+ describe ".deliver_web_hooks!" do
54
+ before :each do
55
+ mock_merb_config({ :web_hooks => ['http://www.test1.com', 'http://www.test2.com'] })
56
+ @notification = Notification.new(mock_details)
57
+ @notification.stub!(:post_hook)
58
+ end
59
+
60
+ it "should call post_hook for each url" do
61
+ @notification.should_receive(:post_hook).once.with('http://www.test1.com')
62
+ @notification.should_receive(:post_hook).once.with('http://www.test2.com')
63
+ @notification.deliver_web_hooks!
64
+ end
65
+ end
66
+
67
+ describe ".deliver_emails!" do
68
+ before :each do
69
+ mock_merb_config({ :email_addresses => ['user1@test.com', 'user2@test.com'] })
70
+ @notification = Notification.new(mock_details)
71
+ @notification.stub!(:send_notification_email)
72
+ end
73
+
74
+ it "should call send_notification_email for each address" do
75
+ @notification.should_receive(:send_notification_email).once.with('user1@test.com')
76
+ @notification.should_receive(:send_notification_email).once.with('user2@test.com')
77
+ @notification.deliver_emails!
78
+ end
79
+ end
80
+
81
+ describe ".should_deliver_notifications?" do
82
+ it "should return true if the current environment is on the config[:environments] list of one item" do
83
+ mock_merb_config({ :environments => 'production' })
84
+ Notification.new(mock_details).should_deliver_notifications?.should be_true
85
+ end
86
+
87
+ it "should return true if the current environment is on the config[:environments] list as an array" do
88
+ mock_merb_config({ :environments => ['staging', 'production'] })
89
+ Notification.new(mock_details).should_deliver_notifications?.should be_true
90
+ end
91
+
92
+ it "should return false if the current environment is not on the config[:environments] list" do
93
+ mock_merb_config({ :environments => ['staging', 'development'] })
94
+ Notification.new(mock_details).should_deliver_notifications?.should be_false
95
+ end
96
+ end
97
+ end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: atmos-merb_exceptions
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.4
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
+ - LICENSE
25
+ files:
26
+ - LICENSE
27
+ - README.markdown
28
+ - Rakefile
29
+ - lib/merb_exceptions.rb
30
+ - lib/merb_exceptions/controller_extensions.rb
31
+ - lib/merb_exceptions/notification.rb
32
+ - lib/merb_exceptions/templates/email.erb
33
+ - lib/merb_exceptions/version.rb
34
+ - spec/spec_helper.rb
35
+ - spec/unit/notification_spec.rb
36
+ has_rdoc: true
37
+ homepage: http://github.com/newbamboo/merb_exceptions/
38
+ post_install_message:
39
+ rdoc_options:
40
+ - --main
41
+ - README.markdown
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: "0"
49
+ version:
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ version:
56
+ requirements: []
57
+
58
+ rubyforge_project:
59
+ rubygems_version: 1.0.1
60
+ signing_key:
61
+ specification_version: 2
62
+ summary: Allows Merb to forward exceptions to emails or web hooks
63
+ test_files:
64
+ - spec/unit/notification_spec.rb