exception_handling 0.2.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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