tocsin 0.1.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.
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ *.swp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in tocsin.gemspec
4
+ gemspec
@@ -0,0 +1,56 @@
1
+ # Tocsin
2
+ Tocsin is a library designed to simply the task of notifying interested parties about your site's operation. You may already be tracking errors through [New Relic](http://www.newrelic.com), but not all errors are created equal -- there are probably parts of your site where an exception occuring is significantly more of a problem than a bored URL surfer visiting random links and throwing 404s. Tocsin can help you be informed when these parts of your site break, or when important events happen.
3
+
4
+ Currently, Tocsin works only in Rails 3, and supports notification via email.
5
+
6
+ ## Installation
7
+ Add Tocsin to your Gemfile:
8
+ <pre>
9
+ gem 'tocsin'
10
+ </pre>
11
+
12
+ Update your bundle:
13
+ <pre>
14
+ bundle install
15
+ </pre>
16
+
17
+ Use the provided Rake task to generate the migration and model needed by Tocsin. (Angry at the lack of normalization? Install [lookup_by](https://github.com/companygardener/lookup_by/) and rewrite the migration and model to use it; Tocsin won't even notice.)
18
+ <pre>
19
+ rake tocsin:install
20
+ </pre>
21
+
22
+ Lastly, configure Tocsin to be useful. Create an initializer in `config/initializers/tocsin.rb` that looks something like this:
23
+ <pre>
24
+ Tocsin.configure do |c|
25
+ c.from_address = 'me@mysite.com'
26
+
27
+ c.notify 'you@gmail.com', :of => { :category => /user_registrations/ }, :by => :email
28
+ c.notify ['you@gmail.com', 'sales@mysite.com'], :of => { :category => /new_sales/ } # N.B. 'email' is the default nofifier.
29
+ c.notify 'ops@mysite.com', :of => { :severity => /critical/ } # Values in the :of hash should be regexes.
30
+ end
31
+ </pre>
32
+
33
+ ## Usage
34
+ In anywhere you want to send yourself a notification:
35
+ <pre>
36
+ Tocsin.notify :category => :user_registrations,
37
+ :message => "User #{user.name} registered at #{Time.now}!"
38
+ </pre>
39
+
40
+ If you want to sound the alarm:
41
+ <pre>
42
+ begin
43
+ # ...
44
+ rescue => e
45
+ Tocsin.raise_alert e, :category => :user_registrations,
46
+ :severity => :critical,
47
+ :message => "An error occurred when a user tried to sign up!"
48
+ end
49
+ </pre>
50
+
51
+ In any code you want to watch for explosions:
52
+ <pre>
53
+ Tocsin.watch! :category => :important_stuff, :severity => :critical, :message => "Error doing important stuff!" do
54
+ Important::Stuff.do!
55
+ end
56
+ </pre>
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new('spec')
5
+
6
+ task :default => :spec
@@ -0,0 +1,159 @@
1
+ require "tocsin/version"
2
+ require "tocsin/notifiers"
3
+ require "tocsin/config"
4
+
5
+ require "active_record/errors"
6
+ require 'logger'
7
+
8
+ # Library for escalating and logging errors.
9
+ module Tocsin
10
+ # Raise an alarm and escalate as configured in etc/tocsin.yml.
11
+ # @param [Exception] exception exception object if one was raised
12
+ # @option options :severity severity of this notication. Should be low,
13
+ # medium, high, critical, or notification (arbitrary, though)
14
+ # @option options :message any info attached to this notification
15
+ # @option options :category totally arbitrary, but escalation can be configured based on
16
+ # this field.
17
+ # @return [Tocsin::Alert] the created alert
18
+ def self.raise_alert(exception, options={})
19
+ urgent = options.delete(:now) || options.delete(:urgent) || options.delete(:synchronous)
20
+ alert = alert_for(exception, options)
21
+
22
+ begin
23
+ urgent ? sound(alert) : queue(alert)
24
+ rescue => e
25
+ sound cannot_enqueue_alert(e)
26
+ sound alert
27
+ end
28
+
29
+ alert
30
+ end
31
+
32
+ # Synonyms for raise_alert when no exception is involved.
33
+ def self.notify(options)
34
+ self.raise_alert(nil, {:severity => "notification"}.merge(options))
35
+ end
36
+
37
+ def self.warn!(options)
38
+ self.raise_alert(nil, options)
39
+ end
40
+
41
+ # Watch the yielded block for exceptions, and log one if it's raised.
42
+ def self.watch(options={})
43
+ begin
44
+ yield
45
+ rescue => e
46
+ raise_alert(e, options)
47
+ end
48
+ end
49
+
50
+ # Same as watch, but re-raises the exception after catching it.
51
+ def self.watch!(options={})
52
+ begin
53
+ yield
54
+ rescue => e
55
+ raise_alert(e, options)
56
+ raise e
57
+ end
58
+ end
59
+
60
+ # Determine the recipients per notification method for a particular alert.
61
+ def self.recipients(alert)
62
+ # Each recipient group should look like (where the filter values are regexps):
63
+ # - category: .*
64
+ # severity: (critical|high)
65
+ # recipients:
66
+ # - rnubel@test.com
67
+ # notifier: email
68
+ return {} unless config.recipient_groups
69
+
70
+ recipients_per_notifier = config.recipient_groups.inject({}) do |rec_lists, group|
71
+ if alert_matches_group(alert, group)
72
+ notifier = group[:notifier] || Tocsin::Notifiers.default_notifier
73
+ rec_lists[notifier] ||= []
74
+ rec_lists[notifier] += group[:recipients]
75
+ end
76
+
77
+ rec_lists
78
+ end
79
+
80
+ recipients_per_notifier.each do |k, v| v.uniq!; v.sort! end # Filter duplicates and sort for sanity
81
+ end
82
+
83
+ def self.configure
84
+ @config = nil
85
+ yield config
86
+ end
87
+
88
+ def self.config
89
+ @config ||= Tocsin::Config.new
90
+ end
91
+
92
+ def self.queue
93
+ @queue ||= config.queue
94
+ end
95
+
96
+ def self.logger
97
+ @logger ||= config.logger
98
+ end
99
+
100
+ # Job to notify admins via email of a problem.
101
+ class NotificationJob
102
+ @queue = Tocsin.queue
103
+
104
+ # Look up the given alert and notify recipients of it.
105
+ def self.perform(alert_id)
106
+ Tocsin.sound(alert_id)
107
+ rescue ActiveRecord::RecordNotFound
108
+ Tocsin.logger.error { "Raised alert with ID=#{alert_id} but couldn't find that alert." }
109
+ end
110
+
111
+ end
112
+
113
+ private
114
+
115
+ def self.sound(alert)
116
+ alert = Tocsin::Alert.find(alert) unless alert.is_a?(Tocsin::Alert)
117
+ recipients = Tocsin.recipients(alert)
118
+
119
+ recipients.each do |notifier_key, recipient_list|
120
+ notifier = Tocsin::Notifiers[notifier_key]
121
+
122
+ if notifier && recipient_list.any?
123
+ notifier.notify(recipient_list, alert)
124
+ Tocsin.logger.info { "Notification sent to #{recipient_list.inspect} via #{notifier_key} for alert #{alert.id}." }
125
+ elsif recipient_list.empty?
126
+ Tocsin.logger.error { "No recipients associated with alert: \n #{alert.inspect}" }
127
+ elsif notifier.nil?
128
+ Tocsin.logger.error { "Raised alert with ID=#{alert.id} for unregistered notifier '#{notifier_key}'." }
129
+ end
130
+ end
131
+ end
132
+
133
+ def self.alert_for(exception, options = {})
134
+ alert = Tocsin::Alert.create(
135
+ :exception => exception && exception.to_s,
136
+ :backtrace => exception && exception.backtrace.join("\n"),
137
+ :severity => options[:severity].to_s || "",
138
+ :message => options[:message].to_s || "",
139
+ :category => options[:category].to_s || "uncategorized"
140
+ )
141
+ end
142
+
143
+ def self.queue(alert)
144
+ alert = Tocsin::Alert.find(alert) unless alert.is_a?(Tocsin::Alert)
145
+ Resque.enqueue(NotificationJob, alert.id)
146
+ end
147
+
148
+ def self.cannot_enqueue_alert(exception=nil)
149
+ logger.error "[Tocsin] Enqueuing alert job into Resque failed!"
150
+ alert_for exception, :severity => "critical",
151
+ :message => "[Tocsin] Enqueuing alert job into Resque failed!",
152
+ :category => "system"
153
+ end
154
+
155
+ def self.alert_matches_group(alert, group)
156
+ alert.category =~ Regexp.new(group[:category] || '.*') &&
157
+ alert.severity =~ Regexp.new(group[:severity] || '.*')
158
+ end
159
+ end
@@ -0,0 +1,32 @@
1
+ module Tocsin
2
+ class Config
3
+ attr_accessor :logger, :queue, :recipient_groups, :from_address
4
+
5
+ # notify [r1, r2], :of => filters, :by => notifier
6
+ def notify(recipients, parameters)
7
+ self.recipient_groups ||= []
8
+
9
+ recipients = [recipients] unless recipients.is_a? Array
10
+ filters = parameters[:of] || {}
11
+ notifier = parameters[:by] || Tocsin::Notifiers.default_notifier
12
+
13
+ group_config = { :recipients => recipients,
14
+ :notifier => notifier}.merge(filters)
15
+ self.recipient_groups.push(group_config)
16
+ end
17
+
18
+ def queue
19
+ @queue ||= :high
20
+ end
21
+
22
+ def logger
23
+ @logger ||= Rails.logger
24
+ rescue NameError, NoMethodError => e
25
+ ok = [ /^uninitialized constant .*Rails$/,
26
+ /^undefined method `logger'/
27
+ ].any?{|regex| e.message =~ regex }
28
+ raise unless ok
29
+ @logger ||= Logger.new($stderr)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,23 @@
1
+ module Tocsin
2
+ module Notifiers
3
+ def self.default_notifier
4
+ :email
5
+ end
6
+
7
+ def self.notifiers
8
+ @notifiers ||= {}
9
+ end
10
+
11
+ def self.register!(key, notifier)
12
+ notifiers.store(key, notifier)
13
+ end
14
+
15
+ def self.[](key)
16
+ notifiers[key]
17
+ end
18
+ end
19
+ end
20
+
21
+ Dir[File.expand_path("../notifiers/*.rb", __FILE__)].each do |f|
22
+ require f
23
+ end
@@ -0,0 +1,30 @@
1
+ require 'mail'
2
+
3
+ module Tocsin
4
+ module Notifiers
5
+ class EmailNotifier
6
+ def self.notify(recipients, alert)
7
+ _body = "
8
+ Alert raised by Tocsin on your site:
9
+ \n
10
+ Message: #{alert.message}
11
+ Category: #{alert.category}
12
+ Severity: #{alert.severity}
13
+ Exception: #{alert.exception}
14
+ Backtrace: #{alert.backtrace}
15
+ ";
16
+
17
+ from_address = Tocsin.config.from_address || recipients.first
18
+
19
+ Mail.deliver do
20
+ from from_address
21
+ to recipients.join(", ")
22
+ subject "[Tocsin] [#{alert.severity}] #{alert.message} (#{alert.category})"
23
+ body _body
24
+ end
25
+ end
26
+ end
27
+
28
+ register! :email, EmailNotifier
29
+ end
30
+ end
@@ -0,0 +1,13 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'tocsin'))
2
+
3
+ namespace :tocsin do
4
+ desc "Generate Tocsin::Alert model & migration"
5
+ task :generate do
6
+ if defined?(Rails)
7
+ puts `rails g model Tocsin::Alert exception:string message:string category:string severity:string backtrace:string --no-test-framework --skip`
8
+ puts "Run rake db:migrate to finish installation."
9
+ else
10
+ puts "Not using rails. Please create Tocsin::Alert manually"
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ module Tocsin
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,38 @@
1
+ require File.expand_path("../../lib/tocsin", __FILE__)
2
+
3
+ RSpec.configure do |c|
4
+ c.mock_with :mocha
5
+ end
6
+
7
+ Mail.defaults do
8
+ delivery_method :test
9
+ end
10
+
11
+ class Tocsin::Alert
12
+ def id
13
+ 1
14
+ end
15
+
16
+ def category
17
+ end
18
+
19
+ def severity
20
+ end
21
+
22
+ def self.create(*args)
23
+ self.new
24
+ end
25
+
26
+ def self.find(*args)
27
+ self.new
28
+ end
29
+
30
+ def self.logger
31
+ @logger ||= Logger.new($stdout)
32
+ end
33
+ end
34
+
35
+ class Resque
36
+ def self.enqueue(*args)
37
+ end
38
+ end
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+
3
+ describe Tocsin::Notifiers::EmailNotifier do
4
+ let(:alert) {
5
+ stub("Tocsin::Alert", :id => 1, :category => 'category', :severity => 'severity',
6
+ :message => 'message', :exception => 'exception',
7
+ :backtrace => 'backtrace')
8
+ }
9
+
10
+ describe "notifying" do
11
+ let(:origin_email) { "test@test.com" }
12
+
13
+ before {
14
+ Tocsin.configure do |c|
15
+ c.from_address = "test@test.com"
16
+ end
17
+ }
18
+
19
+ it "uses the Mail gem to send an alert" do
20
+ described_class.notify(["a@b.com"], alert)
21
+ end
22
+
23
+ describe "the sent email" do
24
+ let(:message) { Mail::TestMailer.deliveries.first }
25
+ let(:body) { message.body.decoded }
26
+
27
+ it "was sent" do
28
+ message.should_not be_nil
29
+ end
30
+
31
+ it "has a subject including the alert's message, severity and category" do
32
+ message.subject.should == "[Tocsin] [severity] message (category)"
33
+ end
34
+
35
+ it "has a body including all fields" do
36
+ body.should =~ /severity/
37
+ body.should =~ /message/
38
+ body.should =~ /category/
39
+ body.should =~ /exception/
40
+ body.should =~ /backtrace/
41
+ end
42
+
43
+ it "is from the configured origin email" do
44
+ message.from.should == [origin_email]
45
+ end
46
+ end
47
+ end
48
+
49
+ it "registers itself under the key :email" do
50
+ Tocsin::Notifiers[:email].should == described_class
51
+ end
52
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+
3
+ # Fixture for testing notifiers.
4
+ class DummyNotifier
5
+
6
+ end
7
+
8
+ describe Tocsin::Notifiers do
9
+ context "after a notifier has been registered with a key" do
10
+ before {
11
+ Tocsin::Notifiers.register! :dummy, DummyNotifier
12
+ }
13
+
14
+ it "can retrieve the notifier based on that key" do
15
+ Tocsin::Notifiers[:dummy].should == DummyNotifier
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,259 @@
1
+ require 'spec_helper'
2
+
3
+ describe Tocsin do
4
+ let(:alert_options) do
5
+ { :severity => :critical,
6
+ :message => "Raised by someone",
7
+ :category => :real_important_job }
8
+ end
9
+
10
+ let(:recipient_groups) do
11
+ [ {:category => /.*/, :severity => /.*/, :recipients => ["x@y.com", "z@w.com"]},
12
+ {:category => /^test$/, :severity => /(high|low)/, :recipients => ["a@b.com", "z@w.com"], :notifier => :email},
13
+ {:category => /^test$/, :severity => /(high|low)/, :recipients => ["1234567890"], :notifier => :text_message}
14
+ ]
15
+ end
16
+
17
+ before do
18
+ Tocsin.configure do |c|
19
+ c.recipient_groups = recipient_groups
20
+ c.logger = Logger.new('/dev/null')
21
+ end
22
+ end
23
+
24
+ context "when an exception has already been rescued" do
25
+ it "raises an alert" do
26
+ Tocsin::Alert.expects(:create).returns(stub("alert", :id => 1))
27
+
28
+ begin
29
+ raise "Testing"
30
+ rescue => e
31
+ Tocsin.raise_alert(e, alert_options)
32
+ end
33
+ end
34
+ end
35
+
36
+ context "when considering a block of code which explodes" do
37
+ it "provides a watch method which raises an alert silently" do
38
+ Tocsin.expects(:raise_alert).with(is_a(Exception), has_entries(alert_options))
39
+
40
+ lambda {
41
+ Tocsin.watch(alert_options) do
42
+ raise "Testing"
43
+ end
44
+ }.should_not raise_error
45
+ end
46
+
47
+ it "provides a watch! method which both send an alert and re-raises the exception" do
48
+ Tocsin.expects(:raise_alert).with(is_a(Exception), has_entries(alert_options))
49
+
50
+ lambda {
51
+ Tocsin.watch!(alert_options) do
52
+ raise "Testing"
53
+ end
54
+ }.should raise_error
55
+ end
56
+ end
57
+
58
+ describe "being configured" do
59
+ it "wipes old configuration" do
60
+ Tocsin.configure do |c|
61
+ c.recipient_groups = []
62
+ end
63
+
64
+ Tocsin.configure { }
65
+ Tocsin.config.recipient_groups.should be_nil
66
+ end
67
+
68
+ describe "#recipient_groups=" do
69
+ it "can set the list of recipient groups directly" do
70
+ Tocsin.configure do |c|
71
+ c.recipient_groups = recipient_groups
72
+ end
73
+
74
+ Tocsin.config.recipient_groups.should == recipient_groups
75
+ end
76
+ end
77
+
78
+ describe "#notify" do
79
+ it "can create a notification group idiomatically" do
80
+ Tocsin.configure do |c|
81
+ c.notify ["rnubel@test.com"], :of => { :severity => /critical/ }, :by => :email
82
+ end
83
+
84
+ Tocsin.config.recipient_groups.should == [
85
+ { :severity => /critical/, :recipients => ["rnubel@test.com"], :notifier => :email }
86
+ ]
87
+ end
88
+
89
+ it "assumes email as the default notifier if not specified" do
90
+ Tocsin.configure do |c|
91
+ c.notify ["rnubel@test.com"], :of => { :severity => /critical/ }
92
+ end
93
+
94
+ Tocsin.config.recipient_groups.should == [
95
+ { :severity => /critical/, :recipients => ["rnubel@test.com"], :notifier => :email }
96
+ ]
97
+ end
98
+
99
+ it "converts a single recipient into an array of that single recipient" do
100
+ Tocsin.configure do |c|
101
+ c.notify "rnubel@test.com", :of => { :severity => /critical/ }, :by => :email
102
+ end
103
+
104
+ Tocsin.config.recipient_groups.should == [
105
+ { :severity => /critical/, :recipients => ["rnubel@test.com"], :notifier => :email }
106
+ ]
107
+ end
108
+
109
+ it 'has a default queue' do
110
+ Tocsin.configure { }
111
+ Tocsin.config.queue.should_not be_nil
112
+ end
113
+
114
+ it 'can be confgiured with a default from_address' do
115
+ Tocsin.configure do |c|
116
+ c.from_address = "webdude@example.net"
117
+ end
118
+ Tocsin.config.from_address.should == "webdude@example.net"
119
+ end
120
+ end
121
+ end
122
+
123
+ context "deciding what recipients apply to a given alert" do
124
+ context "when given a pattern matching only one group" do
125
+ it "returns the group in a hash with the notifier as the key" do
126
+ Tocsin.recipients(stub('alert', :category => "whee", :severity => "test")).should == { :email => ["x@y.com", "z@w.com"] }
127
+ end
128
+ end
129
+
130
+ it "should merge all groups which match the pattern" do
131
+ Tocsin.recipients(stub('alert', :category => "test", :severity => "high")).should == { :email => ["a@b.com", "x@y.com", "z@w.com"],
132
+ :text_message => ["1234567890"] }
133
+ end
134
+ end
135
+
136
+ describe "synoynms for ::raise_alert" do
137
+ describe "::notify" do
138
+ it "calls raise_alert with notification as the default severity" do
139
+ Tocsin.expects(:raise_alert).with(nil, has_entries(:severity => "notification"))
140
+ Tocsin.notify({})
141
+ end
142
+ end
143
+
144
+ describe "::warn!" do
145
+ it "calls raise_alert with the same options as passed" do
146
+ opts = mock("options")
147
+ Tocsin.expects(:raise_alert).with(nil, opts)
148
+ Tocsin.warn!(opts)
149
+ end
150
+ end
151
+ end
152
+
153
+ context "when raising an alarm" do
154
+ let(:exception) do
155
+ begin
156
+ raise "Exception"
157
+ rescue => e
158
+ e
159
+ end
160
+ end
161
+
162
+ let(:alert) do
163
+ Tocsin.raise_alert(exception, alert_options)
164
+ end
165
+
166
+ describe "the created Tocsin::Alert object" do
167
+ it "is created with appropriate fields" do
168
+ Tocsin::Alert.expects(:create).with(has_entries(
169
+ :exception => exception.to_s,
170
+ :backtrace => exception.backtrace.join("\n"),
171
+ :severity => alert_options[:severity].to_s,
172
+ :message => alert_options[:message].to_s,
173
+ :category => alert_options[:category].to_s
174
+ )).returns(stub("alert", :id => 1))
175
+ alert
176
+ end
177
+ end
178
+
179
+ it "should enqueue a notification job in Resque by default" do
180
+ Resque.expects(:enqueue).with(Tocsin::NotificationJob, is_a(Integer))
181
+ Tocsin.expects(:sound).never
182
+ alert
183
+ end
184
+
185
+ it "should sound an alert immediately when asked" do
186
+ Resque.expects(:enqueue).never
187
+ Tocsin.expects(:sound).once
188
+ alert_options[:urgent] = true
189
+ alert
190
+ end
191
+
192
+ it "does not explode if Resque.enqueue fails" do
193
+ Resque.expects(:enqueue).raises("Blah")
194
+
195
+ ## once for the original alert, once for queuing failure
196
+ Tocsin.expects(:sound).twice
197
+
198
+ expect { alert }.to_not raise_error
199
+ end
200
+
201
+ end
202
+
203
+ describe Tocsin::NotificationJob do
204
+ let(:alert) { stub("alert", :id => 5, :category => "test", :severity => "test", :message => "test", :exception => nil, :backtrace => nil) }
205
+ before { Mail::TestMailer.deliveries.clear }
206
+
207
+ it "locates the alert by id" do
208
+ Tocsin::Alert.expects(:find).with(5).returns(alert)
209
+ Tocsin::NotificationJob.perform(5)
210
+ end
211
+
212
+ it "uses the associated notifier to alert recipients" do
213
+ Tocsin::Alert.expects(:find).with(5).returns(alert)
214
+ Tocsin.expects(:recipients).with(alert).returns( :email => ["a@b.com"] )
215
+ Tocsin::Notifiers[:email].expects(:notify).with(["a@b.com"], alert)
216
+ Tocsin::NotificationJob.perform(5)
217
+ end
218
+
219
+ it 'will use the first email address as the sender when from_address is not present' do
220
+ Tocsin::Alert.expects(:find).with(5).returns(alert)
221
+ Tocsin.expects(:recipients).with(alert).returns( :email => ["a@b.com", "x@y.com"] )
222
+ Tocsin::NotificationJob.perform(5)
223
+
224
+ em = Mail::TestMailer.deliveries.first
225
+ em.from.should include("a@b.com")
226
+ end
227
+
228
+ it "should log an error for an unknown notifier" do
229
+ Tocsin::Alert.expects(:find).with(5).returns(alert)
230
+ Tocsin.expects(:recipients).returns({:raven => "Cersei"})
231
+ Tocsin.logger.expects(:error)
232
+ Tocsin::NotificationJob.perform(5)
233
+ end
234
+
235
+ context "empty recipient list" do
236
+ before do
237
+ Tocsin::Alert.expects(:find).with(5).returns(alert)
238
+ Tocsin.expects(:recipients).returns({:email => []})
239
+ end
240
+
241
+ after { Tocsin::NotificationJob.perform(5) }
242
+
243
+ it "should not attempt notification" do
244
+ Tocsin::Notifiers[:email].expects(:notify).never
245
+ end
246
+
247
+ it "should log an error" do
248
+ Tocsin.logger.expects(:error)
249
+ end
250
+ end
251
+
252
+ it "should not raise an exception if the alert isn't found (otherwise, possible recursion)" do
253
+ Tocsin::Alert.expects(:find).with(5).raises(ActiveRecord::RecordNotFound)
254
+ lambda {
255
+ Tocsin::NotificationJob.perform(5)
256
+ }.should_not raise_error
257
+ end
258
+ end
259
+ end
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "tocsin/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "tocsin"
7
+ s.version = Tocsin::VERSION
8
+ s.authors = ["Robert Nubel","Blake Thomas"]
9
+ s.email = ["tocsin.gem@gmail.com"]
10
+ s.homepage = "https://github.com/tocsin/tocsin"
11
+ s.summary = %q{Notification and alert library for Rails-like projects.}
12
+ s.description = %q{Supports wrapping code that you want to be alerted of failures in, as well as sending notifications through the same mechanism.}
13
+
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
+ s.require_paths = ["lib"]
18
+
19
+ # specify any dependencies here; for example:
20
+ s.add_development_dependency "rspec"
21
+ s.add_development_dependency "mocha"
22
+
23
+ s.add_runtime_dependency "rails", ">= 3.0.0"
24
+ s.add_runtime_dependency "resque"
25
+ s.add_runtime_dependency "mail"
26
+ end
metadata ADDED
@@ -0,0 +1,147 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tocsin
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Robert Nubel
9
+ - Blake Thomas
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2013-04-09 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rspec
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ! '>='
29
+ - !ruby/object:Gem::Version
30
+ version: '0'
31
+ - !ruby/object:Gem::Dependency
32
+ name: mocha
33
+ requirement: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ type: :development
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: rails
49
+ requirement: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: 3.0.0
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: 3.0.0
63
+ - !ruby/object:Gem::Dependency
64
+ name: resque
65
+ requirement: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ type: :runtime
72
+ prerelease: false
73
+ version_requirements: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ - !ruby/object:Gem::Dependency
80
+ name: mail
81
+ requirement: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ! '>='
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ type: :runtime
88
+ prerelease: false
89
+ version_requirements: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ! '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ description: Supports wrapping code that you want to be alerted of failures in, as
96
+ well as sending notifications through the same mechanism.
97
+ email:
98
+ - tocsin.gem@gmail.com
99
+ executables: []
100
+ extensions: []
101
+ extra_rdoc_files: []
102
+ files:
103
+ - .gitignore
104
+ - Gemfile
105
+ - README.md
106
+ - Rakefile
107
+ - lib/tocsin.rb
108
+ - lib/tocsin/config.rb
109
+ - lib/tocsin/notifiers.rb
110
+ - lib/tocsin/notifiers/email_notifier.rb
111
+ - lib/tocsin/rake.rb
112
+ - lib/tocsin/version.rb
113
+ - spec/spec_helper.rb
114
+ - spec/tocsin/notifiers/email_notifier_spec.rb
115
+ - spec/tocsin/notifiers_spec.rb
116
+ - spec/tocsin_spec.rb
117
+ - tocsin.gemspec
118
+ homepage: https://github.com/tocsin/tocsin
119
+ licenses: []
120
+ post_install_message:
121
+ rdoc_options: []
122
+ require_paths:
123
+ - lib
124
+ required_ruby_version: !ruby/object:Gem::Requirement
125
+ none: false
126
+ requirements:
127
+ - - ! '>='
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ required_rubygems_version: !ruby/object:Gem::Requirement
131
+ none: false
132
+ requirements:
133
+ - - ! '>='
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ requirements: []
137
+ rubyforge_project:
138
+ rubygems_version: 1.8.23
139
+ signing_key:
140
+ specification_version: 3
141
+ summary: Notification and alert library for Rails-like projects.
142
+ test_files:
143
+ - spec/spec_helper.rb
144
+ - spec/tocsin/notifiers/email_notifier_spec.rb
145
+ - spec/tocsin/notifiers_spec.rb
146
+ - spec/tocsin_spec.rb
147
+ has_rdoc: