eh 0.0.1
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.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +104 -0
- data/Rakefile +1 -0
- data/eh.gemspec +25 -0
- data/lib/eh/eh.rb +171 -0
- data/lib/eh/version.rb +3 -0
- data/lib/error_handler.rb +4 -0
- data/spec/eh_spec.rb +380 -0
- data/spec/mock_logger.rb +16 -0
- metadata +131 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Ernst van Graan, Wynand van Dyk, Hetzner (Pty) Ltd
|
2
|
+
|
3
|
+
MIT License
|
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.
|
data/README.md
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
# Eh
|
2
|
+
|
3
|
+
The Error handler gem provids the following major functional support for error handling:
|
4
|
+
|
5
|
+
o Wrapping of code blocks in exception handling logic, supporting retry, logging and exception consumption
|
6
|
+
o Provision of fault tolerant logging that logs to configured logger(s), or to stderr should loggers be unavailable
|
7
|
+
o Capability to handle unhandled exceptions, log them, email them and either raise them again or convert them to system exit codes (listing of standard linux exit codes provided as constants)
|
8
|
+
|
9
|
+
The Error Handler gem allows the wrapping of arbitrary blocks of code in exception handling logic. Specifically, once an exception occurs:
|
10
|
+
|
11
|
+
o The block can be retried 'threshold' times, with a delay of 'delay' seconds in-between retries (EH::retry and EH::retry!)
|
12
|
+
o The exception can be logged to a single logger, or an array of loggers
|
13
|
+
o The excepion can be re-raised (suppressed during retry, but logged, raised again after retry failure)
|
14
|
+
o The same functionality is available, with retry disabled (EH::run and EH::run!)
|
15
|
+
o The list of Linux system exit codes is also provided as EH::EX_<exit>
|
16
|
+
o Unhandled exceptions can be logged and emailed in main() using EH::report_unhandled()
|
17
|
+
|
18
|
+
This gem is sponsored by Hetzner (Pty) Ltd - http://hetzner.co.za
|
19
|
+
|
20
|
+
## Installation
|
21
|
+
|
22
|
+
Add this line to your application's Gemfile:
|
23
|
+
|
24
|
+
gem 'eh'
|
25
|
+
|
26
|
+
And then execute:
|
27
|
+
|
28
|
+
$ bundle
|
29
|
+
|
30
|
+
Or install it yourself as:
|
31
|
+
|
32
|
+
$ gem install eh
|
33
|
+
|
34
|
+
## Usage
|
35
|
+
|
36
|
+
-- Run code block with retry, logging and exception re-raise, specifying logger, number of
|
37
|
+
times to retry, retry delay, and block arguments
|
38
|
+
def load_configuration(configuration_path, logger = nil)
|
39
|
+
EH::retry!(:logger => logger,
|
40
|
+
:message => "Could not parse YAML configuration",
|
41
|
+
:args => [configuration_path],
|
42
|
+
:threshold => 5,
|
43
|
+
:delay => 0.5) do
|
44
|
+
YAML.load(File.open(configuration_path))
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
-- Run code block with retry only on the exceptions listed, with the default threshold and delay
|
49
|
+
|
50
|
+
def load_configuration(configuration_path, logger = nil)
|
51
|
+
EH::retry!(:args => [configuration_path], :exceptions => [IOError, RuntimeError]) do
|
52
|
+
YAML.load(File.open(configuration_path))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
-- Run code block without retry, and swallowing all exceptions
|
57
|
+
|
58
|
+
def load_configuration(configuration_path, logger = nil)
|
59
|
+
EH::run(:args => [configuration_path]) do
|
60
|
+
YAML.load(File.open(configuration_path))
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
Any combination of the following options are allowed, both with both flavours of run() and retry().
|
65
|
+
|
66
|
+
:logger - log using the logger's error() method
|
67
|
+
:message - log this message, with the exception appended
|
68
|
+
:args - code block arguments
|
69
|
+
:threshold - the number of execution attempts
|
70
|
+
:delay - the number of seconds to delay between retries
|
71
|
+
|
72
|
+
|
73
|
+
Function descriptions:
|
74
|
+
|
75
|
+
run - Execute block, swallow all exceptions, no retry
|
76
|
+
run! - Execute block, re-raise exceptions, no retry
|
77
|
+
retry - Execute block, swallow all exceptions, retry
|
78
|
+
retry! - Execute block, re-raise exceptions, retry
|
79
|
+
|
80
|
+
Exception filters:
|
81
|
+
|
82
|
+
When :exception_filter is provided to retry!(), only the exceptions in the filter are retried. If retry fails, all exceptions are re-raised.
|
83
|
+
When :exception_filter is provided to retry(), only the exceptions in the filter are retried. If retry fails. Exceptions are not re-raised.
|
84
|
+
When :exception_filter is provided to run!(), only the exceptions in the filter are logged, if a logger is provided. All exceptions are re-raised.
|
85
|
+
When :exception_filter is provided to run(), only the exceptions in the filter are logged, if a logger is provided. Exceptions are not re-raised.
|
86
|
+
|
87
|
+
Unhandled exceptions can be logged and emailed as follows:
|
88
|
+
|
89
|
+
at_exit do
|
90
|
+
EH::report_unhandled(logfile = "/var/log/myapp/crash.log", email = "ernst.van.graan@hetzner.co.za")
|
91
|
+
exit EH::EX_CONFIG
|
92
|
+
end
|
93
|
+
|
94
|
+
Please send feedback and comments to the authors at:
|
95
|
+
Ernst van Graan <ernst.van.graan@hetzner.co.za>
|
96
|
+
Wynand van Dyk <wynand.van.dyk@hetzner.co.za>
|
97
|
+
|
98
|
+
## Contributing
|
99
|
+
|
100
|
+
1. Fork it
|
101
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
102
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
103
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
104
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/eh.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'eh/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "eh"
|
8
|
+
spec.version = ErrorHandler::VERSION
|
9
|
+
spec.authors = ["Ernst ban Graan"]
|
10
|
+
spec.email = ["ernst.van.graan@hetzner.co.za"]
|
11
|
+
spec.description = %q{Error handler gem that allows wrapping of code blocks with support for block retry, logging, exception filtering and re-raise}
|
12
|
+
spec.summary = %q{Error handler gem that allows wrapping of code blocks with support for block retry, logging, exception filtering and re-raise}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec"
|
24
|
+
spec.add_development_dependency "simplecov"
|
25
|
+
end
|
data/lib/eh/eh.rb
ADDED
@@ -0,0 +1,171 @@
|
|
1
|
+
module ErrorHandler
|
2
|
+
class EH
|
3
|
+
EX_OK = 0 ; EX_OK.freeze
|
4
|
+
EX_BASE = 64; EX_BASE.freeze # base value for error messages
|
5
|
+
EX_USAGE = 64; EX_USAGE.freeze # command line usage error
|
6
|
+
EX_DATAERR = 65; EX_DATAERR.freeze # data format error
|
7
|
+
EX_NOINPUT = 66; EX_NOINPUT.freeze # cannot open input
|
8
|
+
EX_NOUSER = 67; EX_NOUSER.freeze # addressee unknown
|
9
|
+
EX_NOHOST = 68; EX_NOHOST.freeze # host name unknown
|
10
|
+
EX_UNAVAILABLE = 69; EX_UNAVAILABLE.freeze # service unavailable
|
11
|
+
EX_SOFTWARE = 70; EX_SOFTWARE.freeze # internal software error
|
12
|
+
EX_OSERR = 71; EX_OSERR.freeze # system error (e.g., can't fork)
|
13
|
+
EX_OSFILE = 72; EX_OSFILE.freeze # critical OS file missing
|
14
|
+
EX_CANTCREAT = 73; EX_CANTCREAT.freeze # /* can't create (user) output file
|
15
|
+
EX_IOERR = 74; EX_IOERR.freeze # input/output error
|
16
|
+
EX_TEMPFAIL = 75; EX_TEMPFAIL.freeze # temp failure; user is invited to retry
|
17
|
+
EX_PROTOCOL = 76; EX_PROTOCOL.freeze # remote error in protocol
|
18
|
+
EX_NOPERM = 77; EX_NOPERM.freeze # permission denied
|
19
|
+
|
20
|
+
ERROR = 'error'
|
21
|
+
DEBUG = 'debug'
|
22
|
+
INFO = 'info'
|
23
|
+
WARN = 'warn'
|
24
|
+
FATAL = 'fatal'
|
25
|
+
|
26
|
+
# Coming soon...
|
27
|
+
#def self.report_unhandled(logfile = nil, email = nil)
|
28
|
+
# if $!
|
29
|
+
# message = "Unhandled exception: #{$!}"
|
30
|
+
# warn message
|
31
|
+
# if not logfile.nil?
|
32
|
+
# open(logfile, 'a') { |f|
|
33
|
+
# f.puts message
|
34
|
+
# }
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# if not email.nil?
|
38
|
+
# send_email(email, message)
|
39
|
+
# end
|
40
|
+
# end
|
41
|
+
#end
|
42
|
+
|
43
|
+
def self.retry!(options, &block)
|
44
|
+
opts = options || {}
|
45
|
+
EH::retry_with_raise(opts, block)
|
46
|
+
|
47
|
+
rescue => e
|
48
|
+
raise e if opts.nil? == false and opts[:exception_filter] and not opts[:exception_filter].include? e.class
|
49
|
+
|
50
|
+
EH::log(opts[:logger], "#{opts[:message]}: #{e.message}", EH::log_level(opts)) if opts.nil? == false and not opts[:logger].nil? and not opts[:message].nil?
|
51
|
+
raise e
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.retry(options, &block)
|
55
|
+
opts = options || {}
|
56
|
+
begin
|
57
|
+
EH::retry_with_raise(opts, block)
|
58
|
+
return true
|
59
|
+
rescue => e
|
60
|
+
if not opts[:logger].nil?
|
61
|
+
EH::log(opts[:logger], "#{opts[:message]}: #{e.message}", EH::log_level(opts)) if opts[:exception_filter].nil? or opts[:exception_filter].include? e.class
|
62
|
+
end
|
63
|
+
return false
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.run!(options, &block)
|
68
|
+
opts = options || {}
|
69
|
+
block.call(EH::construct_args(opts))
|
70
|
+
|
71
|
+
rescue => e
|
72
|
+
if not opts[:logger].nil?
|
73
|
+
EH::log(opts[:logger], "#{opts[:message]}: #{e.message}", EH::log_level(opts)) if opts[:exception_filter].nil? or opts[:exception_filter].include? e.class
|
74
|
+
end
|
75
|
+
|
76
|
+
raise e if opts.nil? == false and opts[:exception_filter] and not opts[:exception_filter].include? e.class
|
77
|
+
raise e
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.run(options, &block)
|
81
|
+
opts = options || {}
|
82
|
+
block.call(EH::construct_args(opts))
|
83
|
+
|
84
|
+
rescue => e
|
85
|
+
if not opts[:logger].nil?
|
86
|
+
EH::log(opts[:logger], "#{opts[:message]}: #{e.message}", EH::log_level(opts)) if opts[:exception_filter].nil? or opts[:exception_filter].include? e.class
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.log(facilities, msg, msg_type)
|
91
|
+
if facilities.is_a? Array
|
92
|
+
EH::log_multiple_loggers(facilities, msg, msg_type)
|
93
|
+
else
|
94
|
+
EH::log_single_logger(facilities, msg, msg_type)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def self.construct_args(opts)
|
101
|
+
return nil if opts.nil?
|
102
|
+
return nil if opts[:args].nil?
|
103
|
+
opts[:args].join(' ')
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.retry_with_raise(opts, block)
|
107
|
+
retry_threshold = opts[:threshold] || 3
|
108
|
+
delay = opts[:delay] || 0.2
|
109
|
+
attempts = 0
|
110
|
+
begin
|
111
|
+
block.call(EH::construct_args(opts))
|
112
|
+
rescue => e
|
113
|
+
raise e if opts[:exception_filter] and not opts[:exception_filter].include? e.class
|
114
|
+
|
115
|
+
attempts += 1
|
116
|
+
sleep delay
|
117
|
+
retry if attempts < retry_threshold
|
118
|
+
raise e
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Coming soon...
|
123
|
+
# def send_email(to, message, opts={})
|
124
|
+
# require 'net/smtp'
|
125
|
+
#
|
126
|
+
# opts[:server] ||= Socket.gethostbyname(Socket.gethostname).first
|
127
|
+
# me = "database-agent@#{opts[:server]}"
|
128
|
+
# opts[:from] ||= me
|
129
|
+
# opts[:from_alias] ||= me
|
130
|
+
# opts[:subject] ||= "Uncaught exception"
|
131
|
+
# opts[:body] ||= message
|
132
|
+
#
|
133
|
+
# msg = <<END_OF_MESSAGE
|
134
|
+
#From: #{opts[:from_alias]} <#{opts[:from]}>
|
135
|
+
#To: <#{to}>
|
136
|
+
#Subject: #{opts[:subject]}
|
137
|
+
#
|
138
|
+
# #{opts[:body]}
|
139
|
+
#END_OF_MESSAGE
|
140
|
+
#
|
141
|
+
# Net::SMTP.start(opts[:server]) do |smtp|
|
142
|
+
# smtp.send_message msg, opts[:from], to
|
143
|
+
# end
|
144
|
+
# end
|
145
|
+
|
146
|
+
def self.log_single_logger(logger, msg, msg_type)
|
147
|
+
if logger.nil?
|
148
|
+
warn msg_type + ': ' + msg
|
149
|
+
else
|
150
|
+
self.log([logger], msg, msg_type)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def self.log_multiple_loggers(loggers, msg, msg_type)
|
155
|
+
loggers.each do |logger|
|
156
|
+
next if logger.nil?
|
157
|
+
logger.warn msg if msg_type == WARN
|
158
|
+
logger.debug msg if msg_type == DEBUG
|
159
|
+
logger.error msg if msg_type == ERROR
|
160
|
+
logger.info msg if msg_type == INFO
|
161
|
+
logger.fatal msg if msg_type == FATAL
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def self.log_level(opts)
|
166
|
+
opts[:level] || EH::ERROR
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
EH = ErrorHandler::EH
|
data/lib/eh/version.rb
ADDED
data/spec/eh_spec.rb
ADDED
@@ -0,0 +1,380 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "eh/eh"
|
3
|
+
require "mock_logger"
|
4
|
+
|
5
|
+
describe ErrorHandler do
|
6
|
+
before :each do
|
7
|
+
@logger = MockLogger.new
|
8
|
+
end
|
9
|
+
|
10
|
+
context "When executing a block of code without retry" do
|
11
|
+
describe "run" do
|
12
|
+
it "should swallow all exceptions if asked to" do
|
13
|
+
begin
|
14
|
+
exception = nil
|
15
|
+
EH::run(nil) do
|
16
|
+
raise RuntimeError
|
17
|
+
end
|
18
|
+
rescue => e
|
19
|
+
exception = e
|
20
|
+
end
|
21
|
+
exception.nil?.should == true
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should not retry" do
|
25
|
+
begin
|
26
|
+
count = 0
|
27
|
+
EH::run(:exception_filter => [RuntimeError], :args => [count]) do
|
28
|
+
count += 1
|
29
|
+
raise RuntimeError
|
30
|
+
end
|
31
|
+
rescue => e
|
32
|
+
end
|
33
|
+
count.should == 1
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should log the message specified with the exception appended, using the logger specified" do
|
37
|
+
@logger.should_receive(:error).with("the message: RuntimeError")
|
38
|
+
begin
|
39
|
+
EH::run(:logger => @logger, :message => "the message") do
|
40
|
+
raise RuntimeError
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should log the message specified with the exception appended, if the exception is in :exception_filter, using the logger specified" do
|
46
|
+
@logger.should_receive(:error).with("the message: RuntimeError")
|
47
|
+
begin
|
48
|
+
EH::run(:logger => @logger, :message => "the message", :exception_filter => [RuntimeError]) do
|
49
|
+
raise RuntimeError
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should not log, if :exception_filter is specified and the exception is not in :exception_filter" do
|
55
|
+
@logger.should_not_receive(:error)
|
56
|
+
begin
|
57
|
+
EH::run(:logger => @logger, :message => "the message", :exception_filter => [RuntimeError]) do
|
58
|
+
raise IOError
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should log using the level specified" do
|
64
|
+
@logger.should_receive(:warn).with("the message: RuntimeError")
|
65
|
+
begin
|
66
|
+
EH::run(:logger => @logger, :message => "the message", :level => EH::WARN) do
|
67
|
+
raise RuntimeError
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe "run!" do
|
74
|
+
it "should re-raise all exceptions if asked to" do
|
75
|
+
begin
|
76
|
+
exception = nil
|
77
|
+
EH::run!(nil) do
|
78
|
+
raise RuntimeError
|
79
|
+
end
|
80
|
+
rescue => e
|
81
|
+
exception = e
|
82
|
+
end
|
83
|
+
exception.class.should == RuntimeError
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should not retry" do
|
87
|
+
begin
|
88
|
+
count = 0
|
89
|
+
EH::run!(:exception_filter => [RuntimeError], :args => [count]) do
|
90
|
+
count += 1
|
91
|
+
raise RuntimeError
|
92
|
+
end
|
93
|
+
rescue => e
|
94
|
+
end
|
95
|
+
count.should == 1
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should log the message specified with the exception appended, using the logger specified" do
|
99
|
+
@logger.should_receive(:error).with("the message: RuntimeError")
|
100
|
+
begin
|
101
|
+
EH::run!(:logger => @logger, :message => "the message") do
|
102
|
+
raise RuntimeError
|
103
|
+
end
|
104
|
+
rescue => e
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should log the message specified with the exception appended, if the exception is in :exception_filter, using the logger specified" do
|
109
|
+
@logger.should_receive(:error).with("the message: RuntimeError")
|
110
|
+
begin
|
111
|
+
EH::run!(:logger => @logger, :message => "the message", :exception_filter => [RuntimeError]) do
|
112
|
+
raise RuntimeError
|
113
|
+
end
|
114
|
+
rescue => e
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should not log, if :exception_filter is specified and the exception is not in :exception_filter" do
|
119
|
+
@logger.should_not_receive(:error)
|
120
|
+
begin
|
121
|
+
EH::run!(:logger => @logger, :message => "the message", :exception_filter => [RuntimeError]) do
|
122
|
+
raise IOError
|
123
|
+
end
|
124
|
+
rescue => e
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should log using the level specified" do
|
129
|
+
@logger.should_receive(:warn).with("the message: RuntimeError")
|
130
|
+
begin
|
131
|
+
EH::run!(:logger => @logger, :message => "the message", :level => EH::WARN) do
|
132
|
+
raise RuntimeError
|
133
|
+
end
|
134
|
+
rescue => e
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
context "When executing a block of code with retry" do
|
141
|
+
describe "retry" do
|
142
|
+
it "should swallow all exceptions if asked to" do
|
143
|
+
begin
|
144
|
+
exception = nil
|
145
|
+
EH::retry(nil) do
|
146
|
+
raise RuntimeError
|
147
|
+
end
|
148
|
+
rescue => e
|
149
|
+
exception = e
|
150
|
+
end
|
151
|
+
exception.nil?.should == true
|
152
|
+
end
|
153
|
+
|
154
|
+
it "should retry" do
|
155
|
+
begin
|
156
|
+
count = 0
|
157
|
+
EH::retry(:exception_filter => [RuntimeError], :args => [count]) do
|
158
|
+
count += 1
|
159
|
+
raise RuntimeError
|
160
|
+
end
|
161
|
+
end
|
162
|
+
count.should == 3
|
163
|
+
end
|
164
|
+
|
165
|
+
it "should return true if the code succeeds after retry" do
|
166
|
+
begin
|
167
|
+
count = 0
|
168
|
+
result = EH::retry(:exception_filter => [RuntimeError], :args => [count]) do
|
169
|
+
count += 1
|
170
|
+
raise RuntimeError if count < 2
|
171
|
+
end
|
172
|
+
end
|
173
|
+
result.should == true
|
174
|
+
end
|
175
|
+
|
176
|
+
it "should return false if the code does not succeed after retry" do
|
177
|
+
begin
|
178
|
+
result = EH::retry(:exception_filter => [RuntimeError]) do
|
179
|
+
raise RuntimeError
|
180
|
+
end
|
181
|
+
end
|
182
|
+
result.should == false
|
183
|
+
end
|
184
|
+
|
185
|
+
it "should attempt the number of retries specified in :threshold" do
|
186
|
+
begin
|
187
|
+
count = 0
|
188
|
+
EH::retry(:exception_filter => [RuntimeError], :args => [count], :threshold => 5) do
|
189
|
+
count += 1
|
190
|
+
raise RuntimeError
|
191
|
+
end
|
192
|
+
end
|
193
|
+
count.should == 5
|
194
|
+
end
|
195
|
+
|
196
|
+
it "should delay between intervals as specified in :delay" do
|
197
|
+
begin
|
198
|
+
pre = Time.now
|
199
|
+
EH::retry(:exception_filter => [RuntimeError], :delay => 0.1, :threshold => 6) do
|
200
|
+
raise RuntimeError
|
201
|
+
end
|
202
|
+
end
|
203
|
+
post = Time.now
|
204
|
+
(post - pre > 0.6).should == true
|
205
|
+
end
|
206
|
+
|
207
|
+
it "should log the message specified with the exception appended, using the logger specified" do
|
208
|
+
@logger.should_receive(:error).with("the message: RuntimeError")
|
209
|
+
begin
|
210
|
+
EH::retry(:logger => @logger, :message => "the message") do
|
211
|
+
raise RuntimeError
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
it "should log the message specified with the exception appended, if the exception is in :exception_filter, using the logger specified" do
|
217
|
+
@logger.should_receive(:error).with("the message: RuntimeError")
|
218
|
+
begin
|
219
|
+
EH::retry(:logger => @logger, :message => "the message", :exception_filter => [RuntimeError]) do
|
220
|
+
raise RuntimeError
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
it "should not log, if :exception_filter is specified and the exception is not in :exception_filter" do
|
226
|
+
@logger.should_not_receive(:error)
|
227
|
+
begin
|
228
|
+
EH::retry(:logger => @logger, :message => "the message", :exception_filter => [RuntimeError]) do
|
229
|
+
raise IOError
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
it "should log using the level specified" do
|
235
|
+
@logger.should_receive(:warn).with("the message: RuntimeError")
|
236
|
+
begin
|
237
|
+
EH::retry(:logger => @logger, :message => "the message", :level => EH::WARN) do
|
238
|
+
raise RuntimeError
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
describe "retry!" do
|
245
|
+
it "should re-raise all exceptions if asked to" do
|
246
|
+
begin
|
247
|
+
exception = nil
|
248
|
+
EH::retry!(nil) do
|
249
|
+
raise RuntimeError
|
250
|
+
end
|
251
|
+
rescue => e
|
252
|
+
exception = e
|
253
|
+
end
|
254
|
+
exception.class.should == RuntimeError
|
255
|
+
end
|
256
|
+
|
257
|
+
it "should retry" do
|
258
|
+
begin
|
259
|
+
count = 0
|
260
|
+
EH::retry!(:exception_filter => [RuntimeError], :args => [count]) do
|
261
|
+
count += 1
|
262
|
+
raise RuntimeError
|
263
|
+
end
|
264
|
+
rescue => e
|
265
|
+
end
|
266
|
+
count.should == 3
|
267
|
+
end
|
268
|
+
|
269
|
+
it "should attempt the number of retries specified in :threshold" do
|
270
|
+
begin
|
271
|
+
count = 0
|
272
|
+
EH::retry(:exception_filter => [RuntimeError], :args => [count], :threshold => 5) do
|
273
|
+
count += 1
|
274
|
+
raise RuntimeError
|
275
|
+
end
|
276
|
+
rescue => e
|
277
|
+
end
|
278
|
+
count.should == 5
|
279
|
+
end
|
280
|
+
|
281
|
+
it "should delay between intervals as specified in :delay" do
|
282
|
+
begin
|
283
|
+
pre = Time.now
|
284
|
+
EH::retry(:exception_filter => [RuntimeError], :delay => 0.1, :threshold => 6) do
|
285
|
+
raise RuntimeError
|
286
|
+
end
|
287
|
+
rescue => e
|
288
|
+
end
|
289
|
+
post = Time.now
|
290
|
+
(post - pre > 0.6).should == true
|
291
|
+
end
|
292
|
+
|
293
|
+
it "should log the message specified with the exception appended, using the logger specified" do
|
294
|
+
@logger.should_receive(:error).with("the message: RuntimeError")
|
295
|
+
begin
|
296
|
+
EH::retry!(:logger => @logger, :message => "the message") do
|
297
|
+
raise RuntimeError
|
298
|
+
end
|
299
|
+
rescue => e
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
it "should log the message specified with the exception appended, if the exception is in :exception_filter, using the logger specified" do
|
304
|
+
@logger.should_receive(:error).with("the message: RuntimeError")
|
305
|
+
begin
|
306
|
+
EH::retry!(:logger => @logger, :message => "the message", :exception_filter => [RuntimeError]) do
|
307
|
+
raise RuntimeError
|
308
|
+
end
|
309
|
+
rescue => e
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
it "should not log, if :exception_filter is specified and the exception is not in :exception_filter" do
|
314
|
+
@logger.should_not_receive(:error)
|
315
|
+
begin
|
316
|
+
EH::retry!(:logger => @logger, :message => "the message", :exception_filter => [RuntimeError]) do
|
317
|
+
raise IOError
|
318
|
+
end
|
319
|
+
rescue => e
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
it "should log using the level specified" do
|
324
|
+
@logger.should_receive(:warn).with("the message: RuntimeError")
|
325
|
+
begin
|
326
|
+
EH::retry!(:logger => @logger, :message => "the message", :level => EH::WARN) do
|
327
|
+
raise RuntimeError
|
328
|
+
end
|
329
|
+
rescue => e
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
context "when asked to log" do
|
336
|
+
it "should log to a single logger provided, with the message specified, at the level specified" do
|
337
|
+
@logger.should_receive(:info).with("testing single logging")
|
338
|
+
EH::log(@logger, "testing single logging", EH::INFO)
|
339
|
+
end
|
340
|
+
|
341
|
+
it "should log to a single logger provided, with the message specified, at the level specified" do
|
342
|
+
@logger2 = MockLogger.new
|
343
|
+
|
344
|
+
@logger.should_receive(:debug).with("testing single logging")
|
345
|
+
@logger2.should_receive(:debug).with("testing single logging")
|
346
|
+
EH::log([@logger, @logger2], "testing single logging", EH::DEBUG)
|
347
|
+
end
|
348
|
+
|
349
|
+
it "should log at all log levels" do
|
350
|
+
@logger.should_receive(:info)
|
351
|
+
EH::log(@logger, "info", EH::INFO)
|
352
|
+
|
353
|
+
@logger.should_receive(:debug)
|
354
|
+
EH::log(@logger, "debug", EH::DEBUG)
|
355
|
+
|
356
|
+
@logger.should_receive(:error)
|
357
|
+
EH::log(@logger, "error", EH::ERROR)
|
358
|
+
|
359
|
+
@logger.should_receive(:warn)
|
360
|
+
EH::log(@logger, "warn", EH::WARN)
|
361
|
+
|
362
|
+
@logger.should_receive(:fatal)
|
363
|
+
EH::log(@logger, "fatal", EH::FATAL)
|
364
|
+
end
|
365
|
+
|
366
|
+
it "should log using 'warn' if a nil single logger is provided" do
|
367
|
+
EH::should_receive(:warn).with "fatal: fatal"
|
368
|
+
|
369
|
+
EH::log(nil, "fatal", EH::FATAL)
|
370
|
+
end
|
371
|
+
|
372
|
+
it "should not log if nil is provided in a list of loggers, and 'warn' should not be called" do
|
373
|
+
@logger::should_receive(:fatal).once().with "fatal"
|
374
|
+
EH::should_not_receive(:warn)
|
375
|
+
|
376
|
+
EH::log([nil, @logger], "fatal", EH::FATAL)
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
end
|
data/spec/mock_logger.rb
ADDED
metadata
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: eh
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Ernst ban Graan
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-03-06 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.3'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.3'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: simplecov
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
description: Error handler gem that allows wrapping of code blocks with support for
|
79
|
+
block retry, logging, exception filtering and re-raise
|
80
|
+
email:
|
81
|
+
- ernst.van.graan@hetzner.co.za
|
82
|
+
executables: []
|
83
|
+
extensions: []
|
84
|
+
extra_rdoc_files: []
|
85
|
+
files:
|
86
|
+
- .gitignore
|
87
|
+
- Gemfile
|
88
|
+
- LICENSE.txt
|
89
|
+
- README.md
|
90
|
+
- Rakefile
|
91
|
+
- eh.gemspec
|
92
|
+
- lib/eh/eh.rb
|
93
|
+
- lib/eh/version.rb
|
94
|
+
- lib/error_handler.rb
|
95
|
+
- spec/eh_spec.rb
|
96
|
+
- spec/mock_logger.rb
|
97
|
+
homepage: ''
|
98
|
+
licenses:
|
99
|
+
- MIT
|
100
|
+
post_install_message:
|
101
|
+
rdoc_options: []
|
102
|
+
require_paths:
|
103
|
+
- lib
|
104
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
segments:
|
111
|
+
- 0
|
112
|
+
hash: 798203344795618863
|
113
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
114
|
+
none: false
|
115
|
+
requirements:
|
116
|
+
- - ! '>='
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
segments:
|
120
|
+
- 0
|
121
|
+
hash: 798203344795618863
|
122
|
+
requirements: []
|
123
|
+
rubyforge_project:
|
124
|
+
rubygems_version: 1.8.24
|
125
|
+
signing_key:
|
126
|
+
specification_version: 3
|
127
|
+
summary: Error handler gem that allows wrapping of code blocks with support for block
|
128
|
+
retry, logging, exception filtering and re-raise
|
129
|
+
test_files:
|
130
|
+
- spec/eh_spec.rb
|
131
|
+
- spec/mock_logger.rb
|