exception_handling 0.2.0 → 1.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 29c963a8d5c2ca837a7abcc3b4554d075fe9383a
4
- data.tar.gz: 4307bfe7dee9b79fcb82f711d61f00edb81c27d5
3
+ metadata.gz: 89b539c359cfa4f2cc912eb43c189bbd3ced7398
4
+ data.tar.gz: 92b3d535adbc32c47c95a8bc43fb5b12ff44c8c7
5
5
  SHA512:
6
- metadata.gz: dfa979ae72aff2be2c7ae721473ed379073194b9f15fbc84302eeaf48b43fcaa4191de9c08ba03b1a211311ffc443093bccac290d5a43f63fb09fa0f019ae02a
7
- data.tar.gz: 5f3b24b043d0d5213fee3611f5552feb5144a5261a2810cb8ccb10997daea23b1d64d53e10f68ee2d78ae1744d35536bcdf16780239007a52e073d6cf1dc73b9
6
+ metadata.gz: 5265b86655170e953d0d10564783aac66c78a9ec85b2cc99fb17f9cfbfa3704445c8222f7c13f2877db67060a398134e2e5da4882e52c84b192f1303a0aed0b0
7
+ data.tar.gz: 90a07b46e4dbafd0665d7dc9b071c74a441ad019918b616b0b2f254ac21b657d38603a60682f5e3e7c0028a1c452744f8922924a6a2a3dc87ffcf7019fbe7dcc
data/.gitignore CHANGED
@@ -1 +1,2 @@
1
1
  .idea
2
+ .DS_Store
data/Gemfile.lock CHANGED
@@ -1,11 +1,13 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- exception_handling (0.2.0)
4
+ exception_handling (1.0.0)
5
5
  actionmailer (~> 3.2)
6
6
  actionpack (~> 3.2)
7
7
  activesupport (~> 3.2)
8
8
  eventmachine (>= 0.12.10)
9
+ hobo_support
10
+ invoca-utils (~> 0.0.2)
9
11
 
10
12
  GEM
11
13
  remote: https://rubygems.org/
@@ -26,32 +28,71 @@ GEM
26
28
  activemodel (3.2.19)
27
29
  activesupport (= 3.2.19)
28
30
  builder (~> 3.0.0)
31
+ activerecord (3.2.19)
32
+ activemodel (= 3.2.19)
33
+ activesupport (= 3.2.19)
34
+ arel (~> 3.0.2)
35
+ tzinfo (~> 0.3.29)
36
+ activeresource (3.2.19)
37
+ activemodel (= 3.2.19)
38
+ activesupport (= 3.2.19)
29
39
  activesupport (3.2.19)
30
40
  i18n (~> 0.6, >= 0.6.4)
31
41
  multi_json (~> 1.0)
42
+ arel (3.0.3)
32
43
  bourne (1.3.0)
33
44
  mocha (= 0.13.0)
34
45
  builder (3.0.4)
46
+ coderay (1.1.0)
35
47
  erubis (2.7.0)
36
48
  eventmachine (1.0.3)
37
49
  hike (1.2.3)
50
+ hobo_support (2.0.1)
51
+ rails (~> 3.2.0)
38
52
  i18n (0.6.11)
53
+ invoca-utils (0.0.2)
39
54
  journey (1.0.4)
55
+ json (1.8.1)
40
56
  mail (2.5.4)
41
57
  mime-types (~> 1.16)
42
58
  treetop (~> 1.4.8)
43
59
  metaclass (0.0.1)
60
+ method_source (0.8.2)
44
61
  mime-types (1.25.1)
45
62
  mocha (0.13.0)
46
63
  metaclass (~> 0.0.1)
47
64
  multi_json (1.10.1)
48
65
  polyglot (0.3.5)
66
+ pry (0.10.0)
67
+ coderay (~> 1.1.0)
68
+ method_source (~> 0.8.1)
69
+ slop (~> 3.4)
49
70
  rack (1.4.5)
50
71
  rack-cache (1.2)
51
72
  rack (>= 0.4)
73
+ rack-ssl (1.3.4)
74
+ rack
52
75
  rack-test (0.6.2)
53
76
  rack (>= 1.0)
