chatterbox 0.3.3
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 +1 -0
- data/.treasure_map.rb +22 -0
- data/LICENSE +20 -0
- data/README.markdown +59 -0
- data/Rakefile +47 -0
- data/chatterbox.gemspec +75 -0
- data/examples/chatterbox_example.rb +73 -0
- data/examples/example_helper.rb +23 -0
- data/examples/lib/chatterbox/consumers/email_consumer_example.rb +65 -0
- data/examples/lib/chatterbox/notification_example.rb +147 -0
- data/examples/lib/chatterbox/rails_catcher_example.rb +54 -0
- data/init.rb +2 -0
- data/lib/chatterbox/notification.rb +87 -0
- data/lib/chatterbox/rails_catcher.rb +24 -0
- data/lib/chatterbox.rb +52 -0
- data/lib/consumers/email_consumer.rb +64 -0
- data/lib/consumers.rb +2 -0
- data/rails/init.rb +2 -0
- data/todo.markdown +7 -0
- data/version.yml +4 -0
- data/views/chatterbox/mailer/exception_notification.erb +19 -0
- metadata +119 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
coverage
|
data/.treasure_map.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
map_for(:chatterbox) do |map|
|
2
|
+
|
3
|
+
map.keep_a_watchful_eye_for 'lib', 'examples', 'rails'
|
4
|
+
|
5
|
+
# map.add_mapping %r%examples/(.*)_example\.rb% do |match|
|
6
|
+
# ["examples/#{match[1]}_example.rb"]
|
7
|
+
# end
|
8
|
+
#
|
9
|
+
# map.add_mapping %r%examples/example_helper\.rb% do |match|
|
10
|
+
# Dir["examples/**/*_example.rb"]
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# map.add_mapping %r%lib/(.*)\.rb% do |match|
|
14
|
+
# Dir["examples/#{match[1]}_example.rb"]
|
15
|
+
# end
|
16
|
+
|
17
|
+
map.add_mapping %r%rails/(.*)\.rb% do |match|
|
18
|
+
["examples/#{match[1]}_example.rb"]
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Rob Sanheim
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
Chatterbox
|
2
|
+
==========================================
|
3
|
+
|
4
|
+
Simple Notifications. Publishing and subscribing to notifications is decoupled by default, so bring your own message queue, web service, database, or whatever to act as an intermediary.
|
5
|
+
|
6
|
+
Installing and Running
|
7
|
+
---------------------------------------
|
8
|
+
|
9
|
+
To install within a Rails app:
|
10
|
+
|
11
|
+
Add the following to your environment.rb file:
|
12
|
+
|
13
|
+
config.gem "relevance-chatterbox"
|
14
|
+
|
15
|
+
Then run:
|
16
|
+
|
17
|
+
rake gems:install
|
18
|
+
|
19
|
+
To enable standard Rails exception catching for your controllers, add the following to `application_controller`
|
20
|
+
|
21
|
+
class ApplicationController < ActionController::Base
|
22
|
+
include Chatterbox::RailsCatcher
|
23
|
+
# ...
|
24
|
+
end
|
25
|
+
|
26
|
+
Then, wire up a producer
|
27
|
+
|
28
|
+
Example 1
|
29
|
+
---------------------------------------
|
30
|
+
|
31
|
+
Wiring messages to be sent by Email service
|
32
|
+
|
33
|
+
Chatterbox::Publishers.register do |notice|
|
34
|
+
Chatterbox::Email.deliver(notice)
|
35
|
+
end
|
36
|
+
|
37
|
+
Example 2
|
38
|
+
---------------------------------------
|
39
|
+
|
40
|
+
Wiring up notices to be sent to an exceptions queue, defined in RosettaQueue
|
41
|
+
|
42
|
+
Chatterbox::Publishers.register do |notice|
|
43
|
+
RosettaQueue::Producer.publish(:exceptions, notice)
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
Bugs & Patches
|
48
|
+
--------------
|
49
|
+
|
50
|
+
Links
|
51
|
+
-------------
|
52
|
+
|
53
|
+
Contributors
|
54
|
+
------------
|
55
|
+
* Rob Sanheim
|
56
|
+
|
57
|
+
Copyrights
|
58
|
+
------------
|
59
|
+
* Copyright © 2008-2009 [Relevance, Inc.](http://www.thinkrelevance.com/), under the MIT license
|
data/Rakefile
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'rake'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'jeweler'
|
5
|
+
Jeweler::Tasks.new do |gem|
|
6
|
+
gem.name = "chatterbox"
|
7
|
+
gem.summary = %Q{Notifications and messages}
|
8
|
+
gem.email = "rsanheim@gmail.com"
|
9
|
+
gem.homepage = "http://github.com/rsanheim/chatterbox"
|
10
|
+
gem.authors = ["Rob Sanheim"]
|
11
|
+
gem.add_development_dependency "mocha"
|
12
|
+
gem.add_development_dependency "actioncontroller"
|
13
|
+
gem.add_development_dependency "spicycode-micronaut"
|
14
|
+
gem.add_development_dependency "spicycode-micronaut-rails"
|
15
|
+
end
|
16
|
+
Jeweler::GemcutterTasks.new
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'micronaut/rake_task'
|
22
|
+
Micronaut::RakeTask.new(:examples) do |examples|
|
23
|
+
examples.pattern = 'examples/**/*_example.rb'
|
24
|
+
examples.ruby_opts << '-Ilib -Iexamples'
|
25
|
+
end
|
26
|
+
|
27
|
+
Micronaut::RakeTask.new(:rcov) do |examples|
|
28
|
+
examples.pattern = 'examples/**/*_example.rb'
|
29
|
+
examples.rcov_opts = '-Ilib -Iexamples'
|
30
|
+
examples.rcov = true
|
31
|
+
end
|
32
|
+
|
33
|
+
task :default => :examples
|
34
|
+
|
35
|
+
require 'rake/rdoctask'
|
36
|
+
Rake::RDocTask.new do |rdoc|
|
37
|
+
if File.exist?('VERSION')
|
38
|
+
version = File.read('VERSION')
|
39
|
+
else
|
40
|
+
version = ""
|
41
|
+
end
|
42
|
+
|
43
|
+
rdoc.rdoc_dir = 'rdoc'
|
44
|
+
rdoc.title = "chatterbox-email #{version}"
|
45
|
+
rdoc.rdoc_files.include('README*')
|
46
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
47
|
+
end
|
data/chatterbox.gemspec
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{chatterbox}
|
8
|
+
s.version = "0.3.3"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Rob Sanheim"]
|
12
|
+
s.date = %q{2009-10-05}
|
13
|
+
s.email = %q{rsanheim@gmail.com}
|
14
|
+
s.extra_rdoc_files = [
|
15
|
+
"LICENSE",
|
16
|
+
"README.markdown"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
".gitignore",
|
20
|
+
".treasure_map.rb",
|
21
|
+
"LICENSE",
|
22
|
+
"README.markdown",
|
23
|
+
"Rakefile",
|
24
|
+
"chatterbox.gemspec",
|
25
|
+
"examples/chatterbox_example.rb",
|
26
|
+
"examples/example_helper.rb",
|
27
|
+
"examples/lib/chatterbox/consumers/email_consumer_example.rb",
|
28
|
+
"examples/lib/chatterbox/notification_example.rb",
|
29
|
+
"examples/lib/chatterbox/rails_catcher_example.rb",
|
30
|
+
"init.rb",
|
31
|
+
"lib/chatterbox.rb",
|
32
|
+
"lib/chatterbox/notification.rb",
|
33
|
+
"lib/chatterbox/rails_catcher.rb",
|
34
|
+
"lib/consumers.rb",
|
35
|
+
"lib/consumers/email_consumer.rb",
|
36
|
+
"rails/init.rb",
|
37
|
+
"todo.markdown",
|
38
|
+
"version.yml",
|
39
|
+
"views/chatterbox/mailer/exception_notification.erb"
|
40
|
+
]
|
41
|
+
s.homepage = %q{http://github.com/rsanheim/chatterbox}
|
42
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
43
|
+
s.require_paths = ["lib"]
|
44
|
+
s.rubygems_version = %q{1.3.5}
|
45
|
+
s.summary = %q{Notifications and messages}
|
46
|
+
s.test_files = [
|
47
|
+
"examples/chatterbox_example.rb",
|
48
|
+
"examples/example_helper.rb",
|
49
|
+
"examples/lib/chatterbox/consumers/email_consumer_example.rb",
|
50
|
+
"examples/lib/chatterbox/notification_example.rb",
|
51
|
+
"examples/lib/chatterbox/rails_catcher_example.rb"
|
52
|
+
]
|
53
|
+
|
54
|
+
if s.respond_to? :specification_version then
|
55
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
56
|
+
s.specification_version = 3
|
57
|
+
|
58
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
59
|
+
s.add_development_dependency(%q<mocha>, [">= 0"])
|
60
|
+
s.add_development_dependency(%q<actioncontroller>, [">= 0"])
|
61
|
+
s.add_development_dependency(%q<spicycode-micronaut>, [">= 0"])
|
62
|
+
s.add_development_dependency(%q<spicycode-micronaut-rails>, [">= 0"])
|
63
|
+
else
|
64
|
+
s.add_dependency(%q<mocha>, [">= 0"])
|
65
|
+
s.add_dependency(%q<actioncontroller>, [">= 0"])
|
66
|
+
s.add_dependency(%q<spicycode-micronaut>, [">= 0"])
|
67
|
+
s.add_dependency(%q<spicycode-micronaut-rails>, [">= 0"])
|
68
|
+
end
|
69
|
+
else
|
70
|
+
s.add_dependency(%q<mocha>, [">= 0"])
|
71
|
+
s.add_dependency(%q<actioncontroller>, [">= 0"])
|
72
|
+
s.add_dependency(%q<spicycode-micronaut>, [">= 0"])
|
73
|
+
s.add_dependency(%q<spicycode-micronaut-rails>, [">= 0"])
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), *%w[example_helper])
|
2
|
+
|
3
|
+
describe Chatterbox do
|
4
|
+
|
5
|
+
before do
|
6
|
+
Chatterbox.logger = Logger.new(nil)
|
7
|
+
Chatterbox::Publishers.clear!
|
8
|
+
end
|
9
|
+
|
10
|
+
after do
|
11
|
+
Chatterbox.logger = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "handle_notice" do
|
15
|
+
include Chatterbox
|
16
|
+
|
17
|
+
it "should create Notification and return the notice" do
|
18
|
+
notification = mock(:notice => {:hash => 'of awesomeness'})
|
19
|
+
Chatterbox::Notification.expects(:new).returns(notification)
|
20
|
+
handle_notice("message")
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should publish the notice" do
|
24
|
+
notification = stub(:notice => {:hash => 'of awesomeness'})
|
25
|
+
Chatterbox::Notification.stubs(:new).returns(notification)
|
26
|
+
expects(:publish_notice).with({:hash => 'of awesomeness'})
|
27
|
+
handle_notice("message")
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "logger" do
|
33
|
+
|
34
|
+
it "uses STDOUT logger if Rails not available" do
|
35
|
+
Chatterbox.logger = nil
|
36
|
+
|
37
|
+
Logger.expects(:new).with(STDOUT).returns("logger")
|
38
|
+
Chatterbox.stubs(:rails_default_logger).returns(nil)
|
39
|
+
Chatterbox.logger.should == "logger"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "publish" do
|
44
|
+
|
45
|
+
include Chatterbox
|
46
|
+
|
47
|
+
it "should call each publisher with the notice" do
|
48
|
+
notice = stub
|
49
|
+
publisher = Chatterbox::Publishers.register { "i'm in your block" }
|
50
|
+
publisher.expects(:call).with(notice)
|
51
|
+
|
52
|
+
publish_notice(notice)
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "publishers" do
|
58
|
+
|
59
|
+
it "should allow clearing all publishers" do
|
60
|
+
Chatterbox::Publishers.register { "sending your messages" }
|
61
|
+
Chatterbox::Publishers.publishers.size.should == 1
|
62
|
+
Chatterbox::Publishers.clear!
|
63
|
+
Chatterbox::Publishers.publishers.size.should == 0
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should allow registering with a block" do
|
67
|
+
pub1 = Chatterbox::Publishers.register { "sending your messages" }
|
68
|
+
pub2 = Chatterbox::Publishers.register { "announcing your news" }
|
69
|
+
Chatterbox::Publishers.publishers.should == [pub1, pub2]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'action_controller'
|
3
|
+
require 'micronaut'
|
4
|
+
require 'micronaut-rails'
|
5
|
+
require 'mocha'
|
6
|
+
|
7
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
8
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
9
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'lib'))
|
10
|
+
|
11
|
+
require 'chatterbox'
|
12
|
+
|
13
|
+
def not_in_editor?
|
14
|
+
!(ENV.has_key?('TM_MODE') || ENV.has_key?('EMACS') || ENV.has_key?('VIM'))
|
15
|
+
end
|
16
|
+
|
17
|
+
Micronaut.configure do |c|
|
18
|
+
c.mock_with :mocha
|
19
|
+
c.color_enabled = not_in_editor?
|
20
|
+
c.filter_run :focused => true
|
21
|
+
c.alias_example_to :fit, :focused => true
|
22
|
+
c.enable_controller_support :behaviour => { :describes => lambda { |dt| dt < ::ActionController::Base } }
|
23
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), *%w[.. .. .. example_helper]))
|
2
|
+
require 'chatterbox'
|
3
|
+
|
4
|
+
describe Chatterbox::Mailer do
|
5
|
+
|
6
|
+
it "displays environment vars sorted" do
|
7
|
+
notice = {
|
8
|
+
:environment => {
|
9
|
+
"PATH" => "/usr/bin",
|
10
|
+
"PS1" => "$",
|
11
|
+
"TMPDIR" => "/tmp"
|
12
|
+
}
|
13
|
+
}
|
14
|
+
expected = <<EOL
|
15
|
+
PATH => /usr/bin
|
16
|
+
PS1 => $
|
17
|
+
TMPDIR => /tmp
|
18
|
+
EOL
|
19
|
+
mail = Chatterbox::Mailer.create_exception_notification(notice)
|
20
|
+
mail.body.should include(expected.strip)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "displays any other details in the hash in the email body" do
|
24
|
+
notice = {
|
25
|
+
:details => { "message_id" => "user01-create", "current_user" => "Chris Liebing" },
|
26
|
+
:environment => { "foo" => "path" }
|
27
|
+
}
|
28
|
+
Chatterbox::Mailer.create_exception_notification(notice)
|
29
|
+
expected = <<EOL
|
30
|
+
Details
|
31
|
+
-------
|
32
|
+
current_user => Chris Liebing
|
33
|
+
message_id => user01-create
|
34
|
+
EOL
|
35
|
+
mail = Chatterbox::Mailer.create_exception_notification(notice)
|
36
|
+
mail.body.should include(expected.strip)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "does not mutate the provided hash" do
|
40
|
+
notice = {'foo' => 'bar', :environment => {}}
|
41
|
+
Chatterbox::Mailer.create_exception_notification(notice)
|
42
|
+
notice.should == {'foo' => 'bar', :environment => {}}
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "subject" do
|
46
|
+
|
47
|
+
it "extracts the subject from the given data hash when the key is a symbol" do
|
48
|
+
mail = Chatterbox::Mailer.create_exception_notification(:environment => {}, :summary => 'foo')
|
49
|
+
mail.subject.should include('foo')
|
50
|
+
end
|
51
|
+
|
52
|
+
it "extracts the subject from the given data hash when the key is a string" do
|
53
|
+
mail = Chatterbox::Mailer.create_exception_notification(:environment => {}, 'summary' => 'foo')
|
54
|
+
mail.subject.should include('foo')
|
55
|
+
end
|
56
|
+
|
57
|
+
it "includes the given prefix" do
|
58
|
+
Chatterbox::Mailer.email_prefix = '[super important email]'
|
59
|
+
mail = Chatterbox::Mailer.create_exception_notification(:environment => {})
|
60
|
+
mail.subject.should match(/\[super important email\]/)
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), *%w[.. .. example_helper]))
|
2
|
+
|
3
|
+
describe Chatterbox::Notification do
|
4
|
+
|
5
|
+
before do
|
6
|
+
Chatterbox.logger = Logger.new(nil)
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "creating the notice" do
|
10
|
+
|
11
|
+
it "should safely handle nil" do
|
12
|
+
lambda {
|
13
|
+
Chatterbox::Notification.new(nil).notice
|
14
|
+
}.should_not raise_error
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should use to_hash if message is not an exception and it responds_to possible" do
|
18
|
+
some_object = mock(:to_hash => {:foo => "bar"})
|
19
|
+
Chatterbox::Notification.new(some_object).notice.should include({:foo => "bar"})
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should call to_s on anything that responds to it, as a last resort" do
|
23
|
+
some_object = mock(:to_s => "my to_s")
|
24
|
+
Chatterbox::Notification.new(some_object).notice.should include({:summary => "my to_s"})
|
25
|
+
end
|
26
|
+
|
27
|
+
it "merges hash passed in with default info" do
|
28
|
+
hash = {:message => "hey!"}
|
29
|
+
default_info = mock()
|
30
|
+
default_info.expects(:merge).with(hash)
|
31
|
+
notification = Chatterbox::Notification.new(hash)
|
32
|
+
notification.expects(:default_info).returns(default_info)
|
33
|
+
notification.notice
|
34
|
+
end
|
35
|
+
|
36
|
+
it "turns string notice into a hash keyed by notice" do
|
37
|
+
notification = Chatterbox::Notification.new("You have been placed on alert")
|
38
|
+
notification.notice.should include({:summary => "You have been placed on alert"})
|
39
|
+
end
|
40
|
+
|
41
|
+
it "always includes a summary" do
|
42
|
+
Chatterbox::Notification.new().notice.should include(:summary)
|
43
|
+
Chatterbox::Notification.new({}).notice.should include(:summary)
|
44
|
+
Chatterbox::Notification.new(RuntimeError.new).notice.should include(:summary)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should set summary to N/A if nothing provided" do
|
48
|
+
Chatterbox::Notification.new({}).notice.should include(:summary => "N/A")
|
49
|
+
Chatterbox::Notification.new({:foo => 'baz'}).notice.should include(:summary => "N/A")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "exceptions" do
|
54
|
+
|
55
|
+
def raised_exception
|
56
|
+
raise RuntimeError, "Your zing bats got mixed up with the snosh frazzles."
|
57
|
+
rescue => e
|
58
|
+
e
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should extract exception info" do
|
62
|
+
exception = raised_exception
|
63
|
+
data = Chatterbox::Notification.new(exception).notice
|
64
|
+
data[:summary].should == "RuntimeError: Your zing bats got mixed up with the snosh frazzles."
|
65
|
+
data[:error_class].should == "RuntimeError"
|
66
|
+
data[:error_message].should == "Your zing bats got mixed up with the snosh frazzles."
|
67
|
+
data[:backtrace].should == exception.backtrace
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should extract exception info from an exception in a hash" do
|
71
|
+
exception = raised_exception
|
72
|
+
data = Chatterbox::Notification.new(:exception => exception, :other_info => "yo dawg").notice
|
73
|
+
data[:summary].should == "RuntimeError: Your zing bats got mixed up with the snosh frazzles."
|
74
|
+
data[:error_class].should == "RuntimeError"
|
75
|
+
data[:error_message].should == "Your zing bats got mixed up with the snosh frazzles."
|
76
|
+
data[:backtrace].should == exception.backtrace
|
77
|
+
data[:other_info].should == "yo dawg"
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should let extra data win over auto extracted exception data" do
|
81
|
+
exception = raised_exception
|
82
|
+
data = Chatterbox::Notification.new(:exception => exception, :summary => "I know what I'm doing, and we got an error").notice
|
83
|
+
data[:summary].should == "I know what I'm doing, and we got an error"
|
84
|
+
end
|
85
|
+
|
86
|
+
it "merges rails info and ruby info into the exception info" do
|
87
|
+
notification = Chatterbox::Notification.new(raised_exception)
|
88
|
+
rails = stub_everything(:version => "2.0", :root => "/rails/root", :env => "production")
|
89
|
+
notification.stubs(:rails_configuration).returns(rails)
|
90
|
+
notification.notice.should include(:rails_version => "2.0")
|
91
|
+
notification.notice.should include(:rails_root => "/rails/root")
|
92
|
+
notification.notice.should include(:rails_env => "production")
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
describe "hashes" do
|
98
|
+
|
99
|
+
it "merges rails info and ruby info into the notification" do
|
100
|
+
notification = Chatterbox::Notification.new({})
|
101
|
+
rails = stub_everything(:version => "2.0", :root => "/rails/root", :env => "production")
|
102
|
+
notification.stubs(:rails_configuration).returns(rails)
|
103
|
+
notification.notice.should include(:rails_version => "2.0")
|
104
|
+
notification.notice.should include(:rails_root => "/rails/root")
|
105
|
+
notification.notice.should include(:rails_env => "production")
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
describe "default info to be included with every notification" do
|
111
|
+
|
112
|
+
it "should return full ENV" do
|
113
|
+
environment = { "USER" => "jdoe", "PATH" => "/usr/bin", "HOME" => "/usr/home/jdoe" }
|
114
|
+
notification = Chatterbox::Notification.new
|
115
|
+
notification.stubs(:env).returns(environment)
|
116
|
+
notification.default_info.should include(:environment => environment)
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should return Ruby version and platform" do
|
120
|
+
notification = Chatterbox::Notification.new
|
121
|
+
notification.stubs(:ruby_version).returns("1.8.6")
|
122
|
+
notification.stubs(:ruby_platform).returns("Mac OS X blah")
|
123
|
+
data = notification.default_info
|
124
|
+
data.should include(:ruby_version => "1.8.6")
|
125
|
+
data.should include(:ruby_platform => "Mac OS X blah")
|
126
|
+
end
|
127
|
+
|
128
|
+
describe "when Rails is defined" do
|
129
|
+
|
130
|
+
it "should return Rails info" do
|
131
|
+
notification = Chatterbox::Notification.new
|
132
|
+
rails = stub
|
133
|
+
rails.stubs(:root).returns("/some/path")
|
134
|
+
rails.stubs(:env).returns("production")
|
135
|
+
rails.stubs(:version).returns("2.1.2")
|
136
|
+
notification.stubs(:rails_configuration).returns(rails)
|
137
|
+
|
138
|
+
notification.default_info.should include(:rails_root => "/some/path")
|
139
|
+
notification.default_info.should include(:rails_env => "production")
|
140
|
+
notification.default_info.should include(:rails_version => "2.1.2")
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), *%w[.. .. example_helper]))
|
2
|
+
require File.expand_path(File.join(File.dirname(__FILE__), *%w[.. .. .. rails init]))
|
3
|
+
|
4
|
+
ActionController::Routing::Routes.draw { |map| map.connect ':controller/:action/:id' }
|
5
|
+
|
6
|
+
class WidgetException < RuntimeError; end
|
7
|
+
class WidgetsController < ActionController::Base
|
8
|
+
include Chatterbox::RailsCatcher
|
9
|
+
|
10
|
+
def rescue_action e
|
11
|
+
rescue_action_in_public e
|
12
|
+
end
|
13
|
+
|
14
|
+
def rescue_action_in_public_without_chatterbox e
|
15
|
+
raise e
|
16
|
+
end
|
17
|
+
|
18
|
+
def index
|
19
|
+
raise_exception
|
20
|
+
render :text => "hi"
|
21
|
+
end
|
22
|
+
|
23
|
+
def raise_exception
|
24
|
+
raise WidgetException, "Bad dog!"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe WidgetsController do
|
29
|
+
|
30
|
+
describe "rescue aliasing" do
|
31
|
+
|
32
|
+
it "should alias method chain" do
|
33
|
+
exception = RuntimeError.new
|
34
|
+
@controller.expects(:rescue_action_in_public_without_chatterbox).with(exception)
|
35
|
+
@controller.rescue_action_in_public(exception)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "exception handling" do
|
40
|
+
|
41
|
+
it "should raise on index" do
|
42
|
+
lambda {
|
43
|
+
get :index
|
44
|
+
}.should raise_error(WidgetException, "Bad dog!")
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should send exception to handle_notice" do
|
48
|
+
Chatterbox.expects(:handle_notice).with(instance_of(WidgetException))
|
49
|
+
get :index rescue nil
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
module Chatterbox
|
2
|
+
|
3
|
+
class Notification
|
4
|
+
|
5
|
+
attr_reader :message
|
6
|
+
|
7
|
+
def initialize(message = nil)
|
8
|
+
@message = message
|
9
|
+
end
|
10
|
+
|
11
|
+
def notice
|
12
|
+
hash = normalize_message_to_hash(message)
|
13
|
+
hash = exception_to_notice(hash)
|
14
|
+
default_info.merge(hash)
|
15
|
+
end
|
16
|
+
|
17
|
+
def normalize_message_to_hash(message)
|
18
|
+
case
|
19
|
+
when Exception === message
|
20
|
+
{ :exception => message }
|
21
|
+
when message.respond_to?(:to_hash)
|
22
|
+
message.to_hash
|
23
|
+
when message.respond_to?(:to_s)
|
24
|
+
string_to_notice(message.to_s)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def default_info
|
29
|
+
default_info = {
|
30
|
+
:summary => "N/A",
|
31
|
+
:environment => env,
|
32
|
+
:ruby_version => ruby_version,
|
33
|
+
:ruby_platform => ruby_platform
|
34
|
+
}
|
35
|
+
default_info = add_ruby_info(default_info)
|
36
|
+
default_info = add_rails_info(default_info) if rails_configuration
|
37
|
+
default_info
|
38
|
+
end
|
39
|
+
|
40
|
+
def string_to_notice(message)
|
41
|
+
{ :summary => message }
|
42
|
+
end
|
43
|
+
|
44
|
+
def exception_to_notice(hash)
|
45
|
+
return hash unless hash.key?(:exception)
|
46
|
+
exception = hash[:exception]
|
47
|
+
{
|
48
|
+
:summary => "#{exception.class.name}: #{exception.message}",
|
49
|
+
:error_class => exception.class.name,
|
50
|
+
:error_message => exception.message,
|
51
|
+
:backtrace => exception.backtrace,
|
52
|
+
}.merge(hash)
|
53
|
+
end
|
54
|
+
|
55
|
+
def add_rails_info(data)
|
56
|
+
data.merge({
|
57
|
+
:rails_env => rails_configuration.env,
|
58
|
+
:rails_root => rails_configuration.root,
|
59
|
+
:rails_version => rails_configuration.version
|
60
|
+
})
|
61
|
+
end
|
62
|
+
|
63
|
+
def add_ruby_info(data)
|
64
|
+
data.merge({
|
65
|
+
:ruby_version => ruby_version,
|
66
|
+
:ruby_platform => ruby_platform
|
67
|
+
})
|
68
|
+
end
|
69
|
+
|
70
|
+
def ruby_version
|
71
|
+
RUBY_VERSION
|
72
|
+
end
|
73
|
+
|
74
|
+
def ruby_platform
|
75
|
+
RUBY_PLATFORM
|
76
|
+
end
|
77
|
+
|
78
|
+
def env
|
79
|
+
ENV.to_hash
|
80
|
+
end
|
81
|
+
|
82
|
+
def rails_configuration
|
83
|
+
defined?(Rails) && Rails
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Chatterbox
|
2
|
+
|
3
|
+
module RailsCatcher
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
if base.instance_methods.map(&:to_s).include? 'rescue_action_in_public' and !base.instance_methods.map(&:to_s).include? 'rescue_action_in_public_without_chatterbox'
|
7
|
+
base.send(:alias_method, :rescue_action_in_public_without_chatterbox, :rescue_action_in_public)
|
8
|
+
base.send(:alias_method, :rescue_action_in_public, :rescue_action_in_public_with_chatterbox)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# Overrides the rescue_action method in ActionController::Base, but does not inhibit
|
13
|
+
# any custom processing that is defined with Rails 2's exception helpers.
|
14
|
+
def rescue_action_in_public_with_chatterbox exception
|
15
|
+
Chatterbox.logger.debug { "#{self.class}#rescue_action_in_public_with_chatterbox: caught exception #{exception} - about to handle"}
|
16
|
+
Chatterbox.handle_notice(exception)
|
17
|
+
Chatterbox.logger.debug { "#{self.class}#rescue_action_in_public_with_chatterbox: handing exception #{exception} off to normal rescue handling"}
|
18
|
+
|
19
|
+
rescue_action_in_public_without_chatterbox(exception)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
data/lib/chatterbox.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), *%w[chatterbox notification])
|
2
|
+
require File.join(File.dirname(__FILE__), *%w[consumers])
|
3
|
+
|
4
|
+
module Chatterbox
|
5
|
+
|
6
|
+
def handle_notice(message)
|
7
|
+
notice = Notification.new(message).notice
|
8
|
+
publish_notice(notice)
|
9
|
+
end
|
10
|
+
|
11
|
+
def publish_notice(notice)
|
12
|
+
Publishers.publishers.each { |p| p.call(notice) }
|
13
|
+
end
|
14
|
+
|
15
|
+
def logger
|
16
|
+
@logger ||= rails_default_logger || Logger.new(STDOUT)
|
17
|
+
end
|
18
|
+
|
19
|
+
def logger=(logger)
|
20
|
+
@logger = logger
|
21
|
+
end
|
22
|
+
|
23
|
+
def rails_default_logger
|
24
|
+
defined?(RAILS_DEFAULT_LOGGER) ? RAILS_DEFAULT_LOGGER : nil
|
25
|
+
end
|
26
|
+
|
27
|
+
extend self
|
28
|
+
|
29
|
+
module Publishers
|
30
|
+
|
31
|
+
class << self
|
32
|
+
|
33
|
+
def publishers
|
34
|
+
@publishers ||= []
|
35
|
+
end
|
36
|
+
|
37
|
+
def register(&blk)
|
38
|
+
Chatterbox.logger.debug { "Registering publisher: #{blk}"}
|
39
|
+
publishers << blk
|
40
|
+
blk
|
41
|
+
end
|
42
|
+
|
43
|
+
def clear!
|
44
|
+
@publishers = []
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Chatterbox
|
2
|
+
class EmailConsumer
|
3
|
+
|
4
|
+
attr_reader :notice
|
5
|
+
|
6
|
+
def initialize(notice)
|
7
|
+
@notice = notice
|
8
|
+
end
|
9
|
+
|
10
|
+
def process
|
11
|
+
Chatterbox.logger.debug { "Mailing notification #{notice[:summary]}"}
|
12
|
+
Mailer.deliver_exception_notification(notice)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
# Things taken out of the hash for exception emails:
|
18
|
+
#
|
19
|
+
# :details => a hash of details about the context of the error -- ie current state, request info, etc...any info
|
20
|
+
# related to the exception that is domain specific
|
21
|
+
# :error_class => taken from the Exception
|
22
|
+
# :error_message => taken from the Exception
|
23
|
+
# :backtrace => taken from the Exception
|
24
|
+
class Mailer < ActionMailer::Base
|
25
|
+
@@sender_address = %("Exception Notifier" <exception.notifier@default.com>)
|
26
|
+
cattr_accessor :sender_address
|
27
|
+
|
28
|
+
@@exception_recipients = []
|
29
|
+
cattr_accessor :exception_recipients
|
30
|
+
|
31
|
+
@@email_prefix = "[ERROR] "
|
32
|
+
cattr_accessor :email_prefix
|
33
|
+
|
34
|
+
self.template_root = File.expand_path(File.join(File.dirname(__FILE__), *%w[.. .. views]))
|
35
|
+
|
36
|
+
def self.reloadable?() false end
|
37
|
+
|
38
|
+
def exception_notification(data={})
|
39
|
+
data = data.dup.symbolize_keys
|
40
|
+
|
41
|
+
content_type "text/plain"
|
42
|
+
|
43
|
+
subject "#{email_prefix} Error - #{data.delete(:summary)}"
|
44
|
+
|
45
|
+
recipients exception_recipients
|
46
|
+
from sender_address
|
47
|
+
|
48
|
+
body data
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def sanitize_backtrace(trace)
|
54
|
+
re = Regexp.new(/^#{Regexp.escape(rails_root)}/)
|
55
|
+
trace.map { |line| Pathname.new(line.gsub(re, "[RAILS_ROOT]")).cleanpath.to_s }
|
56
|
+
end
|
57
|
+
|
58
|
+
def rails_root
|
59
|
+
@rails_root ||= Pathname.new(RAILS_ROOT).cleanpath.to_s
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
data/lib/consumers.rb
ADDED
data/rails/init.rb
ADDED
data/todo.markdown
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
* shouldn't require _action_mailer_ unless really needed by the mailer
|
2
|
+
|
3
|
+
# rails catcher
|
4
|
+
* make sure we push through request info for exceptions
|
5
|
+
* wire ignore exceptions in rails catcher
|
6
|
+
* wire up common idioms (or easy ability to) to grab current_user from request, current_url, etc
|
7
|
+
|
data/version.yml
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Error Message
|
2
|
+
--------------
|
3
|
+
<%= @error_class %>: <%= @error_message %>
|
4
|
+
|
5
|
+
Details
|
6
|
+
-------
|
7
|
+
<% @details.keys.map(&:to_s).sort.each do |key| -%>
|
8
|
+
<%= %[#{key} => #{@details[key]}] %>
|
9
|
+
<% end if @details -%>
|
10
|
+
|
11
|
+
Backtrace
|
12
|
+
----------
|
13
|
+
<%= @backtrace.join "\n" if @backtrace %>
|
14
|
+
|
15
|
+
Environment
|
16
|
+
------------
|
17
|
+
<% @environment.keys.sort.each do |key| -%>
|
18
|
+
<%= %[#{key} => #{@environment[key]}] %>
|
19
|
+
<% end -%>
|
metadata
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: chatterbox
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Rob Sanheim
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-05 00:00:00 -04:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: mocha
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: actioncontroller
|
27
|
+
type: :development
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: spicycode-micronaut
|
37
|
+
type: :development
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: "0"
|
44
|
+
version:
|
45
|
+
- !ruby/object:Gem::Dependency
|
46
|
+
name: spicycode-micronaut-rails
|
47
|
+
type: :development
|
48
|
+
version_requirement:
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: "0"
|
54
|
+
version:
|
55
|
+
description:
|
56
|
+
email: rsanheim@gmail.com
|
57
|
+
executables: []
|
58
|
+
|
59
|
+
extensions: []
|
60
|
+
|
61
|
+
extra_rdoc_files:
|
62
|
+
- LICENSE
|
63
|
+
- README.markdown
|
64
|
+
files:
|
65
|
+
- .gitignore
|
66
|
+
- .treasure_map.rb
|
67
|
+
- LICENSE
|
68
|
+
- README.markdown
|
69
|
+
- Rakefile
|
70
|
+
- chatterbox.gemspec
|
71
|
+
- examples/chatterbox_example.rb
|
72
|
+
- examples/example_helper.rb
|
73
|
+
- examples/lib/chatterbox/consumers/email_consumer_example.rb
|
74
|
+
- examples/lib/chatterbox/notification_example.rb
|
75
|
+
- examples/lib/chatterbox/rails_catcher_example.rb
|
76
|
+
- init.rb
|
77
|
+
- lib/chatterbox.rb
|
78
|
+
- lib/chatterbox/notification.rb
|
79
|
+
- lib/chatterbox/rails_catcher.rb
|
80
|
+
- lib/consumers.rb
|
81
|
+
- lib/consumers/email_consumer.rb
|
82
|
+
- rails/init.rb
|
83
|
+
- todo.markdown
|
84
|
+
- version.yml
|
85
|
+
- views/chatterbox/mailer/exception_notification.erb
|
86
|
+
has_rdoc: true
|
87
|
+
homepage: http://github.com/rsanheim/chatterbox
|
88
|
+
licenses: []
|
89
|
+
|
90
|
+
post_install_message:
|
91
|
+
rdoc_options:
|
92
|
+
- --charset=UTF-8
|
93
|
+
require_paths:
|
94
|
+
- lib
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: "0"
|
100
|
+
version:
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: "0"
|
106
|
+
version:
|
107
|
+
requirements: []
|
108
|
+
|
109
|
+
rubyforge_project:
|
110
|
+
rubygems_version: 1.3.5
|
111
|
+
signing_key:
|
112
|
+
specification_version: 3
|
113
|
+
summary: Notifications and messages
|
114
|
+
test_files:
|
115
|
+
- examples/chatterbox_example.rb
|
116
|
+
- examples/example_helper.rb
|
117
|
+
- examples/lib/chatterbox/consumers/email_consumer_example.rb
|
118
|
+
- examples/lib/chatterbox/notification_example.rb
|
119
|
+
- examples/lib/chatterbox/rails_catcher_example.rb
|