tocsin 0.1.0

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