77
+ rails (3.2.19)
78
+ actionmailer (= 3.2.19)
79
+ actionpack (= 3.2.19)
80
+ activerecord (= 3.2.19)
81
+ activeresource (= 3.2.19)
82
+ activesupport (= 3.2.19)
83
+ bundler (~> 1.0)
84
+ railties (= 3.2.19)
85
+ railties (3.2.19)
86
+ actionpack (= 3.2.19)
87
+ activesupport (= 3.2.19)
88
+ rack-ssl (~> 1.3.2)
89
+ rake (>= 0.8.7)
90
+ rdoc (~> 3.4)
91
+ thor (>= 0.14.6, < 2.0)
54
92
  rake (10.1.0)
93
+ rdoc (3.12.2)
94
+ json (~> 1.4)
95
+ rr (1.1.2)
55
96
  shoulda (3.1.1)
56
97
  shoulda-context (~> 1.0)
57
98
  shoulda-matchers (~> 1.2)
@@ -59,21 +100,25 @@ GEM
59
100
  shoulda-matchers (1.5.6)
60
101
  activesupport (>= 3.0.0)
61
102
  bourne (~> 1.3)
103
+ slop (3.6.0)
62
104
  sprockets (2.2.2)
63
105
  hike (~> 1.2)
64
106
  multi_json (~> 1.0)
65
107
  rack (~> 1.0)
66
108
  tilt (~> 1.1, != 1.3.0)
109
+ thor (0.19.1)
67
110
  tilt (1.4.1)
68
111
  treetop (1.4.15)
69
112
  polyglot
70
113
  polyglot (>= 0.3.1)
114
+ tzinfo (0.3.40)
71
115
 
72
116
  PLATFORMS
73
117
  ruby
74
118
 
75
119
  DEPENDENCIES
76
120
  exception_handling!
77
- mocha (= 0.13.0)
121
+ pry
78
122
  rake (>= 0.9)
123
+ rr
79
124
  shoulda (= 3.1.1)
data/README.md ADDED
@@ -0,0 +1,109 @@
1
+ # ExceptionHandling
2
+
3
+ Enable emails for your exceptions that occur in your application!
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'exception_handling'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install exception_handling
18
+
19
+ ## Setup
20
+
21
+ Add some code to initialize the settings in your application.
22
+ For example:
23
+
24
+ require "exception_handling"
25
+
26
+ # required
27
+ ExceptionHandling.server_name = Cluster['server_name']
28
+ ExceptionHandling.sender_address = %("Exceptions" <exceptions@example.com>)
29
+ ExceptionHandling.exception_recipients = ['exceptions@example.com']
30
+ ExceptionHandling.logger = Rails.logger
31
+
32
+ # optional
33
+ ExceptionHandling.escalation_recipients = ['escalation@example.com']
34
+ ExceptionHandling.mailer_send_enabled = true # false, will disable exception emails
35
+ ExceptionHandling.filter_list_filename = "#{Rails.root}/config/exception_filters.yml"
36
+ ExceptionHandling.email_environment = Rails.env
37
+ ExceptionHandling.eventmachine_safe = false
38
+ ExceptionHandling.eventmachine_synchrony = false
39
+
40
+
41
+ ## Usage
42
+
43
+ Mixin the `ExceptionHandling::Methods` module into your controllers, models or classes. The example below adds it to all the controllers in you rails application:
44
+
45
+ class ApplicationController < ActionController::Base
46
+ include ExceptionHandling::Methods
47
+ ...
48
+ end
49
+
50
+ Then call any method available in the `ExceptionHandling::Methods` mixin:
51
+
52
+ begin
53
+ ...
54
+ rescue => ex
55
+ log_error( ex, "A specific error occurred." )
56
+ flash.now['error'] = "A specific error occurred. Support has been notified."
57
+ end
58
+
59
+ ## Custom Hooks
60
+
61
+ ### custom_data_hook
62
+
63
+ Emails are generated using the `.erb` templates in the [views](./views) directory. You can add custom information to the exception data with a custom method. For example:
64
+
65
+ def append_custom_user_info(data)
66
+ begin
67
+ data[:user_details] = {}
68
+ data[:user_details][:username] = "CaryP"
69
+ data[:user_details][:organization] = "Invoca Engineering Dept."
70
+ rescue Exception => e
71
+ # don't let these out!
72
+ end
73
+ end
74
+
75
+ Then tie this in using the `custom_data_hook`:
76
+
77
+ ExceptionHandling.custom_data_hook = method(:append_custom_user_info)
78
+
79
+
80
+ ### post_log_error_hook
81
+
82
+ There is another hook available intended for custom actions after an error email is sent. This can be used to send information about errors to your alerting subsystem. For example:
83
+
84
+ def log_error_metrics(exception_data, exception)
85
+ if exception.is_a?(ExceptionHandling::Warning)
86
+ Invoca::Metrics::Client.metrics.counter("exception_handling/warning")
87
+ else
88
+ Invoca::Metrics::Client.metrics.counter("exception_handling/exception")
89
+ end
90
+ end
91
+ ExceptionHandling.post_log_error_hook = method(:log_error_metrics)
92
+
93
+
94
+ ## Testing
95
+
96
+ There is a reusable rails controller stub that might be useful for your own tests. To leverage it in your own test, simply add the following require to your unit tests:
97
+
98
+ require 'exception_handling/testing'
99
+
100
+ We use it for testing that our `custom_data_hook` code is working properly.
101
+
102
+
103
+ ## Contributing
104
+
105
+ 1. Fork it
106
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
107
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
108
+ 4. Push to the branch (`git push origin my-new-feature`)
109
+ 5. Create new Pull Request
data/Rakefile CHANGED
@@ -1,9 +1,17 @@
1
1
  #!/usr/bin/env rake
