exceptions_begone_notifier 0.2.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 +4 -0
- data/LICENSE +21 -0
- data/Manifest +0 -0
- data/README.rdoc +96 -0
- data/Rakefile +51 -0
- data/TODO +1 -0
- data/VERSION +1 -0
- data/lib/exceptions_begone/connection_configurator.rb +52 -0
- data/lib/exceptions_begone/exceptions_support/cache.rb +44 -0
- data/lib/exceptions_begone/exceptions_support/catcher.rb +23 -0
- data/lib/exceptions_begone/formatter.rb +51 -0
- data/lib/exceptions_begone/sender.rb +45 -0
- data/lib/exceptions_begone_notifier.rb +9 -0
- data/test/test_cache.rb +48 -0
- data/test/test_catcher.rb +73 -0
- data/test/test_connection_configurator.rb +12 -0
- data/test/test_formatter.rb +61 -0
- data/test/test_helper.rb +14 -0
- data/test/test_sender.rb +49 -0
- metadata +89 -0
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2009 {XING AG}[http://www.xing.com/]
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/Manifest
ADDED
File without changes
|
data/README.rdoc
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
=ExceptionsBegoneIdentifier
|
2
|
+
|
3
|
+
This gem catch all exceptions from a Rails application and send them
|
4
|
+
to a ExceptionsBegone server. Actually it doesn't has to be an exception.
|
5
|
+
You can easily send *any* *kind* *of* *notification* and let the server aggregate it for you. It could be for example
|
6
|
+
a cron or nagios message.
|
7
|
+
|
8
|
+
This project is currently under development, however it is stable enough to use
|
9
|
+
it in production environment.
|
10
|
+
|
11
|
+
==Prerequisites
|
12
|
+
|
13
|
+
This gem is <tt>require 'rubygems'</tt> free. See {here}[http://tomayko.com/writings/require-rubygems-antipattern] for explanation.
|
14
|
+
If you want to use rubygems as a gems source do <tt>export RUBYOPT="rubygems"</tt>
|
15
|
+
|
16
|
+
This gem send informations to an {Exceptions Begone Server}[http://github.com/xing/exceptions_begone].
|
17
|
+
See the README for the installation guide.
|
18
|
+
|
19
|
+
The gem relies that the Rails.logger responds to <tt>error</tt> method (which is a default behavior in Rails applications).
|
20
|
+
|
21
|
+
==Downloading gem
|
22
|
+
|
23
|
+
<tt>gem sources -a http://gemcutter.org</tt>
|
24
|
+
|
25
|
+
or if you have the gemcutter gem installed:
|
26
|
+
|
27
|
+
<tt>gem install exceptions_begone_notifier</tt>
|
28
|
+
|
29
|
+
==Enabling functionality
|
30
|
+
|
31
|
+
Add <tt>require "exceptions_begone_notifier"</tt> in the proper place (e.g. in <tt>config/environment.rb</tt>).
|
32
|
+
|
33
|
+
To activate the gem add following lines to you application configuration file (e.g. <tt>config/environments/production.rb</tt>).
|
34
|
+
|
35
|
+
ExceptionsBegone::Catcher.catch_exceptions do |catcher|
|
36
|
+
catcher.host = "exceptions_begone_server"
|
37
|
+
end
|
38
|
+
|
39
|
+
Sender expects following parameters for configuration:
|
40
|
+
* <tt>project</tt>: name of project (this project has to exist on ExceptionsBegone server)
|
41
|
+
* <tt>host</tt>: host name of the ExceptionsBegone server
|
42
|
+
* <tt>port</tt>: same as above
|
43
|
+
* <tt>open_timeout</tt>: http timeout (in sec)
|
44
|
+
* <tt>read_timeout</tt>: same as above
|
45
|
+
|
46
|
+
==Usage
|
47
|
+
|
48
|
+
After activation all exceptions will be send via HTTP POST (JSON) to the ExceptionsBegone server.
|
49
|
+
|
50
|
+
If you want to send a notification use:
|
51
|
+
|
52
|
+
<tt>ExceptionsBegone::Sender.send_YOUR_CATEGORY_GOES_HERE({:identifier => "NotificationName", :payload => {:anything => "you want"}})</tt>
|
53
|
+
|
54
|
+
or
|
55
|
+
|
56
|
+
<tt>ExceptionsBegone::Sender.send_YOUR_CATEGORY_GOES_HERE({:identifier => "NotificationName", :payload => {:anything => "you want"}}, :host => "new host address", :port => "new port")</tt>
|
57
|
+
|
58
|
+
if want to override the settings from the ExceptionsBegone::Catcher. As you can see the category of your notification (e.g. nagios_message or warning)
|
59
|
+
is taken from the method name: "send_ *category*()".
|
60
|
+
|
61
|
+
==Throttling traffic between servers
|
62
|
+
|
63
|
+
If you really need to throttle number of send exceptions you will need a caching layer. You could write your own or use
|
64
|
+
following implementation ExceptionsBegone::Cache and enable it with:
|
65
|
+
|
66
|
+
<tt>ExceptionsBegone::Sender.extend ExceptionsBegone::Cache</tt>
|
67
|
+
|
68
|
+
==Authors
|
69
|
+
|
70
|
+
{Patryk Peszko}[http://github.com/ppeszko]
|
71
|
+
|
72
|
+
==Contributors
|
73
|
+
|
74
|
+
==License
|
75
|
+
|
76
|
+
The MIT License
|
77
|
+
|
78
|
+
Copyright (c) 2009 {XING AG}[http://www.xing.com/]
|
79
|
+
|
80
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
81
|
+
of this software and associated documentation files (the "Software"), to deal
|
82
|
+
in the Software without restriction, including without limitation the rights
|
83
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
84
|
+
copies of the Software, and to permit persons to whom the Software is
|
85
|
+
furnished to do so, subject to the following conditions:
|
86
|
+
|
87
|
+
The above copyright notice and this permission notice shall be included in
|
88
|
+
all copies or substantial portions of the Software.
|
89
|
+
|
90
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
91
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
92
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
93
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
94
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
95
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
96
|
+
THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'rake'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'jeweler'
|
5
|
+
Jeweler::Tasks.new do |gemspec|
|
6
|
+
gemspec.name = "exceptions_begone_notifier"
|
7
|
+
gemspec.description = "Catch and send exceptions to exceptions_begone service"
|
8
|
+
gemspec.summary = "Catch and send exceptions to exceptions_begone service"
|
9
|
+
gemspec.homepage = "http://github.com/ppeszko/exceptions_begone_notifier"
|
10
|
+
gemspec.authors = ["Patryk Peszko"]
|
11
|
+
gemspec.add_dependency('activesupport')
|
12
|
+
end
|
13
|
+
Jeweler::GemcutterTasks.new
|
14
|
+
rescue LoadError
|
15
|
+
puts "Jeweler not available. Install it with: sudo gem install jeweler"
|
16
|
+
end
|
17
|
+
|
18
|
+
require 'rake/testtask'
|
19
|
+
Rake::TestTask.new(:test) do |test|
|
20
|
+
test.libs << 'lib' << 'test'
|
21
|
+
test.pattern = 'test/**/test_*.rb'
|
22
|
+
test.verbose = true
|
23
|
+
end
|
24
|
+
|
25
|
+
begin
|
26
|
+
require 'rcov/rcovtask'
|
27
|
+
Rcov::RcovTask.new do |test|
|
28
|
+
test.libs << 'test'
|
29
|
+
test.pattern = 'test/**/test_*.rb'
|
30
|
+
test.verbose = true
|
31
|
+
test.rcov_opts << '--exclude "gems/*"'
|
32
|
+
end
|
33
|
+
rescue LoadError
|
34
|
+
task :rcov do
|
35
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
task :test => :check_dependencies
|
40
|
+
|
41
|
+
task :default => :test
|
42
|
+
|
43
|
+
require 'rake/rdoctask'
|
44
|
+
Rake::RDocTask.new do |rdoc|
|
45
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
46
|
+
|
47
|
+
rdoc.rdoc_dir = 'rdoc'
|
48
|
+
rdoc.title = "exceptions_begone_notifier #{version}"
|
49
|
+
rdoc.rdoc_files.include('README*')
|
50
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
51
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.2.3
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module ExceptionsBegone
|
2
|
+
class ConnectionConfigurator
|
3
|
+
@@defaults = {
|
4
|
+
:project => "production",
|
5
|
+
:port => 80,
|
6
|
+
:open_timeout => 5,
|
7
|
+
:read_timeout => 5,
|
8
|
+
:host => "127.0.0.1"
|
9
|
+
}
|
10
|
+
|
11
|
+
class << self
|
12
|
+
attr_writer :global_connection
|
13
|
+
|
14
|
+
def global_connection
|
15
|
+
@global_connection ||= ConnectionConfigurator.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def global_parameters=(parameters = {})
|
19
|
+
parameters = parameters.marshal_dump if parameters.respond_to?(:marshal_dump)
|
20
|
+
self.global_connection = ConnectionConfigurator.build(parameters)
|
21
|
+
end
|
22
|
+
|
23
|
+
def build(parameters = {})
|
24
|
+
parameters.blank? ? self.global_connection : new(parameters)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(parameters = {})
|
30
|
+
parameters.each do |method_name, key|
|
31
|
+
self.__send__("#{method_name}=", key)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def path
|
36
|
+
"/projects/#{project}/notifications"
|
37
|
+
end
|
38
|
+
|
39
|
+
def method_missing(method_id, *args, &block)
|
40
|
+
method_id.to_s =~ /(\w*)(=)?/
|
41
|
+
name, setter = $1, $2
|
42
|
+
|
43
|
+
super unless @@defaults.include?(name.to_sym)
|
44
|
+
|
45
|
+
if setter
|
46
|
+
instance_variable_set("@#{name}", *args)
|
47
|
+
else
|
48
|
+
instance_variable_get("@#{name}") || @@defaults[method_id]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
|
3
|
+
module ExceptionsBegone::Cache
|
4
|
+
|
5
|
+
HOURLY_SEND_LIMIT = 500
|
6
|
+
TTL = 60
|
7
|
+
|
8
|
+
def self.extended(target)
|
9
|
+
target.instance_eval do
|
10
|
+
class << self
|
11
|
+
alias_method :send_exception_without_cache, :send_exception
|
12
|
+
alias_method :send_exception, :send_exception_with_cache
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def send_exception_with_cache(exception, controller, request, connection_options = {})
|
18
|
+
exception_signature = Digest::MD5.hexdigest(exception.backtrace.join.to_s)
|
19
|
+
return if skip_sending?(exception_signature)
|
20
|
+
|
21
|
+
save_signature_in_cache(exception_signature)
|
22
|
+
send_exception_without_cache(exception, controller, request, connection_options = {})
|
23
|
+
end
|
24
|
+
|
25
|
+
def skip_sending?(exception_signature)
|
26
|
+
signature_equal?(exception_signature) || hourly_send_limit_reached?
|
27
|
+
end
|
28
|
+
|
29
|
+
def signature_equal?(exception_signature)
|
30
|
+
Rails.cache.read('last_exception_signature', :raw => true) == exception_signature
|
31
|
+
end
|
32
|
+
|
33
|
+
def hourly_send_limit_reached?
|
34
|
+
key = "exceptions_sent_in_last_hour.#{Time.now.hour}"
|
35
|
+
sent_number = Rails.cache.fetch(key, :expires_in => 1.hour, :raw => true) do
|
36
|
+
0
|
37
|
+
end
|
38
|
+
Rails.cache.increment(key) > HOURLY_SEND_LIMIT
|
39
|
+
end
|
40
|
+
|
41
|
+
def save_signature_in_cache(exception_signature)
|
42
|
+
Rails.cache.write('last_exception_signature', exception_signature, :expires_in => TTL, :raw => true)
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ExceptionsBegone
|
2
|
+
module Catcher
|
3
|
+
def catch_exceptions(&block)
|
4
|
+
ActionController::Base.__send__(:include, InstanceMethods)
|
5
|
+
parameters = OpenStruct.new
|
6
|
+
block.call(parameters) if block_given?
|
7
|
+
ConnectionConfigurator.global_parameters = parameters
|
8
|
+
end
|
9
|
+
module_function :catch_exceptions
|
10
|
+
|
11
|
+
module InstanceMethods
|
12
|
+
def self.included(target)
|
13
|
+
target.send(:alias_method, :rescue_action_in_public_without_catcher, :rescue_action_in_public)
|
14
|
+
target.send(:alias_method, :rescue_action_in_public, :rescue_action_in_public_with_catcher)
|
15
|
+
end
|
16
|
+
|
17
|
+
def rescue_action_in_public_with_catcher(exception)
|
18
|
+
Sender.send_exception(exception, self, request)
|
19
|
+
rescue_action_in_public_without_catcher(exception)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module ExceptionsBegone
|
2
|
+
class Formatter
|
3
|
+
class << self
|
4
|
+
def format_data(category, notification)
|
5
|
+
notification.symbolize_keys!
|
6
|
+
notification.merge!(:category => category)
|
7
|
+
serialized_notification = serialize_data(notification)
|
8
|
+
to_json(serialized_notification)
|
9
|
+
end
|
10
|
+
|
11
|
+
def format_exception_data(exception, controller, request)
|
12
|
+
{ :identifier => generate_identifier(controller, exception),
|
13
|
+
:payload => {
|
14
|
+
:parameters => request.parameters,
|
15
|
+
:url => request.url,
|
16
|
+
:ip => request.ip,
|
17
|
+
:request_environment => request.env,
|
18
|
+
:session => request.session,
|
19
|
+
:environment => ENV.to_hash,
|
20
|
+
:backtrace => exception.backtrace
|
21
|
+
}
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
def generate_identifier(controller, exception)
|
26
|
+
"#{controller.controller_name}##{controller.action_name} (#{exception.class}) #{exception.message.inspect}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def serialize_data(data)
|
30
|
+
case data
|
31
|
+
when String
|
32
|
+
data
|
33
|
+
when Hash
|
34
|
+
data.inject({}) do |result, (key, value)|
|
35
|
+
result.update(key => serialize_data(value))
|
36
|
+
end
|
37
|
+
when Array
|
38
|
+
data.map do |elem|
|
39
|
+
serialize_data(elem)
|
40
|
+
end
|
41
|
+
else
|
42
|
+
data.to_s
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_json(attributes)
|
47
|
+
ActiveSupport::JSON.encode(:notification => attributes)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module ExceptionsBegone
|
2
|
+
class Sender
|
3
|
+
class << self
|
4
|
+
# ugly
|
5
|
+
attr_accessor :mailer
|
6
|
+
|
7
|
+
def post(data, connection_options = {})
|
8
|
+
conn_conf = ConnectionConfigurator.build(connection_options)
|
9
|
+
|
10
|
+
http = Net::HTTP.new(conn_conf.host, conn_conf.port)
|
11
|
+
http.read_timeout = conn_conf.read_timeout
|
12
|
+
http.open_timeout = conn_conf.open_timeout
|
13
|
+
|
14
|
+
log(data)
|
15
|
+
http.post(conn_conf.path, data, "Content-type" => "application/json", "Accept" => "application/json")
|
16
|
+
rescue TimeoutError, Errno::ECONNREFUSED => e
|
17
|
+
log(e.inspect)
|
18
|
+
log(data)
|
19
|
+
mailer.deliver_error(e.message, "#{e.inspect} #{data}") if mailer
|
20
|
+
end
|
21
|
+
|
22
|
+
def send_exception(exception, controller, request, connection_options = {})
|
23
|
+
notification = Formatter.format_exception_data(exception, controller, request)
|
24
|
+
send_generic("exception", notification, connection_options = {})
|
25
|
+
end
|
26
|
+
|
27
|
+
def send_generic(category, notification, connection_options = {})
|
28
|
+
data = Formatter.format_data(category, notification)
|
29
|
+
post(data, connection_options)
|
30
|
+
end
|
31
|
+
|
32
|
+
def log(message)
|
33
|
+
Rails.logger.error("[EXCEPTIONS_BEGONE]: #{message}")
|
34
|
+
end
|
35
|
+
|
36
|
+
def method_missing(method_name, *args)
|
37
|
+
if method_name.to_s =~ /^send_(.*)/
|
38
|
+
Sender.__send__(:send_generic, "#{$1}", *args)
|
39
|
+
else
|
40
|
+
super(method_name, *args)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'active_support/json'
|
3
|
+
require 'ostruct'
|
4
|
+
|
5
|
+
require 'exceptions_begone/connection_configurator'
|
6
|
+
require 'exceptions_begone/sender'
|
7
|
+
require 'exceptions_begone/formatter'
|
8
|
+
require 'exceptions_begone/exceptions_support/catcher'
|
9
|
+
require 'exceptions_begone/exceptions_support/cache'
|
data/test/test_cache.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Rails
|
4
|
+
end
|
5
|
+
|
6
|
+
class TestCachingInterface < Test::Unit::TestCase
|
7
|
+
|
8
|
+
class ExceptionsBegone::TestSender
|
9
|
+
class << self
|
10
|
+
def send_exception(exception, controller, request, connection_options = {})
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
ExceptionsBegone::TestSender.extend ExceptionsBegone::Cache
|
16
|
+
|
17
|
+
def setup
|
18
|
+
@cache = stub("cache")
|
19
|
+
Rails.stubs(:cache).returns(@cache)
|
20
|
+
@exception = Exception.new
|
21
|
+
@exception.stubs(:backtrace).returns([])
|
22
|
+
@hashed_backtrace = Digest::MD5.hexdigest(@exception.backtrace.join.to_s)
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_cache_should_be_enabled_after_including
|
26
|
+
ExceptionsBegone::TestSender.expects(:skip_sending?).returns(true)
|
27
|
+
|
28
|
+
ExceptionsBegone::TestSender.send_exception(@exception, "controller", "request")
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_should_send_exception_on_new_exception_if_the_hourly_limit_is_not_reached
|
32
|
+
ExceptionsBegone::TestSender.expects(:signature_equal?).with(@hashed_backtrace).returns(false)
|
33
|
+
ExceptionsBegone::TestSender.expects(:hourly_send_limit_reached?).returns(false)
|
34
|
+
|
35
|
+
ExceptionsBegone::TestSender.expects(:save_signature_in_cache).with(@hashed_backtrace)
|
36
|
+
ExceptionsBegone::TestSender.expects(:send_exception_without_cache).with(@exception, "controller", "request", {})
|
37
|
+
ExceptionsBegone::TestSender.send_exception(@exception, "controller", "request")
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_should_not_send_exception_on_new_exception_if_the_hourly_limit_is_reached
|
41
|
+
ExceptionsBegone::TestSender.expects(:signature_equal?).with(@hashed_backtrace).returns(false)
|
42
|
+
ExceptionsBegone::TestSender.expects(:hourly_send_limit_reached?).returns(true)
|
43
|
+
|
44
|
+
ExceptionsBegone::TestSender.expects(:save_signature_in_cache).never
|
45
|
+
ExceptionsBegone::TestSender.expects(:send_exception_without_cache).never
|
46
|
+
ExceptionsBegone::TestSender.send_exception(@exception, "controller", "request")
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'action_controller'
|
3
|
+
require 'net/http'
|
4
|
+
|
5
|
+
ActionController::Routing::Routes.draw do |map|
|
6
|
+
map.connect ':controller/:action/:id'
|
7
|
+
end
|
8
|
+
|
9
|
+
class TestCatchingExceptions < ActionController::TestCase
|
10
|
+
|
11
|
+
class CatchingExceptionsController < ActionController::Base
|
12
|
+
CatchingExceptionsController.consider_all_requests_local = false
|
13
|
+
|
14
|
+
def action_with_exception
|
15
|
+
raise "Exception from #{self.class.name}"
|
16
|
+
end
|
17
|
+
|
18
|
+
ExceptionsBegone::Catcher.catch_exceptions
|
19
|
+
end
|
20
|
+
|
21
|
+
tests CatchingExceptionsController
|
22
|
+
def setup
|
23
|
+
@request.remote_addr = '1.2.3.4'
|
24
|
+
@request.host = 'example.com'
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_should_catch_all_exceptions_from_controller
|
28
|
+
CatchingExceptionsController.any_instance.expects(:rescue_action_in_public)
|
29
|
+
|
30
|
+
get :action_with_exception
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_should_send_the_exception
|
34
|
+
ExceptionsBegone::Sender.expects(:send_generic).with("exception", anything, anything)
|
35
|
+
CatchingExceptionsController.any_instance.expects(:rescue_action_in_public_without_catcher)
|
36
|
+
|
37
|
+
get :action_with_exception
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class TestConfiguringCatcher < ActionController::TestCase
|
42
|
+
class ConfiguringExceptionsController < ActionController::Base
|
43
|
+
ConfiguringExceptionsController.consider_all_requests_local = false
|
44
|
+
|
45
|
+
def action_with_exception
|
46
|
+
raise "Exception from #{self.class.name}"
|
47
|
+
end
|
48
|
+
|
49
|
+
ExceptionsBegone::Catcher.catch_exceptions do |catcher|
|
50
|
+
catcher.project = "test_configuration_project"
|
51
|
+
catcher.host = "my_host"
|
52
|
+
catcher.port = 987
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
tests ConfiguringExceptionsController
|
57
|
+
def test_should_send_exceptions_to_chosen_service
|
58
|
+
ExceptionsBegone::Sender.stubs(:log)
|
59
|
+
@request.remote_addr = '1.2.3.4'
|
60
|
+
@request.host = 'my_host'
|
61
|
+
|
62
|
+
notification = {:status => "ok"}
|
63
|
+
parameters = {:port => 987, :host => "my_host", :project => "test_configuration_project"}
|
64
|
+
net_http = Net::HTTP.new(parameters[:host], parameters[:port])
|
65
|
+
|
66
|
+
Net::HTTP.expects(:new).with(parameters[:host], parameters[:port]).returns(net_http)
|
67
|
+
Net::HTTP.any_instance.expects(:post).with("/projects/#{parameters[:project]}/notifications", anything, anything)
|
68
|
+
ConfiguringExceptionsController.any_instance.expects(:rescue_action_in_public_without_catcher)
|
69
|
+
|
70
|
+
get :action_with_exception
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class TestConnectionConfigurator < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
ExceptionsBegone::ConnectionConfigurator.instance_variable_set("@global_connection", nil)
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_build_should_return_a_connection_cofigurator_with_default_settings
|
9
|
+
conn_conf = ExceptionsBegone::ConnectionConfigurator.build
|
10
|
+
assert_equal "127.0.0.1", conn_conf.host
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'action_controller'
|
3
|
+
|
4
|
+
class TestFormatter < Test::Unit::TestCase
|
5
|
+
def test_serialize_data
|
6
|
+
object = Object.new
|
7
|
+
nested_object = Object.new
|
8
|
+
object_in_array = Object.new
|
9
|
+
notification = {:string => "", :hash => {}, :array => [object_in_array], :object => object, :nested => {:nested_object => nested_object}}
|
10
|
+
|
11
|
+
object.expects(:to_s).returns("object")
|
12
|
+
nested_object.expects(:to_s).returns("nested_object")
|
13
|
+
object_in_array.expects(:to_s).returns("object_in_array")
|
14
|
+
|
15
|
+
serialize_data = ExceptionsBegone::Formatter.serialize_data(notification)
|
16
|
+
assert_equal "object", serialize_data[:object]
|
17
|
+
assert_equal "nested_object", serialize_data[:nested][:nested_object]
|
18
|
+
assert_equal ["object_in_array"], serialize_data[:array]
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_all_data_should_be_serialized
|
22
|
+
ExceptionsBegone::Formatter.expects(:serialize_data).returns("")
|
23
|
+
|
24
|
+
ExceptionsBegone::Formatter.format_data("test_category", :payload => {})
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class TestFormattingExceptionsData < Test::Unit::TestCase
|
29
|
+
|
30
|
+
def setup
|
31
|
+
@exception = Exception.new("some exception")
|
32
|
+
@controller = ActionController::Base.new
|
33
|
+
request = stub(:parameters => "",
|
34
|
+
:url => "http://www.example.com",
|
35
|
+
:ip => "127.0.0.1",
|
36
|
+
:env => ENV.to_hash,
|
37
|
+
:session => "")
|
38
|
+
@controller.stubs(:request).returns(request)
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_generate_identifier
|
42
|
+
assert_equal "#{@controller.controller_name}##{@controller.action_name} (#{@exception.class}) #{@exception.message.inspect}", ExceptionsBegone::Formatter.generate_identifier(@controller, @exception)
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_format_exception_data_should_automatically_set_identifier
|
46
|
+
identifier = ExceptionsBegone::Formatter.format_exception_data(@exception, @controller, @controller.request)[:identifier]
|
47
|
+
|
48
|
+
assert_match(/#{@controller.controller_name}/, identifier)
|
49
|
+
assert_match(/#{@controller.action_name}/, identifier)
|
50
|
+
assert_match(/#{@exception.class}/, identifier)
|
51
|
+
assert_match(/#{@exception.message.inspect}/, identifier)
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_format_exception_data_should_automatically_set_payload
|
55
|
+
payload = ExceptionsBegone::Formatter.format_exception_data(@exception, @controller, @controller.request)[:payload]
|
56
|
+
|
57
|
+
assert_equal(@controller.request.session, payload[:session])
|
58
|
+
assert_equal(@exception.backtrace, payload[:backtrace])
|
59
|
+
assert_equal(ENV.to_hash, payload[:environment])
|
60
|
+
end
|
61
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'mocha'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'redgreen' unless ENV['TM_FILENAME']
|
6
|
+
rescue LoadError
|
7
|
+
end
|
8
|
+
|
9
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
10
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
11
|
+
require 'exceptions_begone_notifier'
|
12
|
+
|
13
|
+
class Test::Unit::TestCase
|
14
|
+
end
|
data/test/test_sender.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'net/http'
|
3
|
+
|
4
|
+
class TestSendingNotifications < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def setup
|
7
|
+
ExceptionsBegone::Sender.stubs(:log)
|
8
|
+
@notification = { :identifier => "NotificationName", :payload => "payload" }
|
9
|
+
@parameters = {:port => 987, :host => "my_host", :project => "my_project"}
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_should_be_possible_to_send_notification
|
13
|
+
Net::HTTP.any_instance.expects(:post).with("/projects/production/notifications", anything, anything)
|
14
|
+
|
15
|
+
ExceptionsBegone::Sender.send_notification({:status => "ok"}, :host => "my_host")
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_should_be_possible_to_specify_parameters_connection_parameters
|
19
|
+
@notification = {:status => "ok"}
|
20
|
+
@parameters = {:port => 987, :host => "my_host", :project => "my_project"}
|
21
|
+
net_http = Net::HTTP.new(@parameters[:host], @parameters[:port])
|
22
|
+
|
23
|
+
Net::HTTP.expects(:new).with(@parameters[:host], @parameters[:port]).returns(net_http)
|
24
|
+
Net::HTTP.any_instance.expects(:post).with("/projects/#{@parameters[:project]}/notifications", anything, anything)
|
25
|
+
|
26
|
+
ExceptionsBegone::Sender.send_notification(@notification, @parameters)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_deliver_notfications_as_json
|
30
|
+
Net::HTTP.any_instance.expects(:post).with("/projects/#{@parameters[:project]}/notifications", @notification, 'Content-type' => 'application/json', 'Accept' => 'application/json')
|
31
|
+
|
32
|
+
ExceptionsBegone::Sender.post(@notification, @parameters)
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_should_handle_timeouts
|
36
|
+
Net::HTTP.any_instance.expects(:post).with("/projects/#{@parameters[:project]}/notifications", @notification, 'Content-type' => 'application/json', 'Accept' => 'application/json').raises(TimeoutError)
|
37
|
+
|
38
|
+
assert_nothing_raised do
|
39
|
+
ExceptionsBegone::Sender.post(@notification, @parameters)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_category_should_be_automatically_generated_from_method_name
|
44
|
+
encoded_notification = ActiveSupport::JSON.encode(:notification => {:category => 'warning'}.merge(@notification))
|
45
|
+
Net::HTTP.any_instance.expects(:post).with("/projects/#{@parameters[:project]}/notifications", encoded_notification, "Content-type" => "application/json", "Accept" => "application/json")
|
46
|
+
|
47
|
+
ExceptionsBegone::Sender.send_warning(@notification, @parameters)
|
48
|
+
end
|
49
|
+
end
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: exceptions_begone_notifier
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Patryk Peszko
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-12-03 00:00:00 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: activesupport
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
description: Catch and send exceptions to exceptions_begone service
|
26
|
+
email:
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- LICENSE
|
33
|
+
- README.rdoc
|
34
|
+
- TODO
|
35
|
+
files:
|
36
|
+
- .gitignore
|
37
|
+
- LICENSE
|
38
|
+
- Manifest
|
39
|
+
- README.rdoc
|
40
|
+
- Rakefile
|
41
|
+
- TODO
|
42
|
+
- VERSION
|
43
|
+
- lib/exceptions_begone/connection_configurator.rb
|
44
|
+
- lib/exceptions_begone/exceptions_support/cache.rb
|
45
|
+
- lib/exceptions_begone/exceptions_support/catcher.rb
|
46
|
+
- lib/exceptions_begone/formatter.rb
|
47
|
+
- lib/exceptions_begone/sender.rb
|
48
|
+
- lib/exceptions_begone_notifier.rb
|
49
|
+
- test/test_cache.rb
|
50
|
+
- test/test_catcher.rb
|
51
|
+
- test/test_connection_configurator.rb
|
52
|
+
- test/test_formatter.rb
|
53
|
+
- test/test_helper.rb
|
54
|
+
- test/test_sender.rb
|
55
|
+
has_rdoc: true
|
56
|
+
homepage: http://github.com/ppeszko/exceptions_begone_notifier
|
57
|
+
licenses: []
|
58
|
+
|
59
|
+
post_install_message:
|
60
|
+
rdoc_options:
|
61
|
+
- --charset=UTF-8
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: "0"
|
69
|
+
version:
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: "0"
|
75
|
+
version:
|
76
|
+
requirements: []
|
77
|
+
|
78
|
+
rubyforge_project:
|
79
|
+
rubygems_version: 1.3.5
|
80
|
+
signing_key:
|
81
|
+
specification_version: 3
|
82
|
+
summary: Catch and send exceptions to exceptions_begone service
|
83
|
+
test_files:
|
84
|
+
- test/test_cache.rb
|
85
|
+
- test/test_catcher.rb
|
86
|
+
- test/test_connection_configurator.rb
|
87
|
+
- test/test_formatter.rb
|
88
|
+
- test/test_helper.rb
|
89
|
+
- test/test_sender.rb
|