babysitter 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 +19 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +88 -0
- data/Rakefile +1 -0
- data/babysitter.gemspec +26 -0
- data/lib/babysitter.rb +36 -0
- data/lib/babysitter/configuration.rb +22 -0
- data/lib/babysitter/counter.rb +60 -0
- data/lib/babysitter/exception_notifiers.rb +1 -0
- data/lib/babysitter/exception_notifiers/simple_notification_service.rb +42 -0
- data/lib/babysitter/logger_with_stats.rb +43 -0
- data/lib/babysitter/logging.rb +9 -0
- data/lib/babysitter/monitor.rb +57 -0
- data/lib/babysitter/null_logger.rb +11 -0
- data/lib/babysitter/tracker.rb +52 -0
- data/lib/babysitter/version.rb +3 -0
- data/spec/lib/babysitter/configuration_spec.rb +46 -0
- data/spec/lib/babysitter/exception_notifiers/simple_notification_service_spec.rb +105 -0
- data/spec/lib/babysitter/monitor_spec.rb +231 -0
- data/spec/lib/babysitter/tracker_spec.rb +81 -0
- data/spec/lib/babysitter_spec.rb +50 -0
- data/spec/spec_helper.rb +21 -0
- metadata +160 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Lonely Planet Online
|
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,88 @@
|
|
1
|
+
# Babysitter
|
2
|
+
|
3
|
+
A ruby gem that uses [Fozzie](http://github.com/lonelyplanet/fozzie) to report progress on long-running tasks.
|
4
|
+
When provided with a Logger it will output statistics of progress to the logs.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
gem "babysitter"
|
11
|
+
|
12
|
+
And then execute:
|
13
|
+
|
14
|
+
$ bundle install
|
15
|
+
|
16
|
+
Or install it yourself as:
|
17
|
+
|
18
|
+
$ gem install babysitter
|
19
|
+
|
20
|
+
## Use
|
21
|
+
|
22
|
+
|
23
|
+
### Configuration
|
24
|
+
|
25
|
+
Babysitter.configure do |c|
|
26
|
+
c.logger = MyApp.logger
|
27
|
+
end
|
28
|
+
|
29
|
+
The default logger does nothing, override if you want to see log output.
|
30
|
+
|
31
|
+
### Amazon Simple Notification Service Integration
|
32
|
+
|
33
|
+
Babysitter can also make use of Amazons Simple Notification Service to provide notifications of when exceptions occur.
|
34
|
+
|
35
|
+
Babysitter.configure do |c|
|
36
|
+
c.enable_simple_notification_service(
|
37
|
+
topic_arn: "my-topic-arn",
|
38
|
+
credentials: -> {
|
39
|
+
access_key_id: "YOUR_ACCESS_KEY_ID",
|
40
|
+
secret_address_key: "YOUR_SECRET_ADDRESS_KEY",
|
41
|
+
}
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
The credentials should be supplied as a Proc so that they are only requested when they are required. This supports the use of temporary
|
46
|
+
credentials with IAM and prevents any credentials fetched at configuration time having expired at some point later when an error occurs.
|
47
|
+
|
48
|
+
### Monitoring
|
49
|
+
|
50
|
+
monitor = Babysitter.monitor("statsd.bucket.name")
|
51
|
+
monitor.start("Workername: description") do |tracker|
|
52
|
+
things_to_do.each do |work|
|
53
|
+
do_some work
|
54
|
+
tracker.error(:badness,'Something bad happened') if something_bad?
|
55
|
+
tracker.warn(:suspicions,'Something supicious happenedd') if something_bad?
|
56
|
+
tracker.inc("Workername: {{count}} tasks completed", 1, counting: :things_to_do) # report progress here
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
This will send statistics to StatsD in the supplied bucket name and will generate logs like this:
|
62
|
+
|
63
|
+
|
64
|
+
INFO -- : Start: statsd.bucket.name Matcher generating possible combinations
|
65
|
+
INFO -- : Done: 100 combinations generated
|
66
|
+
INFO -- : Rate: 20746.88796680498 combinations per second
|
67
|
+
INFO -- : End: statsd.bucket.name
|
68
|
+
|
69
|
+
Logging statistics will incremented for bucket names
|
70
|
+
|
71
|
+
statsd.bucket.name.badness.errors
|
72
|
+
statsd.bucket.name.suspicions.warnings
|
73
|
+
|
74
|
+
|
75
|
+
Any exceptions that occur will be logged nicely. Exceptions will abort the process.
|
76
|
+
|
77
|
+
## Development
|
78
|
+
|
79
|
+
$ git clone git@github.com:lonelyplanet/babysitter.git
|
80
|
+
$ cd babysitter
|
81
|
+
|
82
|
+
## Contributing
|
83
|
+
|
84
|
+
1. Fork it
|
85
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
86
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
87
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
88
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/babysitter.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'babysitter/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "babysitter"
|
8
|
+
gem.version = Babysitter::VERSION
|
9
|
+
gem.authors = ["Nicolas Overloop", "Paul Grayson", "Andy Roberts", "Mike Wagg"]
|
10
|
+
gem.email = ["noverloop@gmail.com", "paul.grayson@lonelyplanet.com", "coder@onesandthrees.com", "michael@guerillatactics.co.uk"]
|
11
|
+
gem.description = %q{Babysits long-running processes and reports progress or failures}
|
12
|
+
gem.summary = %q{Babysits long-running processes and reports progress or failures}
|
13
|
+
gem.homepage = ""
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
gem.add_dependency 'fozzie'
|
20
|
+
gem.add_dependency 'timecop'
|
21
|
+
gem.add_dependency 'aws-sdk'
|
22
|
+
|
23
|
+
gem.add_development_dependency 'awesome_print'
|
24
|
+
gem.add_development_dependency 'rspec'
|
25
|
+
|
26
|
+
end
|
data/lib/babysitter.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
require_relative "babysitter/version"
|
4
|
+
require_relative "babysitter/null_logger"
|
5
|
+
require_relative "babysitter/configuration"
|
6
|
+
require_relative "babysitter/logging"
|
7
|
+
require_relative "babysitter/logger_with_stats"
|
8
|
+
require_relative "babysitter/tracker"
|
9
|
+
require_relative "babysitter/monitor"
|
10
|
+
require_relative "babysitter/counter"
|
11
|
+
require_relative "babysitter/exception_notifiers"
|
12
|
+
require 'fozzie'
|
13
|
+
|
14
|
+
module Babysitter
|
15
|
+
|
16
|
+
def self.monitor(*args)
|
17
|
+
Monitor.new(*args)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.configuration
|
21
|
+
@configuration ||= Configuration.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.configure
|
25
|
+
yield configuration
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.logger
|
29
|
+
configuration.logger
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.exception_notifiers
|
33
|
+
configuration.exception_notifiers
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Babysitter
|
2
|
+
|
3
|
+
class Configuration
|
4
|
+
|
5
|
+
attr_writer :logger
|
6
|
+
attr_reader :exception_notifiers
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@exception_notifiers = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def logger
|
13
|
+
@logger ||= NullLogger.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def enable_simple_notification_service(opts = {})
|
17
|
+
@exception_notifiers << ExceptionNotifiers::SimpleNotificationService.new(opts)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Babysitter
|
2
|
+
class Counter
|
3
|
+
include Logging
|
4
|
+
|
5
|
+
attr_reader :count, :log_every, :template, :logged_count, :stat_name, :counting
|
6
|
+
|
7
|
+
attr_accessor :template
|
8
|
+
|
9
|
+
def initialize(log_every, opts)
|
10
|
+
@count = 0
|
11
|
+
@logged_count = 0
|
12
|
+
@log_every = log_every
|
13
|
+
@stat_name = opts.delete(:stat_name)
|
14
|
+
@counting = opts.delete(:counting)
|
15
|
+
@timer_start = Time.now
|
16
|
+
end
|
17
|
+
|
18
|
+
def inc( template, amount=1, opts={} )
|
19
|
+
@template = template
|
20
|
+
new_counting = opts.delete(:counting)
|
21
|
+
@counting = new_counting unless new_counting.nil?
|
22
|
+
log_this_time = block_number(@count) != block_number(@count + amount)
|
23
|
+
@count += amount
|
24
|
+
log_counter_messsage if log_this_time
|
25
|
+
end
|
26
|
+
|
27
|
+
def block_number(count)
|
28
|
+
count / @log_every
|
29
|
+
end
|
30
|
+
|
31
|
+
def final_report?
|
32
|
+
!(template.nil? or template.empty?) && count != logged_count
|
33
|
+
end
|
34
|
+
|
35
|
+
def log_counter_messsage
|
36
|
+
logger.info( "Done: #{template.gsub("{{count}}", count.to_s)}" )
|
37
|
+
send_progress_stats(count - logged_count)
|
38
|
+
|
39
|
+
rate = (count - logged_count).to_f / (Time.now - @timer_start)
|
40
|
+
logger.info( "Rate: #{rate} #{counting} per second" )
|
41
|
+
send_rate_stats(rate)
|
42
|
+
|
43
|
+
@logged_count = count
|
44
|
+
@timer_start = Time.now
|
45
|
+
end
|
46
|
+
|
47
|
+
def send_rate_stats(rate)
|
48
|
+
Stats.gauge stat_name+[counting, :rate], rate unless stat_name.nil?
|
49
|
+
end
|
50
|
+
|
51
|
+
def send_progress_stats(progress)
|
52
|
+
Stats.count stat_name+[counting, :progress], progress unless stat_name.nil?
|
53
|
+
end
|
54
|
+
|
55
|
+
def send_total_stats
|
56
|
+
Stats.gauge stat_name+[counting, :total], count
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require_relative "exception_notifiers/simple_notification_service"
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
|
3
|
+
module Babysitter
|
4
|
+
module ExceptionNotifiers
|
5
|
+
class SimpleNotificationService
|
6
|
+
def initialize(opts = {})
|
7
|
+
@topic_arn = opts.delete(:topic_arn)
|
8
|
+
@get_credentials = opts.delete(:credentials)
|
9
|
+
raise ArgumentError, "topic_arn is required." if @topic_arn.nil?
|
10
|
+
raise ArgumentError, "credentials is required and must be a Proc." if @get_credentials.nil? || !@get_credentials.is_a?(Proc)
|
11
|
+
|
12
|
+
validate_topic
|
13
|
+
end
|
14
|
+
|
15
|
+
def notify(subject, msg)
|
16
|
+
topic.publish(msg, subject: sanitise_subject(subject))
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def sns
|
22
|
+
AWS::SNS.new(@get_credentials.call)
|
23
|
+
end
|
24
|
+
|
25
|
+
def topic
|
26
|
+
sns.topics[@topic_arn]
|
27
|
+
end
|
28
|
+
|
29
|
+
def sanitise_subject(subject)
|
30
|
+
sanitised = /\s*([^\x00-\x1F]*)/.match(subject)[1]
|
31
|
+
|
32
|
+
return "(no subject)" if sanitised.empty?
|
33
|
+
return sanitised[0..96] + "..." if sanitised.size > 100
|
34
|
+
sanitised
|
35
|
+
end
|
36
|
+
|
37
|
+
def validate_topic
|
38
|
+
topic.display_name
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Babysitter
|
2
|
+
class LoggerWithStats
|
3
|
+
include Logging
|
4
|
+
|
5
|
+
attr_accessor :stat_name_prefix
|
6
|
+
|
7
|
+
STATS_SUFFIX_BY_METHOD = { warn: :warnings, error: :errors, fatal: :fatals }
|
8
|
+
|
9
|
+
def initialize(stat_name_prefix)
|
10
|
+
@stat_name_prefix = stat_name_prefix
|
11
|
+
end
|
12
|
+
|
13
|
+
def method_missing(meth, *opts)
|
14
|
+
unless %w{ info debug error fatal}.include?(meth.to_s)
|
15
|
+
super
|
16
|
+
return
|
17
|
+
end
|
18
|
+
stats_suffix_from_method(meth).tap{ |suffix| increment(suffix) if suffix }
|
19
|
+
logger.send(meth, *opts)
|
20
|
+
end
|
21
|
+
|
22
|
+
def warn(*opts)
|
23
|
+
increment(stats_suffix_from_method(:warn))
|
24
|
+
logger.warn(*opts)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def stats_suffix_from_method(meth)
|
30
|
+
STATS_SUFFIX_BY_METHOD[meth]
|
31
|
+
end
|
32
|
+
|
33
|
+
def increment(stat_name_suffix)
|
34
|
+
Stats.increment full_stat_name(stat_name_suffix)
|
35
|
+
end
|
36
|
+
|
37
|
+
def full_stat_name(stat_name_suffix)
|
38
|
+
stat_name_prefix + [stat_name_suffix]
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Babysitter
|
2
|
+
class Monitor
|
3
|
+
include Logging
|
4
|
+
|
5
|
+
attr_accessor :stat_name
|
6
|
+
|
7
|
+
def initialize(stat_name=nil)
|
8
|
+
@stat_name = convert_stat_name_to_array(stat_name)
|
9
|
+
end
|
10
|
+
|
11
|
+
def start(msg=nil, log_every=100, &blk)
|
12
|
+
raise ArgumentError, "Stats bucket name must not be blank" if stat_name.nil? or stat_name.empty?
|
13
|
+
log_msg = format_log_message(msg)
|
14
|
+
tracker = Tracker.new(log_every, stat_name)
|
15
|
+
logger.info "Start: #{log_msg}"
|
16
|
+
|
17
|
+
begin
|
18
|
+
result = Stats.time_to_do stat_name+[:overall] do
|
19
|
+
blk.call(tracker)
|
20
|
+
end
|
21
|
+
rescue Exception => e
|
22
|
+
tracker.final_report rescue nil
|
23
|
+
log_exception_details(log_msg, e)
|
24
|
+
raise
|
25
|
+
end
|
26
|
+
|
27
|
+
tracker.send_total_stats
|
28
|
+
tracker.final_report
|
29
|
+
logger.info "End: #{log_msg}"
|
30
|
+
result
|
31
|
+
end
|
32
|
+
|
33
|
+
def completed(msg)
|
34
|
+
logger.info "Done: #{msg}"
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def format_log_message(msg)
|
40
|
+
log_msg = stat_name.join('.')
|
41
|
+
[log_msg,msg].compact.join(' ')
|
42
|
+
end
|
43
|
+
|
44
|
+
def convert_stat_name_to_array(stat_name)
|
45
|
+
stat_name.is_a?(Array) ? stat_name : stat_name.split('.') unless stat_name.nil? or stat_name.empty?
|
46
|
+
end
|
47
|
+
|
48
|
+
def log_exception_details(msg, exception)
|
49
|
+
lines = ["Aborting: #{msg} due to exception #{exception.class}: #{exception}"]
|
50
|
+
lines.concat(exception.backtrace) if exception.backtrace
|
51
|
+
|
52
|
+
lines.each { |line| logger.error(line) }
|
53
|
+
Babysitter.exception_notifiers.each { |notifier| notifier.notify(exception.class.name, lines.join("\n")) }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Babysitter
|
2
|
+
class Tracker
|
3
|
+
include Logging
|
4
|
+
|
5
|
+
attr_reader :counting, :stat_name, :counter
|
6
|
+
attr_accessor :log_every
|
7
|
+
|
8
|
+
def initialize(log_every, stat_name=nil)
|
9
|
+
@stat_name = stat_name
|
10
|
+
@counting = :iterations
|
11
|
+
@log_every = log_every
|
12
|
+
@counter = Counter.new(log_every, stat_name: stat_name, counting: counting)
|
13
|
+
end
|
14
|
+
|
15
|
+
def inc(*args)
|
16
|
+
counter.inc(*args)
|
17
|
+
end
|
18
|
+
|
19
|
+
def count
|
20
|
+
counter.count
|
21
|
+
end
|
22
|
+
|
23
|
+
def final_report
|
24
|
+
counter.log_counter_messsage if counter.final_report?
|
25
|
+
end
|
26
|
+
|
27
|
+
def warn(topic_name, message)
|
28
|
+
logger_with_stats_for(topic_name).warn(message)
|
29
|
+
end
|
30
|
+
|
31
|
+
def error(topic_name, message)
|
32
|
+
logger_with_stats_for(topic_name).error(message)
|
33
|
+
end
|
34
|
+
|
35
|
+
def send_total_stats
|
36
|
+
counter.send_total_stats
|
37
|
+
end
|
38
|
+
|
39
|
+
def logger_with_stats_for(topic_name)
|
40
|
+
@loggers ||= {}
|
41
|
+
@loggers[topic_name] ||= LoggerWithStats.new(stats_prefix_for_topic(topic_name))
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def stats_prefix_for_topic(topic_name)
|
47
|
+
stat_name+[topic_name]
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Babysitter
|
4
|
+
describe Configuration do
|
5
|
+
context 'when initialized' do
|
6
|
+
|
7
|
+
let (:null_logger) { double( 'null logger' ) }
|
8
|
+
|
9
|
+
it 'has a default logger set to a new NullLogger' do
|
10
|
+
NullLogger.should_receive(:new).and_return( null_logger )
|
11
|
+
subject.logger.should === null_logger
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'has no exception notifiers' do
|
15
|
+
subject.exception_notifiers.should be_empty
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
describe 'enabling Amazon simple notification service integration' do
|
21
|
+
let (:sns_exception_notifier) { double }
|
22
|
+
let (:valid_params) { {
|
23
|
+
arbritary_key: "some-value",
|
24
|
+
topic_arn: "my-topic-arn"
|
25
|
+
} }
|
26
|
+
|
27
|
+
before :each do
|
28
|
+
Babysitter::ExceptionNotifiers::SimpleNotificationService.stub(:new).and_return(sns_exception_notifier)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'adds an exception notifier' do
|
32
|
+
subject.enable_simple_notification_service(valid_params)
|
33
|
+
|
34
|
+
subject.exception_notifiers.should_not be_empty
|
35
|
+
subject.exception_notifiers.first.should eql(sns_exception_notifier)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'configures the exception notifier' do
|
39
|
+
ExceptionNotifiers::SimpleNotificationService.should_receive(:new).with(hash_including(valid_params))
|
40
|
+
|
41
|
+
subject.enable_simple_notification_service(valid_params)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Babysitter
|
4
|
+
module ExceptionNotifiers
|
5
|
+
describe SimpleNotificationService do
|
6
|
+
subject { SimpleNotificationService.new(valid_opts) }
|
7
|
+
let(:get_credentials_lambda) { -> {
|
8
|
+
{
|
9
|
+
access_key_id: 'an-access-key-id',
|
10
|
+
secret_access_key: 'a-secret-address-key',
|
11
|
+
}
|
12
|
+
} }
|
13
|
+
let(:valid_opts) { {
|
14
|
+
credentials: get_credentials_lambda,
|
15
|
+
topic_arn: 'my-topic-arn'
|
16
|
+
} }
|
17
|
+
let(:sns) { double :sns, topics: { 'my-topic-arn' => topic } }
|
18
|
+
let(:topic) { double :topic, publish: nil, display_name: "A topic" }
|
19
|
+
before :each do
|
20
|
+
AWS::SNS.stub(:new).and_return(sns)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'requires a topic_arn' do
|
24
|
+
valid_opts.delete :topic_arn
|
25
|
+
-> { subject }.should raise_error(ArgumentError, /topic_arn/)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "requires credentials" do
|
29
|
+
valid_opts.delete :credentials
|
30
|
+
-> { subject }.should raise_error(ArgumentError, /credentials/)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'requires a block to retrieve AWS credentials' do
|
34
|
+
valid_opts[:credentials] = {}
|
35
|
+
-> { subject }.should raise_error(ArgumentError, /credentials/)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'uses the options passed to configure the credentials for sns' do
|
39
|
+
AWS::SNS.should_receive(:new).with(get_credentials_lambda.call)
|
40
|
+
subject
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'validates the topic by checking it has a display name' do
|
44
|
+
topic.should_receive(:display_name)
|
45
|
+
subject
|
46
|
+
end
|
47
|
+
|
48
|
+
describe '.notify' do
|
49
|
+
let(:message) { "the message" }
|
50
|
+
let(:notification_subject) { "the subject" }
|
51
|
+
|
52
|
+
it 'again uses the options passed to configure the credentials for sns' do
|
53
|
+
AWS::SNS.should_receive(:new).with(get_credentials_lambda.call).twice
|
54
|
+
subject.notify(notification_subject, message)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'publishes to the topic specified' do
|
58
|
+
topic.should_receive(:publish)
|
59
|
+
|
60
|
+
subject.notify(notification_subject, message)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'publishes the message' do
|
64
|
+
topic.should_receive(:publish).with(message, hash_including(subject: notification_subject))
|
65
|
+
|
66
|
+
subject.notify(notification_subject, message)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "shortens the subject to 100 characters if necessary" do
|
70
|
+
shortened_subject = 97.times.map { "x" }.join + "..."
|
71
|
+
original_subject = 101.times.map { "x" }.join
|
72
|
+
|
73
|
+
topic.should_receive(:publish).with(message, hash_including(subject: shortened_subject))
|
74
|
+
|
75
|
+
subject.notify(original_subject, message)
|
76
|
+
end
|
77
|
+
|
78
|
+
it "strips control characters" do
|
79
|
+
expected_subject = "this is the subject"
|
80
|
+
original_subject = "#{expected_subject}"
|
81
|
+
32.times.each { |code| original_subject << code.chr }
|
82
|
+
|
83
|
+
topic.should_receive(:publish).with(message, hash_including(subject: expected_subject))
|
84
|
+
|
85
|
+
subject.notify(original_subject, message)
|
86
|
+
end
|
87
|
+
|
88
|
+
it "strips leading whitespace" do
|
89
|
+
expected_subject = "this is the subject"
|
90
|
+
original_subject = " #{expected_subject}"
|
91
|
+
|
92
|
+
topic.should_receive(:publish).with(message, hash_including(subject: expected_subject))
|
93
|
+
|
94
|
+
subject.notify(original_subject, message)
|
95
|
+
end
|
96
|
+
|
97
|
+
it "handles empty subject" do
|
98
|
+
topic.should_receive(:publish).with(message, hash_including(subject: "(no subject)"))
|
99
|
+
|
100
|
+
subject.notify("", message)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,231 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Babysitter
|
4
|
+
describe Monitor do
|
5
|
+
before(:each) do
|
6
|
+
Stats.stub!(:count).with(anything, anything)
|
7
|
+
Stats.stub!(:gauge).with(anything, anything)
|
8
|
+
end
|
9
|
+
|
10
|
+
context 'when initialized with a dot separated bucket name' do
|
11
|
+
|
12
|
+
subject{ Monitor.new(bucket_name) }
|
13
|
+
let(:bucket_name) { 'my.splendid.bucket.name' }
|
14
|
+
let(:start_block) { Proc.new{ block_result } }
|
15
|
+
let(:block_result) { double('block result').as_null_object }
|
16
|
+
let(:logger) { double('logger').as_null_object }
|
17
|
+
|
18
|
+
describe '#completed' do
|
19
|
+
it 'logs a done message' do
|
20
|
+
Monitor.any_instance.stub(:logger).and_return(logger)
|
21
|
+
logger.should_receive(:info).with("Done: the completed thing")
|
22
|
+
subject.completed('the completed thing')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#start' do
|
27
|
+
|
28
|
+
it 'yields to the block, returning the result' do
|
29
|
+
subject.start(&start_block).should === block_result
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'calls Stats.time_to_do with the bucket name' do
|
33
|
+
expected_stat = bucket_name.split('.')+[:overall]
|
34
|
+
Stats.should_receive(:time_to_do).with(expected_stat)
|
35
|
+
subject.start(&start_block)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'calls logger.info with start message' do
|
39
|
+
Monitor.any_instance.stub(:logger).and_return(logger)
|
40
|
+
logger.should_receive(:info).with("Start: #{bucket_name}")
|
41
|
+
subject.start(&start_block)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'calls logger.info with end message' do
|
45
|
+
Monitor.any_instance.stub(:logger).and_return(logger)
|
46
|
+
logger.should_receive(:info).with("End: #{bucket_name}")
|
47
|
+
subject.start(&start_block)
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'when the start method is given a message' do
|
51
|
+
it 'calls logger.info with start message' do
|
52
|
+
Monitor.any_instance.stub(:logger).and_return(logger)
|
53
|
+
logger.should_receive(:info).with("Start: #{bucket_name} special message")
|
54
|
+
subject.start('special message', &start_block)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'calls logger.info with end message' do
|
58
|
+
Monitor.any_instance.stub(:logger).and_return(logger)
|
59
|
+
logger.should_receive(:info).with("End: #{bucket_name} special message")
|
60
|
+
subject.start('special message', &start_block)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'when the block increments the counter twice, each with a count of 5, and identifies counted objects' do
|
65
|
+
let(:start_block_two_increments) do
|
66
|
+
Proc.new do |counter|
|
67
|
+
2.times{ counter.inc('incrementing by {{count}} things', 5, counting: :things) }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'calls Stats.count with bucket name plus counted objects, and a count of 10' do
|
72
|
+
expected_bucket_name = bucket_name.split('.') + [:things, :total]
|
73
|
+
Stats.should_receive(:gauge).with(expected_bucket_name, 10)
|
74
|
+
subject.start(&start_block_two_increments)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'calls logger.info with each done message once' do
|
78
|
+
Counter.any_instance.stub(:logger).and_return(logger)
|
79
|
+
[5,10].each { |inc| logger.should_receive(:info).with( "Done: incrementing by #{inc} things").once }
|
80
|
+
subject.start('short message', 5, &start_block_two_increments)
|
81
|
+
end
|
82
|
+
end # context 'when the block increments the counter twice, each with a count of 5, and identifies counted objects'
|
83
|
+
|
84
|
+
context 'when the block increments the counter 7 times, with no amount specified, and no name for counted objects' do
|
85
|
+
let(:start_block_seven_increments) do
|
86
|
+
Proc.new do |counter|
|
87
|
+
7.times{ counter.inc('incrementing by {{count}} things') }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'calls Stats.count with bucket name plus iterations, and a count of 7' do
|
92
|
+
expected_bucket_name = bucket_name.split('.') + [:iterations, :total]
|
93
|
+
Stats.should_receive(:gauge).with(expected_bucket_name, 7)
|
94
|
+
subject.start(&start_block_seven_increments)
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'calls logger.info with each done message once' do
|
98
|
+
Counter.any_instance.stub(:logger).and_return(logger)
|
99
|
+
[5,7].each { |inc| logger.should_receive(:info).with( "Done: incrementing by #{inc} things").once }
|
100
|
+
subject.start('short message', 5, &start_block_seven_increments)
|
101
|
+
end
|
102
|
+
end # context 'when the block increments the counter 7 times, with no amount specified, and no name for counted objects'
|
103
|
+
|
104
|
+
context 'when logging every 10th call, and the block increments the counter 7 times, each with a count of 9, and identifies counted objects' do
|
105
|
+
let(:start_block_three_increments) do
|
106
|
+
Proc.new do |counter|
|
107
|
+
7.times{ counter.inc('incrementing by {{count}} things', 9, counting: :things) }
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'calls logger.info with increments 18,27,36,45,54,63' do
|
112
|
+
Counter.any_instance.stub(:logger).and_return(logger)
|
113
|
+
[18,27,36,45,54,63].each { |inc| logger.should_receive(:info).with( "Done: incrementing by #{inc} things").once }
|
114
|
+
subject.start('short message', 10, &start_block_three_increments)
|
115
|
+
end
|
116
|
+
end # context 'when logging every 10th call, and the block increments the counter 7 times, each with a count of 9, and identifies counted objects' do
|
117
|
+
|
118
|
+
context "when the block logs a warning" do
|
119
|
+
let(:start_block_with_warning) do
|
120
|
+
Proc.new do |monitor|
|
121
|
+
monitor.warn(:my_warning_bucket, 'my warning message')
|
122
|
+
end
|
123
|
+
end
|
124
|
+
before(:each) do
|
125
|
+
Babysitter.stub(:logger).and_return(logger)
|
126
|
+
logger.stub!(:warn)
|
127
|
+
Stats.stub!(:increment)
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'calls logger.info with the warning message' do
|
131
|
+
logger.should_receive(:warn).with( "my warning message")
|
132
|
+
subject.start(&start_block_with_warning)
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'calls Stats.count with warning bucket name' do
|
136
|
+
expected_bucket_name = bucket_name.split('.') + [:my_warning_bucket, :warnings]
|
137
|
+
Stats.should_receive(:increment).with(expected_bucket_name)
|
138
|
+
subject.start(&start_block_with_warning)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
context "when the block logs an error" do
|
143
|
+
let(:start_block_with_error) do
|
144
|
+
Proc.new do |monitor|
|
145
|
+
monitor.error(:my_error_bucket, 'my error message')
|
146
|
+
end
|
147
|
+
end
|
148
|
+
before(:each) do
|
149
|
+
Babysitter.stub(:logger).and_return(logger)
|
150
|
+
logger.stub!(:error)
|
151
|
+
Stats.stub!(:increment)
|
152
|
+
end
|
153
|
+
|
154
|
+
it 'calls logger.error with the error message' do
|
155
|
+
logger.should_receive(:error).with( "my error message")
|
156
|
+
subject.start(&start_block_with_error)
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'calls Stats.count with error bucket name' do
|
160
|
+
expected_bucket_name = bucket_name.split('.') + [:my_error_bucket, :errors]
|
161
|
+
Stats.should_receive(:increment).with(expected_bucket_name)
|
162
|
+
subject.start(&start_block_with_error)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
context 'when the block raises an error' do
|
167
|
+
let(:error) { RuntimeError.new(error_message) }
|
168
|
+
let(:backtrace) { 3.times.map { |i| "Line #{i}"} }
|
169
|
+
let(:error_message) { 'A big fat error' }
|
170
|
+
let(:expected_message) { "Aborting: #{bucket_name} due to exception RuntimeError: #{error_message}" }
|
171
|
+
let(:start_block_with_error) { Proc.new { raise error } }
|
172
|
+
before(:each) do
|
173
|
+
error.stub(:backtrace).and_return(backtrace)
|
174
|
+
Babysitter.stub(:logger).and_return(logger)
|
175
|
+
Babysitter.stub(:exception_notifiers).and_return(2.times.map { double notify: nil })
|
176
|
+
logger.stub!(:error)
|
177
|
+
Stats.stub!(:increment)
|
178
|
+
end
|
179
|
+
|
180
|
+
it 'calls logger.error with the exeption details' do
|
181
|
+
logger.should_receive(:error).with(expected_message)
|
182
|
+
backtrace.each do |line|
|
183
|
+
logger.should_receive(:error).with(/\w*#{line}/)
|
184
|
+
end
|
185
|
+
|
186
|
+
begin
|
187
|
+
subject.start(&start_block_with_error)
|
188
|
+
rescue
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'calls each exception notifier with the exception details' do
|
193
|
+
message = [expected_message].concat(backtrace).join("\n")
|
194
|
+
|
195
|
+
Babysitter.exception_notifiers.each do |exception_notifier|
|
196
|
+
exception_notifier.should_receive(:notify).with('RuntimeError', message)
|
197
|
+
end
|
198
|
+
|
199
|
+
begin
|
200
|
+
subject.start(&start_block_with_error)
|
201
|
+
rescue
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
context 'when the block increments 2 times at intervals of 2 seconds' do
|
207
|
+
let(:start_block_for_timing) do
|
208
|
+
Proc.new do |counter|
|
209
|
+
2.times do
|
210
|
+
Timecop.travel(Time.now+2) # move on 2 seconds
|
211
|
+
counter.inc('doing increment',1)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
before(:each) { Timecop.travel(Time.now) }
|
216
|
+
after(:each) { Timecop.return }
|
217
|
+
|
218
|
+
it 'calculates a rate close to 0.5 per second' do
|
219
|
+
Counter.any_instance.should_receive(:send_rate_stats) do |rate|
|
220
|
+
rate.should be_within(0.01).of(0.5)
|
221
|
+
end
|
222
|
+
subject.start(&start_block_for_timing)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
end
|
227
|
+
|
228
|
+
end
|
229
|
+
|
230
|
+
end
|
231
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Babysitter
|
4
|
+
describe Tracker do
|
5
|
+
|
6
|
+
subject{ Tracker.new(log_interval, stat_bucket_prefix) }
|
7
|
+
let(:log_interval) { 5 }
|
8
|
+
let(:stat_bucket_prefix) { [:my, :stat, :bucket, :prefix] }
|
9
|
+
let(:logger) { double('boring old vanilla babysitter logger') }
|
10
|
+
before(:each) do
|
11
|
+
Babysitter.stub(:logger).and_return(logger)
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '#logger_with_stats_for' do
|
15
|
+
|
16
|
+
it 'returns the same logger when passed the same symbol twice' do
|
17
|
+
l1 = subject.logger_with_stats_for(:lodgings)
|
18
|
+
l2 = subject.logger_with_stats_for(:lodgings)
|
19
|
+
l1.should be_equal(l2)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'returns different loggers when passed different symbols' do
|
23
|
+
l = subject.logger_with_stats_for(:lodgings)
|
24
|
+
p = subject.logger_with_stats_for(:places)
|
25
|
+
l.should_not be_equal(p)
|
26
|
+
end
|
27
|
+
|
28
|
+
end # describe '#logger_with_stats' do
|
29
|
+
|
30
|
+
describe 'logger returned by logger_with_stats_for(some_topic)' do
|
31
|
+
subject{ Tracker.new(log_interval, stat_bucket_prefix).logger_with_stats_for(some_topic) }
|
32
|
+
let(:some_topic) { :some_topic }
|
33
|
+
let(:text_of_the_message) {'the message we want in the logs'}
|
34
|
+
|
35
|
+
{ warn: :warnings, error: :errors, fatal: :fatals }.each do |message_type, stats_bucket_suffix|
|
36
|
+
describe "##{message_type}" do
|
37
|
+
before(:each) do
|
38
|
+
logger.stub(message_type)
|
39
|
+
Stats.stub!(:increment)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'logs the message' do
|
43
|
+
logger.should_receive(message_type).with(text_of_the_message)
|
44
|
+
subject.send(message_type, text_of_the_message)
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'sends the stats' do
|
48
|
+
expected_stats_bucket = stat_bucket_prefix + [some_topic, stats_bucket_suffix]
|
49
|
+
Stats.should_receive(:increment).with(expected_stats_bucket)
|
50
|
+
subject.send(message_type, text_of_the_message)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
[:info, :debug].each do |message_type|
|
57
|
+
describe "##{message_type}" do
|
58
|
+
before(:each) do
|
59
|
+
logger.stub(message_type)
|
60
|
+
Stats.stub!(:increment)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'logs the message' do
|
64
|
+
logger.should_receive(message_type).with(text_of_the_message)
|
65
|
+
subject.send(message_type, text_of_the_message)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'sends no stats' do
|
69
|
+
Stats.should_not_receive(:increment)
|
70
|
+
subject.send(message_type, text_of_the_message)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
end # describe 'logger returned by logger_with_stats_for(:something)' do
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Babysitter do
|
4
|
+
|
5
|
+
describe '.monitor' do
|
6
|
+
it 'returns an instance of Monitor' do
|
7
|
+
Babysitter.monitor.should be_an_instance_of(Babysitter::Monitor)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '.configuration' do
|
12
|
+
it 'returns an instance of Configuration' do
|
13
|
+
Babysitter.configuration.should be_an_instance_of(Babysitter::Configuration)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'returns the same instance every time' do
|
17
|
+
c = Babysitter.configuration
|
18
|
+
Babysitter.configuration.should eql(c)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '.configure' do
|
23
|
+
describe 'object yielded to block' do
|
24
|
+
it 'is the unique configuration object' do
|
25
|
+
Babysitter.configure do |c|
|
26
|
+
c.should eql(Babysitter.configuration)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe '.logger' do
|
33
|
+
let(:configured_logger) { double('configured logger').as_null_object }
|
34
|
+
|
35
|
+
it 'returns the logger from the configuration' do
|
36
|
+
Babysitter::Configuration.any_instance.stub(:logger).and_return(configured_logger)
|
37
|
+
Babysitter.logger.should eql(configured_logger)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '.exception_notifiers' do
|
42
|
+
let(:exception_notifiers) { double('notifiers').as_null_object}
|
43
|
+
|
44
|
+
it 'returns the notifiers from the configuration' do
|
45
|
+
Babysitter::Configuration.any_instance.stub(:exception_notifiers).and_return(exception_notifiers)
|
46
|
+
Babysitter.exception_notifiers.should eql(exception_notifiers)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
4
|
+
# loaded once.
|
5
|
+
#
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
require 'awesome_print'
|
8
|
+
require 'babysitter'
|
9
|
+
require 'timecop'
|
10
|
+
|
11
|
+
RSpec.configure do |config|
|
12
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
13
|
+
config.run_all_when_everything_filtered = true
|
14
|
+
config.filter_run :focus
|
15
|
+
|
16
|
+
# Run specs in random order to surface order dependencies. If you find an
|
17
|
+
# order dependency and want to debug it, you can fix the order by providing
|
18
|
+
# the seed, which is printed after each run.
|
19
|
+
# --seed 1234
|
20
|
+
config.order = 'random'
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: babysitter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Nicolas Overloop
|
9
|
+
- Paul Grayson
|
10
|
+
- Andy Roberts
|
11
|
+
- Mike Wagg
|
12
|
+
autorequire:
|
13
|
+
bindir: bin
|
14
|
+
cert_chain: []
|
15
|
+
date: 2013-02-13 00:00:00.000000000 Z
|
16
|
+
dependencies:
|
17
|
+
- !ruby/object:Gem::Dependency
|
18
|
+
name: fozzie
|
19
|
+
requirement: !ruby/object:Gem::Requirement
|
20
|
+
none: false
|
21
|
+
requirements:
|
22
|
+
- - ! '>='
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: '0'
|
25
|
+
type: :runtime
|
26
|
+
prerelease: false
|
27
|
+
version_requirements: !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: timecop
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
none: false
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
type: :runtime
|
42
|
+
prerelease: false
|
43
|
+
version_requirements: !ruby/object:Gem::Requirement
|
44
|
+
none: false
|
45
|
+
requirements:
|
46
|
+
- - ! '>='
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: aws-sdk
|
51
|
+
requirement: !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ! '>='
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
type: :runtime
|
58
|
+
prerelease: false
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ! '>='
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
- !ruby/object:Gem::Dependency
|
66
|
+
name: awesome_print
|
67
|
+
requirement: !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
69
|
+
requirements:
|
70
|
+
- - ! '>='
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '0'
|
73
|
+
type: :development
|
74
|
+
prerelease: false
|
75
|
+
version_requirements: !ruby/object:Gem::Requirement
|
76
|
+
none: false
|
77
|
+
requirements:
|
78
|
+
- - ! '>='
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
name: rspec
|
83
|
+
requirement: !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ! '>='
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
type: :development
|
90
|
+
prerelease: false
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
92
|
+
none: false
|
93
|
+
requirements:
|
94
|
+
- - ! '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: Babysits long-running processes and reports progress or failures
|
98
|
+
email:
|
99
|
+
- noverloop@gmail.com
|
100
|
+
- paul.grayson@lonelyplanet.com
|
101
|
+
- coder@onesandthrees.com
|
102
|
+
- michael@guerillatactics.co.uk
|
103
|
+
executables: []
|
104
|
+
extensions: []
|
105
|
+
extra_rdoc_files: []
|
106
|
+
files:
|
107
|
+
- .gitignore
|
108
|
+
- Gemfile
|
109
|
+
- LICENSE.txt
|
110
|
+
- README.md
|
111
|
+
- Rakefile
|
112
|
+
- babysitter.gemspec
|
113
|
+
- lib/babysitter.rb
|
114
|
+
- lib/babysitter/configuration.rb
|
115
|
+
- lib/babysitter/counter.rb
|
116
|
+
- lib/babysitter/exception_notifiers.rb
|
117
|
+
- lib/babysitter/exception_notifiers/simple_notification_service.rb
|
118
|
+
- lib/babysitter/logger_with_stats.rb
|
119
|
+
- lib/babysitter/logging.rb
|
120
|
+
- lib/babysitter/monitor.rb
|
121
|
+
- lib/babysitter/null_logger.rb
|
122
|
+
- lib/babysitter/tracker.rb
|
123
|
+
- lib/babysitter/version.rb
|
124
|
+
- spec/lib/babysitter/configuration_spec.rb
|
125
|
+
- spec/lib/babysitter/exception_notifiers/simple_notification_service_spec.rb
|
126
|
+
- spec/lib/babysitter/monitor_spec.rb
|
127
|
+
- spec/lib/babysitter/tracker_spec.rb
|
128
|
+
- spec/lib/babysitter_spec.rb
|
129
|
+
- spec/spec_helper.rb
|
130
|
+
homepage: ''
|
131
|
+
licenses: []
|
132
|
+
post_install_message:
|
133
|
+
rdoc_options: []
|
134
|
+
require_paths:
|
135
|
+
- lib
|
136
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
143
|
+
none: false
|
144
|
+
requirements:
|
145
|
+
- - ! '>='
|
146
|
+
- !ruby/object:Gem::Version
|
147
|
+
version: '0'
|
148
|
+
requirements: []
|
149
|
+
rubyforge_project:
|
150
|
+
rubygems_version: 1.8.24
|
151
|
+
signing_key:
|
152
|
+
specification_version: 3
|
153
|
+
summary: Babysits long-running processes and reports progress or failures
|
154
|
+
test_files:
|
155
|
+
- spec/lib/babysitter/configuration_spec.rb
|
156
|
+
- spec/lib/babysitter/exception_notifiers/simple_notification_service_spec.rb
|
157
|
+
- spec/lib/babysitter/monitor_spec.rb
|
158
|
+
- spec/lib/babysitter/tracker_spec.rb
|
159
|
+
- spec/lib/babysitter_spec.rb
|
160
|
+
- spec/spec_helper.rb
|