2
2
  require "bundler/gem_tasks"
3
+ require 'rake/testtask'
3
4
 
4
- task :default => :test
5
+ namespace :test do
6
+
7
+ Rake::TestTask.new do |t|
8
+ t.name = :unit
9
+ t.libs << "test"
10
+ t.pattern = 'test/unit/**/*_test.rb'
11
+ t.verbose = true
12
+ end
13
+ Rake::Task['test:unit'].comment = "Run the unit tests"
5
14
 
6
- desc "Run unit tests."
7
- task :test do
8
- ruby "test/exception_handling_test.rb"
9
15
  end
16
+
17
+ task :default => 'test:unit'
@@ -1,24 +1,28 @@
1
1
  require File.expand_path('../lib/exception_handling/version', __FILE__)
2
2
 
3
- Gem::Specification.new do |gem|
4
- gem.add_dependency 'eventmachine', '>=0.12.10'
5
- gem.add_dependency 'activesupport', '~> 3.2'
6
- gem.add_dependency 'actionpack', '~> 3.2'
7
- gem.add_dependency 'actionmailer', '~> 3.2'
8
- gem.add_development_dependency 'rake', '>=0.9'
9
- gem.add_development_dependency 'shoulda', '=3.1.1'
10
- gem.add_development_dependency 'mocha', '=0.13.0'
3
+ Gem::Specification.new do |spec|
4
+ spec.authors = ["Colin Kelley"]
5
+ spec.email = ["colindkelley@gmail.com"]
6
+ spec.description = %q{Exception handling logger/emailer}
7
+ spec.summary = %q{Invoca's exception handling logger/emailer layer, based on exception_notifier. Works with Rails or EventMachine or EventMachine+Synchrony.}
8
+ spec.homepage = "https://github.com/Invoca/exception_handling"
11
9
 
12
- gem.authors = ["Colin Kelley"]
13
- gem.email = ["colindkelley@gmail.com"]
14
- gem.description = %q{Exception handling logger/emailer}
15
- gem.summary = %q{Invoca's exception handling logger/emailer layer, based on exception_notifier. Works with Rails or EventMachine or EventMachine+Synchrony.}
16
- gem.homepage = "https://github.com/Invoca/exception_handling"
10
+ spec.files = `git ls-files`.split($\)
11
+ spec.executables = spec.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
12
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/.*\.rb})
13
+ spec.name = "exception_handling"
14
+ spec.require_paths = ["lib"]
15
+ spec.version = ExceptionHandling::VERSION
17
16
 
18
- gem.files = `git ls-files`.split($\)
19
- gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
20
- gem.test_files = gem.files.grep(%r{^(test|spec|features)/.*\.rb})
21
- gem.name = "exception_handling"
22
- gem.require_paths = ["lib"]
23
- gem.version = ExceptionHandling::VERSION
17
+ spec.add_dependency 'eventmachine', '>=0.12.10'
18
+ spec.add_dependency 'activesupport', '~> 3.2'
19
+ spec.add_dependency 'actionpack', '~> 3.2'
20
+ spec.add_dependency 'actionmailer', '~> 3.2'
21
+ spec.add_dependency 'invoca-utils', '~> 0.0.2'
22
+ spec.add_dependency 'hobo_support'
23
+
24
+ spec.add_development_dependency 'rake', '>=0.9'
25
+ spec.add_development_dependency 'shoulda', '=3.1.1'
26
+ spec.add_development_dependency 'rr'
27
+ spec.add_development_dependency 'pry'
24
28
  end
