averell-exception_notification 1.0.20100224

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,112 @@
1
+ = Exception Notifier Plugin for Rails
2
+
3
+ The Exception Notifier plugin provides a mailer object and a default set of
4
+ templates for sending email notifications when errors occur in a Rails
5
+ application. The plugin is configurable, allowing programmers to specify:
6
+
7
+ * the sender address of the email
8
+ * the recipient addresses
9
+ * the text used to prefix the subject line
10
+
11
+ The email includes information about the current request, session, and
12
+ environment, and also gives a backtrace of the exception.
13
+
14
+ == Usage
15
+
16
+ First, include the ExceptionNotifiable mixin in whichever controller you want
17
+ to generate error emails (typically ApplicationController):
18
+
19
+ class ApplicationController < ActionController::Base
20
+ include ExceptionNotifiable
21
+ ...
22
+ end
23
+
24
+ Then, specify the email recipients in your environment:
25
+
26
+ ExceptionNotifier.exception_recipients = %w(joe@schmoe.com bill@schmoe.com)
27
+
28
+ And that's it! The defaults take care of the rest.
29
+
30
+ == Configuration
31
+
32
+ You can tweak other values to your liking, as well. In your environment file,
33
+ just set any or all of the following values:
34
+
35
+ # defaults to exception.notifier@default.com
36
+ ExceptionNotifier.sender_address =
37
+ %("Application Error" <app.error@myapp.com>)
38
+
39
+ # defaults to "[ERROR] "
40
+ ExceptionNotifier.email_prefix = "[APP] "
41
+
42
+ Email notifications will only occur when the IP address is determined not to
43
+ be local. You can specify certain addresses to always be local so that you'll
44
+ get a detailed error instead of the generic error page. You do this in your
45
+ controller (or even per-controller):
46
+
47
+ consider_local "64.72.18.143", "14.17.21.25"
48
+
49
+ You can specify subnet masks as well, so that all matching addresses are
50
+ considered local:
51
+
52
+ consider_local "64.72.18.143/24"
53
+
54
+ The address "127.0.0.1" is always considered local. If you want to completely
55
+ reset the list of all addresses (for instance, if you wanted "127.0.0.1" to
56
+ NOT be considered local), you can simply do, somewhere in your controller:
57
+
58
+ local_addresses.clear
59
+
60
+ == Customization
61
+
62
+ By default, the notification email includes four parts: request, session,
63
+ environment, and backtrace (in that order). You can customize how each of those
64
+ sections are rendered by placing a partial named for that part in your
65
+ app/views/exception_notifier directory (e.g., _session.rhtml). Each partial has
66
+ access to the following variables:
67
+
68
+ * @controller: the controller that caused the error
69
+ * @request: the current request object
70
+ * @exception: the exception that was raised
71
+ * @host: the name of the host that made the request
72
+ * @backtrace: a sanitized version of the exception's backtrace
73
+ * @rails_root: a sanitized version of RAILS_ROOT
74
+ * @data: a hash of optional data values that were passed to the notifier
75
+ * @sections: the array of sections to include in the email
76
+
77
+ You can reorder the sections, or exclude sections completely, by altering the
78
+ ExceptionNotifier.sections variable. You can even add new sections that
79
+ describe application-specific data--just add the section's name to the list
80
+ (whereever you'd like), and define the corresponding partial. Then, if your
81
+ new section requires information that isn't available by default, make sure
82
+ it is made available to the email using the exception_data macro:
83
+
84
+ class ApplicationController < ActionController::Base
85
+ ...
86
+ protected
87
+ exception_data :additional_data
88
+
89
+ def additional_data
90
+ { :document => @document,
91
+ :person => @person }
92
+ end
93
+ ...
94
+ end
95
+
96
+ In the above case, @document and @person would be made available to the email
97
+ renderer, allowing your new section(s) to access and display them. See the
98
+ existing sections defined by the plugin for examples of how to write your own.
99
+
100
+ == Advanced Customization
101
+
102
+ By default, the email notifier will only notify on critical errors. For
103
+ ActiveRecord::RecordNotFound and ActionController::UnknownAction, it will
104
+ simply render the contents of your public/404.html file. Other exceptions
105
+ will render public/500.html and will send the email notification. If you want
106
+ to use different rules for the notification, you will need to implement your
107
+ own rescue_action_in_public method. You can look at the default implementation
108
+ in ExceptionNotifiable for an example of how to go about that.
109
+
110
+
111
+ Copyright (c) 2005 Jamis Buck, released under the MIT license
112
+ Copyright (c) 2008-2010 Jeremy Evans
@@ -0,0 +1,99 @@
1
+ require 'ipaddr'
2
+
3
+ # Copyright (c) 2005 Jamis Buck
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+ module ExceptionNotifiable
24
+ def self.included(target)
25
+ target.extend(ClassMethods)
26
+ end
27
+
28
+ module ClassMethods
29
+ def consider_local(*args)
30
+ local_addresses.concat(args.flatten.map { |a| IPAddr.new(a) })
31
+ end
32
+
33
+ def local_addresses
34
+ addresses = read_inheritable_attribute(:local_addresses)
35
+ unless addresses
36
+ addresses = [IPAddr.new("127.0.0.1")]
37
+ write_inheritable_attribute(:local_addresses, addresses)
38
+ end
39
+ addresses
40
+ end
41
+
42
+ def exception_data(deliverer=self)
43
+ if deliverer == self
44
+ read_inheritable_attribute(:exception_data)
45
+ else
46
+ write_inheritable_attribute(:exception_data, deliverer)
47
+ end
48
+ end
49
+
50
+ def exceptions_to_treat_as_404
51
+ exceptions = [ActionController::UnknownController, ActionController::UnknownAction]
52
+ exceptions << ActionController::UnknownHttpMethod if ActionController.const_defined?(:UnknownHttpMethod)
53
+ exceptions << ActionController::RoutingError if ActionController.const_defined?(:RoutingError)
54
+ exceptions << ActiveRecord::RecordNotFound if Object.const_defined?(:ActiveRecord)
55
+ exceptions
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def local_request?
62
+ remote = IPAddr.new(request.remote_ip)
63
+ !self.class.local_addresses.detect { |addr| addr.include?(remote) }.nil?
64
+ end
65
+
66
+ def render_404
67
+ respond_to do |type|
68
+ type.html { render :file => "#{RAILS_ROOT}/public/404.html", :status => "404 Not Found" }
69
+ type.all { render :nothing => true, :status => "404 Not Found" }
70
+ end
71
+ end
72
+
73
+ def render_500
74
+ respond_to do |type|
75
+ type.html { render :file => "#{RAILS_ROOT}/public/500.html", :status => "500 Error" }
76
+ type.all { render :nothing => true, :status => "500 Error" }
77
+ end
78
+ end
79
+
80
+ def rescue_action_in_public(exception)
81
+ case exception
82
+ when *self.class.exceptions_to_treat_as_404
83
+ render_404
84
+
85
+ else
86
+ render_500
87
+
88
+ deliverer = self.class.exception_data
89
+ data = case deliverer
90
+ when nil then {}
91
+ when Symbol then send(deliverer)
92
+ when Proc then deliverer.call(self)
93
+ end
94
+
95
+ ExceptionNotifier.deliver_exception_notification(exception, self,
96
+ request, data)
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,2 @@
1
+ require 'exception_notifiable'
2
+ require 'exception_notifier'
@@ -0,0 +1,94 @@
1
+ require 'pathname'
2
+
3
+ # Copyright (c) 2005 Jamis Buck
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+ class ExceptionNotifier < ActionMailer::Base
24
+ @@sender_address = %("Exception Notifier" <exception.notifier@default.com>)
25
+ cattr_accessor :sender_address
26
+
27
+ @@exception_recipients = []
28
+ cattr_accessor :exception_recipients
29
+
30
+ @@email_prefix = "[ERROR] "
31
+ cattr_accessor :email_prefix
32
+
33
+ @@request_sections = %w(request session environment)
34
+ cattr_accessor :request_sections
35
+
36
+ @@general_sections = %w(backtrace)
37
+ cattr_accessor :general_sections
38
+
39
+ self.template_root = "#{File.dirname(__FILE__)}/../views"
40
+
41
+ def self.reloadable?() false end
42
+
43
+ def exception_notification(exception, controller, request, data={})
44
+ content_type "text/plain"
45
+
46
+ subject "#{email_prefix}#{controller.controller_name}##{controller.action_name} (#{exception.class}) #{exception.message.inspect}"
47
+
48
+ recipients exception_recipients
49
+ from sender_address
50
+
51
+ body data.merge({ :controller => controller, :request => request,
52
+ :exception => exception, :host => (request.env["HTTP_X_FORWARDED_HOST"] || request.env["HTTP_HOST"]),
53
+ :backtrace => sanitize_backtrace(exception.backtrace),
54
+ :rails_root => rails_root, :data => data,
55
+ :sections => (request_sections+general_sections) })
56
+ end
57
+
58
+ def self.trace(subject="", data={}, &block)
59
+ begin
60
+ yield
61
+ rescue SystemExit => exception
62
+ # Do nothing but raise
63
+ raise exception
64
+ rescue Exception, StandardError => exception
65
+ deliver_exception_notification_without_request exception, subject, data
66
+ raise exception
67
+ end
68
+ end
69
+
70
+ private
71
+ def exception_notification_without_request(exception, subject, data={})
72
+ content_type "text/plain"
73
+
74
+ subject "#{email_prefix}#{subject} (#{exception.class}) #{exception.message.inspect}"
75
+
76
+ recipients exception_recipients
77
+ from sender_address
78
+
79
+ body data.merge({ :exception => exception, :host => `hostname`,
80
+ :backtrace => sanitize_backtrace(exception.backtrace),
81
+ :rails_root => rails_root, :data => data,
82
+ :sections => general_sections })
83
+ end
84
+
85
+ def sanitize_backtrace(trace)
86
+ re = Regexp.new(/^#{Regexp.escape(rails_root)}/)
87
+ trace.map { |line| Pathname.new(line.gsub(re, "[RAILS_ROOT]")).cleanpath.to_s }
88
+ end
89
+
90
+ def rails_root
91
+ @rails_root ||= Pathname.new(RAILS_ROOT).cleanpath.to_s
92
+ end
93
+
94
+ end
@@ -0,0 +1,78 @@
1
+ require 'pp'
2
+
3
+ # Copyright (c) 2005 Jamis Buck
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+ module ExceptionNotifierHelper
24
+ VIEW_PATH = "views/exception_notifier"
25
+ APP_PATH = "#{RAILS_ROOT}/app/#{VIEW_PATH}"
26
+ PARAM_FILTER_REPLACEMENT = "[FILTERED]"
27
+
28
+ def render_section(section)
29
+ RAILS_DEFAULT_LOGGER.info("rendering section #{section.inspect}")
30
+ summary = render_overridable(section).strip
31
+ unless summary.blank?
32
+ title = render_overridable(:title, :locals => { :title => section }).strip
33
+ "#{title}\n\n#{summary.gsub(/^/, " ")}\n\n"
34
+ end
35
+ end
36
+
37
+ def render_overridable(partial, options={})
38
+ if File.exist?(path = "#{APP_PATH}/_#{partial}.rhtml")
39
+ render(options.merge(:file => path, :use_full_path => false))
40
+ elsif File.exist?(path = "#{File.dirname(__FILE__)}/../#{VIEW_PATH}/_#{partial}.rhtml")
41
+ render(options.merge(:file => path, :use_full_path => false))
42
+ else
43
+ ""
44
+ end
45
+ end
46
+
47
+ def inspect_model_object(model, locals={})
48
+ render_overridable(:inspect_model,
49
+ :locals => { :inspect_model => model,
50
+ :show_instance_variables => true,
51
+ :show_attributes => true }.merge(locals))
52
+ end
53
+
54
+ def inspect_value(value)
55
+ len = 512
56
+ result = object_to_yaml(value).gsub(/\n/, "\n ").strip
57
+ result = result[0,len] + "... (#{result.length-len} bytes more)" if result.length > len+20
58
+ result
59
+ end
60
+
61
+ def object_to_yaml(object)
62
+ object.to_yaml.sub(/^---\s*/m, "")
63
+ end
64
+
65
+ def exclude_raw_post_parameters?
66
+ @controller && @controller.respond_to?(:filter_parameters)
67
+ end
68
+
69
+ def filter_sensitive_post_data_parameters(parameters)
70
+ exclude_raw_post_parameters? ? @controller.send(:filter_parameters, parameters) : parameters
71
+ end
72
+
73
+ def filter_sensitive_post_data_from_env(env_key, env_value)
74
+ return env_value unless exclude_raw_post_parameters?
75
+ return PARAM_FILTER_REPLACEMENT if (env_key =~ /RAW_POST_DATA/i)
76
+ return @controller.send(:filter_parameters, {env_key => env_value}).values[0]
77
+ end
78
+ end
@@ -0,0 +1 @@
1
+ require "action_mailer"
@@ -0,0 +1,61 @@
1
+ require 'test_helper'
2
+ require 'exception_notifier_helper'
3
+
4
+ class ExceptionNotifierHelperTest < Test::Unit::TestCase
5
+
6
+ class ExceptionNotifierHelperIncludeTarget
7
+ include ExceptionNotifierHelper
8
+ end
9
+
10
+ def setup
11
+ @helper = ExceptionNotifierHelperIncludeTarget.new
12
+ end
13
+
14
+ # No controller
15
+
16
+ def test_should_not_exclude_raw_post_parameters_if_no_controller
17
+ assert !@helper.exclude_raw_post_parameters?
18
+ end
19
+
20
+ # Controller, no filtering
21
+
22
+ class ControllerWithoutFilterParameters; end
23
+
24
+ def test_should_not_filter_env_values_for_raw_post_data_keys_if_controller_can_not_filter_parameters
25
+ stub_controller(ControllerWithoutFilterParameters.new)
26
+ assert @helper.filter_sensitive_post_data_from_env("RAW_POST_DATA", "secret").include?("secret")
27
+ end
28
+ def test_should_not_exclude_raw_post_parameters_if_controller_can_not_filter_parameters
29
+ stub_controller(ControllerWithoutFilterParameters.new)
30
+ assert !@helper.exclude_raw_post_parameters?
31
+ end
32
+ def test_should_return_params_if_controller_can_not_filter_parameters
33
+ stub_controller(ControllerWithoutFilterParameters.new)
34
+ assert_equal :params, @helper.filter_sensitive_post_data_parameters(:params)
35
+ end
36
+
37
+ # Controller with filtering
38
+
39
+ class ControllerWithFilterParameters
40
+ def filter_parameters(params); :filtered end
41
+ end
42
+
43
+ def test_should_filter_env_values_for_raw_post_data_keys_if_controller_can_filter_parameters
44
+ stub_controller(ControllerWithFilterParameters.new)
45
+ assert !@helper.filter_sensitive_post_data_from_env("RAW_POST_DATA", "secret").include?("secret")
46
+ assert @helper.filter_sensitive_post_data_from_env("SOME_OTHER_KEY", "secret").include?("secret")
47
+ end
48
+ def test_should_exclude_raw_post_parameters_if_controller_can_filter_parameters
49
+ stub_controller(ControllerWithFilterParameters.new)
50
+ assert @helper.exclude_raw_post_parameters?
51
+ end
52
+ def test_should_delegate_param_filtering_to_controller_if_controller_can_filter_parameters
53
+ stub_controller(ControllerWithFilterParameters.new)
54
+ assert_equal :filtered, @helper.filter_sensitive_post_data_parameters(:params)
55
+ end
56
+
57
+ private
58
+ def stub_controller(controller)
59
+ @helper.instance_variable_set(:@controller, controller)
60
+ end
61
+ end
@@ -0,0 +1,7 @@
1
+ require 'test/unit'
2
+ require 'rubygems'
3
+ require 'active_support'
4
+
5
+ $:.unshift File.join(File.dirname(__FILE__), '../lib')
6
+
7
+ RAILS_ROOT = '.' unless defined?(RAILS_ROOT)
@@ -0,0 +1 @@
1
+ <%=raw @backtrace.join "\n" %>
@@ -0,0 +1,7 @@
1
+ <% max = @request.env.keys.max { |a,b| a.length <=> b.length } -%>
2
+ <% @request.env.keys.sort.each do |key| -%>
3
+ * <%=raw "%-*s: %s" % [max.length, key, filter_sensitive_post_data_from_env(key, @request.env[key].to_s.strip)] %>
4
+ <% end -%>
5
+
6
+ * Process: <%=raw $$ %>
7
+ * Server : <%=raw Socket.gethostname %>
@@ -0,0 +1,16 @@
1
+ <% if show_attributes -%>
2
+ [attributes]
3
+ <% attrs = inspect_model.attributes -%>
4
+ <% max = attrs.keys.max { |a,b| a.length <=> b.length } -%>
5
+ <% attrs.keys.sort.each do |attr| -%>
6
+ * <%=raw("%*-s: %s" % [max.length, attr, object_to_yaml(attrs[attr]).gsub(/\n/, "\n ").strip]) %>
7
+ <% end -%>
8
+ <% end -%>
9
+
10
+ <% if show_instance_variables -%>
11
+ [instance variables]
12
+ <% inspect_model.instance_variables.sort.each do |variable| -%>
13
+ <%- next if variable == "@attributes" -%>
14
+ * <%=raw variable %>: <%=raw inspect_value(inspect_model.instance_variable_get(variable)) %>
15
+ <% end -%>
16
+ <% end -%>
@@ -0,0 +1,4 @@
1
+ * URL : <%=raw @request.protocol %><%=raw @host %><%=raw @request.request_uri %>
2
+ * IP address: <%=raw @request.env["HTTP_X_FORWARDED_FOR"] || @request.env["REMOTE_ADDR"] %>
3
+ * Parameters: <%=raw filter_sensitive_post_data_parameters(@request.parameters).inspect %>
4
+ * Rails root: <%=raw @rails_root %>
@@ -0,0 +1 @@
1
+ * session: <%=raw @request.session.inspect %>
@@ -0,0 +1,3 @@
1
+ -------------------------------
2
+ <%=raw title.to_s.humanize %>:
3
+ -------------------------------
@@ -0,0 +1,6 @@
1
+ A <%=raw @exception.class %> occurred in <%=raw @controller.controller_name %>#<%=raw @controller.action_name %>:
2
+
3
+ <%=raw @exception.message %>
4
+ <%=raw @backtrace.first %>
5
+
6
+ <%=raw @sections.map { |section| render_section(section) }.join %>
@@ -0,0 +1,6 @@
1
+ A <%=raw @exception.class %> occurred:
2
+
3
+ <%=raw @exception.message %>
4
+ <%=raw @backtrace.first %>
5
+
6
+ <%=raw @sections.map { |section| render_section(section) }.join %>
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: averell-exception_notification
3
+ version: !ruby/object:Gem::Version
4
+ hash: 40200471
5
+ prerelease: false
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 20100224
10
+ version: 1.0.20100224
11
+ platform: ruby
12
+ authors:
13
+ - Averell
14
+ - Rails Core Team
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2010-02-24 00:00:00 +01:00
20
+ default_executable:
21
+ dependencies: []
22
+
23
+ description:
24
+ email: FOURDJGAAPHP@spammotel.com
25
+ executables: []
26
+
27
+ extensions: []
28
+
29
+ extra_rdoc_files:
30
+ - README
31
+ files:
32
+ - lib/exception_notifiable.rb
33
+ - lib/exception_notification.rb
34
+ - lib/exception_notifier.rb
35
+ - lib/exception_notifier_helper.rb
36
+ - rails/init.rb
37
+ - README
38
+ - test/exception_notifier_helper_test.rb
39
+ - test/test_helper.rb
40
+ - views/exception_notifier/_backtrace.rhtml
41
+ - views/exception_notifier/_environment.rhtml
42
+ - views/exception_notifier/_inspect_model.rhtml
43
+ - views/exception_notifier/_request.rhtml
44
+ - views/exception_notifier/_session.rhtml
45
+ - views/exception_notifier/_title.rhtml
46
+ - views/exception_notifier/exception_notification.rhtml
47
+ - views/exception_notifier/exception_notification_without_request.rhtml
48
+ has_rdoc: true
49
+ homepage: http://github.com/averell/exception_notification
50
+ licenses: []
51
+
52
+ post_install_message:
53
+ rdoc_options:
54
+ - --main
55
+ - README
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ hash: 3
64
+ segments:
65
+ - 0
66
+ version: "0"
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ hash: 3
73
+ segments:
74
+ - 0
75
+ version: "0"
76
+ requirements: []
77
+
78
+ rubyforge_project:
79
+ rubygems_version: 1.3.7
80
+ signing_key:
81
+ specification_version: 3
82
+ summary: Gemified exception_notification rails plugin, compatible with Rails 2.3.5 with RailsXss
83
+ test_files:
84
+ - test/exception_notifier_helper_test.rb
85
+ - test/test_helper.rb