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