@@ -0,0 +1,84 @@
1
+ #
2
+ # Used by functional tests to track exceptions.
3
+ #
4
+
5
+ module LogErrorStub
6
+ class UnexpectedExceptionLogged < StandardError; end
7
+ class ExpectedExceptionNotLogged < StandardError; end
8
+
9
+ def setup_log_error_stub
10
+ clear_exception_whitelist
11
+ stub_log_error unless respond_to?(:dont_stub_log_error) && dont_stub_log_error
12
+ end
13
+
14
+ def teardown_log_error_stub
15
+ ExceptionHandling.stub_handler = nil
16
+ return unless @exception_whitelist
17
+ @exception_whitelist.each do |item|
18
+ add_failure("log_error expected #{item[1][:expected]} times with pattern: '#{item[0].is_a?(Regexp) ? item[0].source : item[0]}' #{item[1][:count]} found #{item[1][:found]}") unless item[1][:expected] == item[1][:found]
19
+ end
20
+ end
21
+
22
+ attr_accessor :exception_whitelist
23
+
24
+ #
25
+ # Call this function in your functional tests - usually first line after a "should" statement
26
+ # once called, you can then call expects_exception
27
+ # By stubbing log error, ExceptionHandling will keep a list of all expected exceptions and
28
+ # gracefully note their occurrence.
29
+ #
30
+ def stub_log_error
31
+ ExceptionHandling.stub_handler = self
32
+ end
33
+
34
+ #
35
+ # Gets called by ExceptionHandling::log_error in test mode.
36
+ # If you have called expects_exception then this function will simply note that an
37
+ # instance of that exception has occurred - otherwise it will raise (which will
38
+ # generally result in a 500 return code for your test request)
39
+ #
40
+ def handle_stub_log_error(exception_data, always_raise = false)
41
+ raise_unexpected_exception(exception_data) if always_raise || !exception_filtered?(exception_data)
42
+ end
43
+
44
+ #
45
+ # Did the calling code call expects_exception on this exception?
46
+ #
47
+ def exception_filtered?(exception_data)
48
+ @exception_whitelist && @exception_whitelist.any? do |expectation|
49
+ if expectation[0] === exception_data[:error]
50
+ expectation[1][:found] += 1
51
+ true
52
+ end
53
+ end
54
+ end
55
+
56
+ #
57
+ # Call this from your test file to declare what exceptions you expect to raise.
58
+ #
59
+ def expects_exception(pattern, options = {})
60
+ @exception_whitelist ||= []
61
+ expected_count = options[:count] || 1
62
+ options = {:expected => expected_count, :found => 0}
63
+ if to_increment = @exception_whitelist.find {|ex| ex[0] == pattern}
64
+ to_increment[1][:expected] += expected_count
65
+ else
66
+ @exception_whitelist << [pattern, options]
67
+ end
68
+ end
69
+
70
+ def clear_exception_whitelist
71
+ @exception_whitelist = nil
72
+ end
73
+
74
+ private
75
+
76
+ def raise_unexpected_exception(exception_data)
77
+ raise(UnexpectedExceptionLogged,
78
+ exception_data[:error] + "\n" +
79
+ "---original backtrace---\n" +
80
+ exception_data[:backtrace].join("\n") + "\n" +
81
+ "------")
82
+ end
83
+
84
+ end
@@ -4,7 +4,7 @@ module ExceptionHandling
4
4
  class Mailer < ActionMailer::Base
5
5
  default :content_type => "text/html"
6
6
 
7
- self.append_view_path "#{File.dirname(__FILE__)}/../views"
7
+ self.append_view_path "#{File.dirname(__FILE__)}/../../views"
8
8
 
9
9
  [:email_environment, :server_name, :sender_address, :exception_recipients, :escalation_recipients].each do |method|
10
10
  define_method method do
@@ -0,0 +1,69 @@
1
+ module ExceptionHandling
2
+ module Methods # included on models and controllers
3
+
4
+ protected
5
+
6
+ def log_error(exception_or_string, exception_context = '')
7
+ controller = self if respond_to?(:request) && respond_to?(:session)
8
+ ExceptionHandling.log_error(exception_or_string, exception_context, controller)
9
+ end
10
+
11
+ def log_error_rack(exception_or_string, exception_context = '', rack_filter = '')
12
+ ExceptionHandling.log_error_rack(exception_or_string, exception_context, rack_filter)
13
+ end
14
+
15
+ def log_warning(message)
16
+ log_error(Warning.new(message))
17
+ end
18
+
19
+ def log_info(message)
20
+ ExceptionHandling.logger.info( message )
21
+ end
22
+
23
+ def log_debug(message)
24
+ ExceptionHandling.logger.debug( message )
25
+ end
26
+
27
+ def ensure_safe(exception_context = "")
28
+ begin
29
+ yield
30
+ rescue => ex
31
+ log_error ex, exception_context
32
+ nil
33
+ end
34
+ end
35
+
36
+ def escalate_error(exception_or_string, email_subject)
37
+ ExceptionHandling.escalate_error(exception_or_string, email_subject)
38
+ end
39
+
40
+ def escalate_warning(message, email_subject)
41
+ ExceptionHandling.escalate_warning(message, email_subject)
42
+ end
43
+
44
+ def ensure_escalation(*args)
45
+ ExceptionHandling.ensure_escalation(*args) do
46
+ yield
47
+ end
48
+ end
49
+
50
+ # Store aside the current controller when included
51
+ LONG_REQUEST_SECONDS = (defined?(Rails) && Rails.env == 'test' ? 300 : 30)
52
+ def set_current_controller
53
+ ExceptionHandling.current_controller = self
54
+ result = nil
55
+ time = Benchmark.measure do
56
+ result = yield
57
+ end
58
+ name = " in #{controller_name}::#{action_name}" rescue " "
59
+ log_error( "Long controller action detected#{name} %.4fs " % time.real ) if time.real > LONG_REQUEST_SECONDS && !['development', 'test'].include?(ExceptionHandling.email_environment)
60
+ result
61
+ ensure
62
+ ExceptionHandling.current_controller = nil
63
+ end
64
+
65
+ def self.included( controller )
66
+ controller.around_filter :set_current_controller if controller.respond_to? :around_filter
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,65 @@
1
+
2
+ # some useful test objects
3
+
4
+ module ExceptionHandling
5
+ module Testing
6
+ class ControllerStub
7
+
8
+ class Request
9
+ attr_accessor :parameters, :protocol, :host, :request_uri, :env, :session_options
10
+ def initialize
11
+ @parameters = {:id => "1"}
12
+ @protocol = 'http'
13
+ @host = 'localhost'
14
+ @request_uri = "/fun/testing.html?foo=bar"
15
+ @env = {:HOST => "local"}
16
+ @session_options = { :id => '93951506217301' }
17
+ end
18
+ end
19
+
20
+ attr_accessor :request, :session
21
+ class << self
22
+ attr_accessor :around_filter_method
23
+ end
24
+
25
+ def initialize
26
+ @request = Request.new
27
+ @session_id = "ZKL95"
28
+ @session =
29
+ if defined?(Username)
30
+ {
31
+ :login_count => 22,
32
+ :username_id => Username.first.id,
33
+ :user_id => User.first.id,
34
+ }
35
+ else
36
+ { }
37
+ end
38
+ end
39
+
40
+ def simulate_around_filter( &block )
41
+ set_current_controller( &block )
42
+ end
43
+
44
+ def controller_name
45
+ "ControllerStub"
46
+ end
47
+
48
+ def action_name
49
+ "test_action"
50
+ end
51
+
52
+ def self.around_filter( method )
53
+ ControllerStub.around_filter_method = method
54
+ end
55
+
56
+ def complete_request_uri
57
+ "#{@request.protocol}#{@request.host}#{@request.request_uri}"
58
+ end
59
+
60
+ include ExceptionHandling::Methods
61
+
62
+ end
63
+
64
+ end
65
+ end
@@ -1,3 +1,3 @@
1
1
  module ExceptionHandling
2
- VERSION = "0.2.0"
2
+ VERSION = "1.0.0"
3
3
  end