hoptoad-ribbit 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +5 -0
- data/.gitignore +23 -0
- data/LICENSE +21 -0
- data/README.rdoc +22 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/hoptoad-ribbit.gemspec +70 -0
- data/lib/ribbit.rb +100 -0
- data/lib/ribbit/adapters.rb +39 -0
- data/lib/ribbit/adapters/adapter.rb +29 -0
- data/lib/ribbit/adapters/merb.rb +53 -0
- data/lib/ribbit/adapters/none.rb +19 -0
- data/lib/ribbit/backtrace.rb +99 -0
- data/lib/ribbit/configuration.rb +234 -0
- data/lib/ribbit/notice.rb +283 -0
- data/lib/ribbit/sender.rb +70 -0
- data/spec/ribbit_spec.rb +13 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +9 -0
- metadata +94 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Copyright (c) 2007, Tammer Saleh, Thoughtbot, Inc.
|
|
2
|
+
Copyright (c) 2009 Glen Mailer
|
|
3
|
+
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
5
|
+
a copy of this software and associated documentation files (the
|
|
6
|
+
"Software"), to deal in the Software without restriction, including
|
|
7
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
8
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
9
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
10
|
+
the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be
|
|
13
|
+
included in all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
17
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
19
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
20
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
21
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
= Ribbit
|
|
2
|
+
|
|
3
|
+
This Hoptoad Notifier attempts to remove anything rails specific from the
|
|
4
|
+
original notifier released by Thoughtbot.
|
|
5
|
+
|
|
6
|
+
My intention is to have a completely rails-free core - and then provide
|
|
7
|
+
adapters for popular frameworks - firstly Merb as thats what I'm using,
|
|
8
|
+
and then subsequently things like Rails and Rack.
|
|
9
|
+
|
|
10
|
+
== Note on Patches/Pull Requests
|
|
11
|
+
|
|
12
|
+
* Fork the project.
|
|
13
|
+
* Make your feature addition or bug fix.
|
|
14
|
+
* Add tests for it. This is important so I don't break it in a
|
|
15
|
+
future version unintentionally.
|
|
16
|
+
* Commit, do not mess with rakefile, version, or history.
|
|
17
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
|
18
|
+
* Send me a pull request. Bonus points for topic branches.
|
|
19
|
+
|
|
20
|
+
== Copyright
|
|
21
|
+
|
|
22
|
+
Copyright (c) 2009 Glen Mailer. See LICENSE for details.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'rake'
|
|
3
|
+
|
|
4
|
+
begin
|
|
5
|
+
require 'jeweler'
|
|
6
|
+
Jeweler::Tasks.new do |gem|
|
|
7
|
+
gem.name = "hoptoad-ribbit"
|
|
8
|
+
gem.summary = "Generic Hoptoad Notifications and an adapter for merb"
|
|
9
|
+
gem.description = %q{
|
|
10
|
+
Provides a Module to handle sending notifications to hoptoad.
|
|
11
|
+
|
|
12
|
+
Currently includes a Merb adapter, but the intention is to add more later.
|
|
13
|
+
}
|
|
14
|
+
gem.email = "glenjamin@gmail.com"
|
|
15
|
+
gem.homepage = "http://github.com/glenjamin/hoptoad-notifier"
|
|
16
|
+
gem.authors = ["Glen Mailer"]
|
|
17
|
+
gem.add_dependency "builder", ">= 2.0.0"
|
|
18
|
+
gem.add_development_dependency "rspec", ">= 1.2.9"
|
|
19
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
|
20
|
+
end
|
|
21
|
+
Jeweler::GemcutterTasks.new
|
|
22
|
+
rescue LoadError
|
|
23
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
require 'spec/rake/spectask'
|
|
27
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
|
28
|
+
spec.libs << 'lib' << 'spec'
|
|
29
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
|
33
|
+
spec.libs << 'lib' << 'spec'
|
|
34
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
|
35
|
+
spec.rcov = true
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
task :spec => :check_dependencies
|
|
39
|
+
|
|
40
|
+
task :default => :spec
|
|
41
|
+
|
|
42
|
+
require 'rake/rdoctask'
|
|
43
|
+
Rake::RDocTask.new do |rdoc|
|
|
44
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
|
45
|
+
|
|
46
|
+
rdoc.rdoc_dir = 'rdoc'
|
|
47
|
+
rdoc.title = "ribbit #{version}"
|
|
48
|
+
rdoc.rdoc_files.include('README*')
|
|
49
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
50
|
+
end
|
data/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.1.0
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Generated by jeweler
|
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
|
4
|
+
# -*- encoding: utf-8 -*-
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |s|
|
|
7
|
+
s.name = %q{hoptoad-ribbit}
|
|
8
|
+
s.version = "0.1.0"
|
|
9
|
+
|
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
|
11
|
+
s.authors = ["Glen Mailer"]
|
|
12
|
+
s.date = %q{2010-01-05}
|
|
13
|
+
s.description = %q{
|
|
14
|
+
Provides a Module to handle sending notifications to hoptoad.
|
|
15
|
+
|
|
16
|
+
Currently includes a Merb adapter, but the intention is to add more later.
|
|
17
|
+
}
|
|
18
|
+
s.email = %q{glenjamin@gmail.com}
|
|
19
|
+
s.extra_rdoc_files = [
|
|
20
|
+
"LICENSE",
|
|
21
|
+
"README.rdoc"
|
|
22
|
+
]
|
|
23
|
+
s.files = [
|
|
24
|
+
".document",
|
|
25
|
+
".gitignore",
|
|
26
|
+
"LICENSE",
|
|
27
|
+
"README.rdoc",
|
|
28
|
+
"Rakefile",
|
|
29
|
+
"VERSION",
|
|
30
|
+
"hoptoad-ribbit.gemspec",
|
|
31
|
+
"lib/ribbit.rb",
|
|
32
|
+
"lib/ribbit/adapters.rb",
|
|
33
|
+
"lib/ribbit/adapters/adapter.rb",
|
|
34
|
+
"lib/ribbit/adapters/merb.rb",
|
|
35
|
+
"lib/ribbit/adapters/none.rb",
|
|
36
|
+
"lib/ribbit/backtrace.rb",
|
|
37
|
+
"lib/ribbit/configuration.rb",
|
|
38
|
+
"lib/ribbit/notice.rb",
|
|
39
|
+
"lib/ribbit/sender.rb",
|
|
40
|
+
"spec/ribbit_spec.rb",
|
|
41
|
+
"spec/spec.opts",
|
|
42
|
+
"spec/spec_helper.rb"
|
|
43
|
+
]
|
|
44
|
+
s.homepage = %q{http://github.com/glenjamin/hoptoad-notifier}
|
|
45
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
|
46
|
+
s.require_paths = ["lib"]
|
|
47
|
+
s.rubygems_version = %q{1.3.5}
|
|
48
|
+
s.summary = %q{Generic Hoptoad Notifications and an adapter for merb}
|
|
49
|
+
s.test_files = [
|
|
50
|
+
"spec/ribbit_spec.rb",
|
|
51
|
+
"spec/spec_helper.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_runtime_dependency(%q<builder>, [">= 2.0.0"])
|
|
60
|
+
s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
|
|
61
|
+
else
|
|
62
|
+
s.add_dependency(%q<builder>, [">= 2.0.0"])
|
|
63
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
|
64
|
+
end
|
|
65
|
+
else
|
|
66
|
+
s.add_dependency(%q<builder>, [">= 2.0.0"])
|
|
67
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
data/lib/ribbit.rb
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
require 'ribbit/configuration'
|
|
2
|
+
require 'ribbit/notice'
|
|
3
|
+
require 'ribbit/sender'
|
|
4
|
+
require 'ribbit/backtrace'
|
|
5
|
+
require 'ribbit/adapters'
|
|
6
|
+
require 'ribbit/adapters/adapter'
|
|
7
|
+
|
|
8
|
+
module Ribbit
|
|
9
|
+
CLIENT_NAME = "Ribbit"
|
|
10
|
+
VERSION = "0.1.0.dev"
|
|
11
|
+
API_VERSION = "2.0"
|
|
12
|
+
LOG_PREFIX = "** [Ribbit] "
|
|
13
|
+
|
|
14
|
+
class << self
|
|
15
|
+
# The sender object is responsible for delivering formatted data to the Hoptoad server.
|
|
16
|
+
# Must respond to #send_to_hoptoad. See Ribbit::Sender.
|
|
17
|
+
attr_accessor :sender
|
|
18
|
+
|
|
19
|
+
# A Hoptoad configuration object. Must act like a hash and return sensible
|
|
20
|
+
# values for all Hoptoad configuration options. See Ribbit::Configuration.
|
|
21
|
+
attr_accessor :configuration
|
|
22
|
+
|
|
23
|
+
# The adapter allows different things to happen on different environments
|
|
24
|
+
# Basicially we proxy some methods through to subclasses of
|
|
25
|
+
# Ribbit::Adapters::Adapter
|
|
26
|
+
attr_accessor :adapter
|
|
27
|
+
|
|
28
|
+
# Collection of all existing adapters
|
|
29
|
+
def adapters
|
|
30
|
+
Ribbit::Adapters.adapters
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Proxy logger onto the adapter
|
|
34
|
+
def logger
|
|
35
|
+
adapter.logger if adapter
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Call this method to modify defaults in your initializers.
|
|
39
|
+
#
|
|
40
|
+
# @example
|
|
41
|
+
# Ribbit.configure do |config|
|
|
42
|
+
# config.api_key = '1234567890abcdef'
|
|
43
|
+
# config.secure = false
|
|
44
|
+
# end
|
|
45
|
+
def configure(silent = false)
|
|
46
|
+
self.configuration ||= Configuration.new
|
|
47
|
+
yield(configuration)
|
|
48
|
+
self.sender = Sender.new(configuration)
|
|
49
|
+
# Attempt to attach an adapter, either by class or name
|
|
50
|
+
if adapters.include? configuration.adapter
|
|
51
|
+
self.adapter = configuration.adapter.new(configuration)
|
|
52
|
+
elsif configuration.adapter
|
|
53
|
+
adapter_class = Ribbit::Adapters.load_adapter configuration.adapter
|
|
54
|
+
self.adapter = adapter_class.new(configuration) rescue nil
|
|
55
|
+
end
|
|
56
|
+
self.adapter.activate! if self.adapter
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Sends an exception manually using this method, even when you are not in a controller.
|
|
60
|
+
#
|
|
61
|
+
# @param [Exception] exception The exception you want to notify Hoptoad about.
|
|
62
|
+
# @param [Hash] opts Data that will be sent to Hoptoad.
|
|
63
|
+
#
|
|
64
|
+
# @option opts [String] :api_key The API key for this project. The API key is a unique identifier that Hoptoad uses for identification.
|
|
65
|
+
# @option opts [String] :error_message The error returned by the exception (or the message you want to log).
|
|
66
|
+
# @option opts [String] :backtrace A backtrace, usually obtained with +caller+.
|
|
67
|
+
# @option opts [String] :request The controller's request object.
|
|
68
|
+
# @option opts [String] :session The contents of the user's session.
|
|
69
|
+
# @option opts [String] :environment ENV merged with the contents of the request's environment.
|
|
70
|
+
def notify(exception, opts = {})
|
|
71
|
+
send_notice(build_notice_for(exception, opts))
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Sends the notice unless it is one of the default ignored exceptions
|
|
75
|
+
# @see Ribbit.notify
|
|
76
|
+
def notify_or_ignore(exception, opts = {})
|
|
77
|
+
notice = build_notice_for(exception, opts)
|
|
78
|
+
send_notice(notice) unless notice.ignore?
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def send_notice(notice)
|
|
84
|
+
if configuration.public?
|
|
85
|
+
sender.send_to_hoptoad(notice.to_xml)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def build_notice_for(exception, opts = {})
|
|
90
|
+
if exception.respond_to?(:to_hash)
|
|
91
|
+
opts = opts.merge(exception)
|
|
92
|
+
else
|
|
93
|
+
opts = opts.merge(:exception => exception)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
Notice.new(configuration.merge(opts))
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module Ribbit
|
|
2
|
+
module Adapters
|
|
3
|
+
class << self
|
|
4
|
+
|
|
5
|
+
attr_accessor :adapters
|
|
6
|
+
|
|
7
|
+
def adapters
|
|
8
|
+
@adapters ||= []
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Register a new adapter - you do not need to call this directly if subclassing Adapter
|
|
12
|
+
def add_adapter(adapter)
|
|
13
|
+
self.adapters << adapter
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def load_adapter(name)
|
|
17
|
+
require_adapter!(name)
|
|
18
|
+
adapter_class_name = name.to_s.capitalize
|
|
19
|
+
Ribbit::Adapters.const_get(adapter_class_name) rescue nil
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def adapter_path(name)
|
|
25
|
+
name = name.to_s.downcase
|
|
26
|
+
File.join(File.dirname(__FILE__), 'adapters', "#{name}.rb")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def require_adapter!(name)
|
|
30
|
+
begin
|
|
31
|
+
require adapter_path(name)
|
|
32
|
+
rescue LoadError
|
|
33
|
+
false
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Ribbit
|
|
2
|
+
module Adapters
|
|
3
|
+
class Adapter
|
|
4
|
+
|
|
5
|
+
attr_accessor :configuration
|
|
6
|
+
|
|
7
|
+
class << self
|
|
8
|
+
def inherited(klass)
|
|
9
|
+
Ribbit::Adapters.add_adapter klass
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def initialize(config)
|
|
14
|
+
self.configuration = config
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# This should provide something that implements the logger interface
|
|
18
|
+
def logger
|
|
19
|
+
raise NotImplementedError
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# This should perform the steps to integrate hoptoad catching into the framework
|
|
23
|
+
def activate!
|
|
24
|
+
raise NotImplementedError
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module Ribbit
|
|
2
|
+
module Adapters
|
|
3
|
+
class Merb < Adapter
|
|
4
|
+
|
|
5
|
+
def initialize(config)
|
|
6
|
+
super(config)
|
|
7
|
+
config.environment_name = ::Merb.env
|
|
8
|
+
config.project_root = ::Merb.root
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def logger
|
|
12
|
+
::Merb.logger
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def activate!
|
|
16
|
+
::Merb::BootLoader.after_app_loads do
|
|
17
|
+
::Merb::AbstractController.send(:include, ControllerMixin)
|
|
18
|
+
if configuration.automatic?
|
|
19
|
+
::Merb::Dispatcher::DefaultException.send(:include, ControllerMixin)
|
|
20
|
+
::Merb::Dispatcher::DefaultException.send(:include, DefaultExceptionExtensions)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
module ControllerMixin
|
|
26
|
+
protected
|
|
27
|
+
|
|
28
|
+
def notify_hoptoad(ex=nil)
|
|
29
|
+
exceptions = request.exceptions || [ex]
|
|
30
|
+
exceptions.each do |exception|
|
|
31
|
+
Ribbit.notify(exception, {
|
|
32
|
+
:parameters => params,
|
|
33
|
+
:session => session,
|
|
34
|
+
:controller => params[:controller],
|
|
35
|
+
:action => params[:action],
|
|
36
|
+
:url => absolute_url(:this),
|
|
37
|
+
:cgi_data => ENV.to_hash.merge(request.env)
|
|
38
|
+
})
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end # ControllerMixin
|
|
42
|
+
|
|
43
|
+
module DefaultExceptionExtensions
|
|
44
|
+
def self.included(mod)
|
|
45
|
+
mod.class_eval do
|
|
46
|
+
before :notify_hoptoad
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
end # class Merb
|
|
52
|
+
end # module Adapters
|
|
53
|
+
end # module Ribbit
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require 'logger'
|
|
2
|
+
|
|
3
|
+
module Ribbit
|
|
4
|
+
module Adapters
|
|
5
|
+
class None < Adapter
|
|
6
|
+
|
|
7
|
+
# This should provide something that implements the logger interface
|
|
8
|
+
def logger
|
|
9
|
+
@logger ||= Logger.new(STDOUT)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# This should perform the steps to integrate hoptoad catching into the framework
|
|
13
|
+
def activate!
|
|
14
|
+
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
module Ribbit
|
|
2
|
+
# Front end to parsing the backtrace for each notice
|
|
3
|
+
class Backtrace
|
|
4
|
+
|
|
5
|
+
# Handles backtrace parsing line by line
|
|
6
|
+
class Line
|
|
7
|
+
|
|
8
|
+
INPUT_FORMAT = %r{^([^:]+):(\d+)(?::in `([^']+)')?$}.freeze
|
|
9
|
+
|
|
10
|
+
# The file portion of the line (such as app/models/user.rb)
|
|
11
|
+
attr_reader :file
|
|
12
|
+
|
|
13
|
+
# The line number portion of the line
|
|
14
|
+
attr_reader :number
|
|
15
|
+
|
|
16
|
+
# The method of the line (such as index)
|
|
17
|
+
attr_reader :method
|
|
18
|
+
|
|
19
|
+
# Parses a single line of a given backtrace
|
|
20
|
+
# @param [String] unparsed_line The raw line from +caller+ or some backtrace
|
|
21
|
+
# @return [Line] The parsed backtrace line
|
|
22
|
+
def self.parse(unparsed_line)
|
|
23
|
+
_, file, number, method = unparsed_line.match(INPUT_FORMAT).to_a
|
|
24
|
+
new(file, number, method)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def initialize(file, number, method)
|
|
28
|
+
self.file = file
|
|
29
|
+
self.number = number
|
|
30
|
+
self.method = method
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Reconstructs the line in a readable fashion
|
|
34
|
+
def to_s
|
|
35
|
+
"#{file}:#{number}:in `#{method}'"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def ==(other)
|
|
39
|
+
to_s == other.to_s
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def inspect
|
|
43
|
+
"<Line:#{to_s}>"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
attr_writer :file, :number, :method
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# holder for an Array of Backtrace::Line instances
|
|
52
|
+
attr_reader :lines
|
|
53
|
+
|
|
54
|
+
def self.parse(ruby_backtrace, opts = {})
|
|
55
|
+
ruby_lines = split_multiline_backtrace(ruby_backtrace)
|
|
56
|
+
|
|
57
|
+
filters = opts[:filters] || []
|
|
58
|
+
filtered_lines = ruby_lines.to_a.map do |line|
|
|
59
|
+
filters.inject(line) do |line, proc|
|
|
60
|
+
proc.call(line)
|
|
61
|
+
end
|
|
62
|
+
end.compact
|
|
63
|
+
|
|
64
|
+
lines = filtered_lines.collect do |unparsed_line|
|
|
65
|
+
Line.parse(unparsed_line)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
instance = new(lines)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def initialize(lines)
|
|
72
|
+
self.lines = lines
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def inspect
|
|
76
|
+
"<Backtrace: " + lines.collect { |line| line.inspect }.join(", ") + ">"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def ==(other)
|
|
80
|
+
if other.respond_to?(:lines)
|
|
81
|
+
lines == other.lines
|
|
82
|
+
else
|
|
83
|
+
false
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
attr_writer :lines
|
|
90
|
+
|
|
91
|
+
def self.split_multiline_backtrace(backtrace)
|
|
92
|
+
if backtrace.to_a.size == 1
|
|
93
|
+
backtrace.to_a.first.split(/\n\s*/)
|
|
94
|
+
else
|
|
95
|
+
backtrace
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
module Ribbit
|
|
2
|
+
# Used to set up and modify settings for the notifier.
|
|
3
|
+
class Configuration
|
|
4
|
+
|
|
5
|
+
OPTIONS = [:adapter, :api_key, :automatic, :backtrace_filters,
|
|
6
|
+
:development_environments, :development_lookup, :environment_filters,
|
|
7
|
+
:environment_name, :host, :http_open_timeout, :http_read_timeout,
|
|
8
|
+
:ignore, :ignore_by_filters, :ignore_user_agent, :notifier_name,
|
|
9
|
+
:notifier_url, :notifier_version, :params_filters, :project_root,
|
|
10
|
+
:port, :protocol, :proxy_host, :proxy_pass, :proxy_port, :proxy_user,
|
|
11
|
+
:secure].freeze
|
|
12
|
+
|
|
13
|
+
# The API key for your project, found on the project edit form.
|
|
14
|
+
attr_accessor :api_key
|
|
15
|
+
|
|
16
|
+
# The host to connect to (defaults to hoptoadapp.com).
|
|
17
|
+
attr_accessor :host
|
|
18
|
+
|
|
19
|
+
# The port on which your Hoptoad server runs (defaults to 443 for secure
|
|
20
|
+
# connections, 80 for insecure connections).
|
|
21
|
+
attr_accessor :port
|
|
22
|
+
|
|
23
|
+
# +true+ for https connections, +false+ for http connections.
|
|
24
|
+
attr_accessor :secure
|
|
25
|
+
|
|
26
|
+
# The HTTP open timeout in seconds (defaults to 2).
|
|
27
|
+
attr_accessor :http_open_timeout
|
|
28
|
+
|
|
29
|
+
# The HTTP read timeout in seconds (defaults to 5).
|
|
30
|
+
attr_accessor :http_read_timeout
|
|
31
|
+
|
|
32
|
+
# The hostname of your proxy server (if using a proxy)
|
|
33
|
+
attr_accessor :proxy_host
|
|
34
|
+
|
|
35
|
+
# The port of your proxy server (if using a proxy)
|
|
36
|
+
attr_accessor :proxy_port
|
|
37
|
+
|
|
38
|
+
# The username to use when logging into your proxy server (if using a proxy)
|
|
39
|
+
attr_accessor :proxy_user
|
|
40
|
+
|
|
41
|
+
# The password to use when logging into your proxy server (if using a proxy)
|
|
42
|
+
attr_accessor :proxy_pass
|
|
43
|
+
|
|
44
|
+
# A list of parameters that should be filtered out of what is sent to Hoptoad.
|
|
45
|
+
# By default, all "password" attributes will have their contents replaced.
|
|
46
|
+
attr_reader :params_filters
|
|
47
|
+
|
|
48
|
+
# A list of environment keys that should be filtered out of what is send to Hoptoad.
|
|
49
|
+
# Empty by default.
|
|
50
|
+
attr_reader :environment_filters
|
|
51
|
+
|
|
52
|
+
# A list of filters for cleaning and pruning the backtrace. See #filter_backtrace.
|
|
53
|
+
attr_reader :backtrace_filters
|
|
54
|
+
|
|
55
|
+
# A list of filters for ignoring exceptions. See #ignore_by_filter.
|
|
56
|
+
attr_reader :ignore_by_filters
|
|
57
|
+
|
|
58
|
+
# A list of exception classes to ignore. The array can be appended to.
|
|
59
|
+
attr_reader :ignore
|
|
60
|
+
|
|
61
|
+
# A list of user agents that are being ignored. The array can be appended to.
|
|
62
|
+
attr_reader :ignore_user_agent
|
|
63
|
+
|
|
64
|
+
# A list of environments in which notifications should not be sent.
|
|
65
|
+
attr_accessor :development_environments
|
|
66
|
+
|
|
67
|
+
# +true+ if errors should be caught automatically
|
|
68
|
+
attr_accessor :automatic
|
|
69
|
+
alias_method :automatic?, :automatic
|
|
70
|
+
|
|
71
|
+
# +true+ if you want to check for production errors matching development errors, +false+ otherwise.
|
|
72
|
+
attr_accessor :development_lookup
|
|
73
|
+
|
|
74
|
+
# The name of the environment the application is running in
|
|
75
|
+
attr_accessor :environment_name
|
|
76
|
+
|
|
77
|
+
# The adapter to use to plug functionality into the parent application
|
|
78
|
+
attr_accessor :adapter
|
|
79
|
+
|
|
80
|
+
# The path to the project in which the error occurred, such as the RAILS_ROOT
|
|
81
|
+
attr_accessor :project_root
|
|
82
|
+
|
|
83
|
+
# The name of the notifier library being used to send notifications (such as "Hoptoad Notifier")
|
|
84
|
+
attr_accessor :notifier_name
|
|
85
|
+
|
|
86
|
+
# The version of the notifier library being used to send notifications (such as "1.0.2")
|
|
87
|
+
attr_accessor :notifier_version
|
|
88
|
+
|
|
89
|
+
# The url of the notifier library being used to send notifications
|
|
90
|
+
attr_accessor :notifier_url
|
|
91
|
+
|
|
92
|
+
DEFAULT_PARAMS_FILTERS = %w(password password_confirmation).freeze
|
|
93
|
+
|
|
94
|
+
DEFAULT_BACKTRACE_FILTERS = [
|
|
95
|
+
lambda { |line|
|
|
96
|
+
if defined?(Ribbit.configuration.project_root)
|
|
97
|
+
line.gsub(/#{Ribbit.configuration.project_root}/, "[PROJECT_ROOT]")
|
|
98
|
+
else
|
|
99
|
+
line
|
|
100
|
+
end
|
|
101
|
+
},
|
|
102
|
+
lambda { |line| line.gsub(/^\.\//, "") },
|
|
103
|
+
lambda { |line|
|
|
104
|
+
if defined?(Gem)
|
|
105
|
+
Gem.path.inject(line) do |line, path|
|
|
106
|
+
line.gsub(/#{path}/, "[GEM_ROOT]")
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
},
|
|
110
|
+
lambda { |line| line if line !~ %r{lib/ribbit} }
|
|
111
|
+
].freeze
|
|
112
|
+
|
|
113
|
+
IGNORE_DEFAULT = []
|
|
114
|
+
|
|
115
|
+
# Some of these don't exist for whatever the app is, so we have to consider that.
|
|
116
|
+
IGNORE_DEFAULT.map!{|e| eval(e) rescue nil }.compact!
|
|
117
|
+
IGNORE_DEFAULT.freeze
|
|
118
|
+
|
|
119
|
+
alias_method :secure?, :secure
|
|
120
|
+
|
|
121
|
+
def initialize
|
|
122
|
+
@secure = false
|
|
123
|
+
@host = 'hoptoadapp.com'
|
|
124
|
+
@http_open_timeout = 2
|
|
125
|
+
@http_read_timeout = 5
|
|
126
|
+
@params_filters = DEFAULT_PARAMS_FILTERS.dup
|
|
127
|
+
@environment_filters = []
|
|
128
|
+
@backtrace_filters = DEFAULT_BACKTRACE_FILTERS.dup
|
|
129
|
+
@ignore_by_filters = []
|
|
130
|
+
@ignore = IGNORE_DEFAULT.dup
|
|
131
|
+
@ignore_user_agent = []
|
|
132
|
+
@development_environments = %w(development test)
|
|
133
|
+
@automatic = true
|
|
134
|
+
@development_lookup = true
|
|
135
|
+
@notifier_name = CLIENT_NAME
|
|
136
|
+
@notifier_version = VERSION
|
|
137
|
+
@notifier_url = 'http://hoptoadapp.com'
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Takes a block and adds it to the list of backtrace filters. When the filters
|
|
141
|
+
# run, the block will be handed each line of the backtrace and can modify
|
|
142
|
+
# it as necessary.
|
|
143
|
+
#
|
|
144
|
+
# @example
|
|
145
|
+
# config.filter_bracktrace do |line|
|
|
146
|
+
# line.gsub(/^#{Rails.root}/, "[RAILS_ROOT]")
|
|
147
|
+
# end
|
|
148
|
+
#
|
|
149
|
+
# @param [Proc] block The new backtrace filter.
|
|
150
|
+
# @yieldparam [String] line A line in the backtrace.
|
|
151
|
+
def filter_backtrace(&block)
|
|
152
|
+
self.backtrace_filters << block
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Takes a block and adds it to the list of ignore filters.
|
|
156
|
+
# When the filters run, the block will be handed the exception.
|
|
157
|
+
# @example
|
|
158
|
+
# config.ignore_by_filter do |exception_data|
|
|
159
|
+
# true if exception_data[:error_class] == "RuntimeError"
|
|
160
|
+
# end
|
|
161
|
+
#
|
|
162
|
+
# @param [Proc] block The new ignore filter
|
|
163
|
+
# @yieldparam [Hash] data The exception data given to +Ribbit.notify+
|
|
164
|
+
# @yieldreturn [Boolean] If the block returns true the exception will be ignored, otherwise it will be processed by hoptoad.
|
|
165
|
+
def ignore_by_filter(&block)
|
|
166
|
+
self.ignore_by_filters << block
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Overrides the list of default ignored errors.
|
|
170
|
+
#
|
|
171
|
+
# @param [Array<Exception>] names A list of exceptions to ignore.
|
|
172
|
+
def ignore_only=(names)
|
|
173
|
+
@ignore = [names].flatten
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Overrides the list of default ignored user agents
|
|
177
|
+
#
|
|
178
|
+
# @param [Array<String>] A list of user agents to ignore
|
|
179
|
+
def ignore_user_agent_only=(names)
|
|
180
|
+
@ignore_user_agent = [names].flatten
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Allows config options to be read like a hash
|
|
184
|
+
#
|
|
185
|
+
# @param [Symbol] option Key for a given attribute
|
|
186
|
+
def [](option)
|
|
187
|
+
send(option)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Returns a hash of all configurable options
|
|
191
|
+
def to_hash
|
|
192
|
+
OPTIONS.inject({}) do |hash, option|
|
|
193
|
+
hash.merge(option.to_sym => send(option))
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Returns a hash of all configurable options merged with +hash+
|
|
198
|
+
#
|
|
199
|
+
# @param [Hash] hash A set of configuration options that will take precedence over the defaults
|
|
200
|
+
def merge(hash)
|
|
201
|
+
to_hash.merge(hash)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Determines if the notifier will send notices.
|
|
205
|
+
# @return [Boolean] Returns +false+ if in a development environment, +true+ otherwise.
|
|
206
|
+
def public?
|
|
207
|
+
!development_environments.include?(environment_name)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def port
|
|
211
|
+
@port || default_port
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def protocol
|
|
215
|
+
if secure?
|
|
216
|
+
'https'
|
|
217
|
+
else
|
|
218
|
+
'http'
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
private
|
|
223
|
+
|
|
224
|
+
def default_port
|
|
225
|
+
if secure?
|
|
226
|
+
443
|
|
227
|
+
else
|
|
228
|
+
80
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
end
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
require 'builder'
|
|
2
|
+
|
|
3
|
+
module Ribbit
|
|
4
|
+
class Notice
|
|
5
|
+
|
|
6
|
+
# The exception that caused this notice, if any
|
|
7
|
+
attr_reader :exception
|
|
8
|
+
|
|
9
|
+
# The API key for the project to which this notice should be sent
|
|
10
|
+
attr_reader :api_key
|
|
11
|
+
|
|
12
|
+
# The backtrace from the given exception or hash.
|
|
13
|
+
attr_reader :backtrace
|
|
14
|
+
|
|
15
|
+
# The name of the class of error (such as RuntimeError)
|
|
16
|
+
attr_reader :error_class
|
|
17
|
+
|
|
18
|
+
# The name of the server environment (such as "production")
|
|
19
|
+
attr_reader :environment_name
|
|
20
|
+
|
|
21
|
+
# CGI variables such as HTTP_METHOD
|
|
22
|
+
attr_reader :cgi_data
|
|
23
|
+
|
|
24
|
+
# The message from the exception, or a general description of the error
|
|
25
|
+
attr_reader :error_message
|
|
26
|
+
|
|
27
|
+
# See Configuration#backtrace_filters
|
|
28
|
+
attr_reader :backtrace_filters
|
|
29
|
+
|
|
30
|
+
# See Configuration#params_filters
|
|
31
|
+
attr_reader :params_filters
|
|
32
|
+
|
|
33
|
+
# A hash of parameters from the query string or post body.
|
|
34
|
+
attr_reader :parameters
|
|
35
|
+
alias_method :params, :parameters
|
|
36
|
+
|
|
37
|
+
# The component (if any) which was used in this request (usually the controller)
|
|
38
|
+
attr_reader :component
|
|
39
|
+
alias_method :controller, :component
|
|
40
|
+
|
|
41
|
+
# The action (if any) that was called in this request
|
|
42
|
+
attr_reader :action
|
|
43
|
+
|
|
44
|
+
# A hash of session data from the request
|
|
45
|
+
attr_reader :session_data
|
|
46
|
+
|
|
47
|
+
# The path to the project that caused the error (usually RAILS_ROOT)
|
|
48
|
+
attr_reader :project_root
|
|
49
|
+
|
|
50
|
+
# The URL at which the error occurred (if any)
|
|
51
|
+
attr_reader :url
|
|
52
|
+
|
|
53
|
+
# See Configuration#ignore
|
|
54
|
+
attr_reader :ignore
|
|
55
|
+
|
|
56
|
+
# See Configuration#ignore_by_filters
|
|
57
|
+
attr_reader :ignore_by_filters
|
|
58
|
+
|
|
59
|
+
# The name of the notifier library sending this notice, such as "Hoptoad Notifier"
|
|
60
|
+
attr_reader :notifier_name
|
|
61
|
+
|
|
62
|
+
# The version number of the notifier library sending this notice, such as "2.1.3"
|
|
63
|
+
attr_reader :notifier_version
|
|
64
|
+
|
|
65
|
+
# A URL for more information about the notifier library sending this notice
|
|
66
|
+
attr_reader :notifier_url
|
|
67
|
+
|
|
68
|
+
def initialize(args)
|
|
69
|
+
self.args = args
|
|
70
|
+
self.exception = args[:exception]
|
|
71
|
+
self.api_key = args[:api_key]
|
|
72
|
+
self.project_root = args[:project_root]
|
|
73
|
+
self.url = args[:url]
|
|
74
|
+
|
|
75
|
+
self.notifier_name = args[:notifier_name]
|
|
76
|
+
self.notifier_version = args[:notifier_version]
|
|
77
|
+
self.notifier_url = args[:notifier_url]
|
|
78
|
+
|
|
79
|
+
self.ignore = args[:ignore] || []
|
|
80
|
+
self.ignore_by_filters = args[:ignore_by_filters] || []
|
|
81
|
+
self.backtrace_filters = args[:backtrace_filters] || []
|
|
82
|
+
self.params_filters = args[:params_filters] || []
|
|
83
|
+
self.parameters = args[:parameters] || {}
|
|
84
|
+
self.component = args[:component] || args[:controller]
|
|
85
|
+
self.action = args[:action]
|
|
86
|
+
|
|
87
|
+
self.environment_name = args[:environment_name]
|
|
88
|
+
self.cgi_data = args[:cgi_data] || ENV
|
|
89
|
+
self.backtrace = Backtrace.parse(exception_attribute(:backtrace, caller))
|
|
90
|
+
self.error_class = exception_attribute(:error_class) {|exception| exception.class.name }
|
|
91
|
+
self.error_message = exception_attribute(:error_message, 'Notification') do |exception|
|
|
92
|
+
"#{exception.class.name}: #{exception.message}"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
find_session_data
|
|
96
|
+
clean_params
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Converts the given notice to XML
|
|
100
|
+
def to_xml
|
|
101
|
+
builder = Builder::XmlMarkup.new
|
|
102
|
+
builder.instruct!
|
|
103
|
+
xml = builder.notice(:version => Ribbit::API_VERSION) do |notice|
|
|
104
|
+
notice.tag!("api-key", api_key)
|
|
105
|
+
notice.notifier do |notifier|
|
|
106
|
+
notifier.name(notifier_name)
|
|
107
|
+
notifier.version(notifier_version)
|
|
108
|
+
notifier.url(notifier_url)
|
|
109
|
+
end
|
|
110
|
+
notice.error do |error|
|
|
111
|
+
error.tag!('class', error_class)
|
|
112
|
+
error.message(error_message)
|
|
113
|
+
error.backtrace do |backtrace|
|
|
114
|
+
self.backtrace.lines.each do |line|
|
|
115
|
+
backtrace.line(:number => line.number,
|
|
116
|
+
:file => line.file,
|
|
117
|
+
:method => line.method)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
if url ||
|
|
122
|
+
controller ||
|
|
123
|
+
action ||
|
|
124
|
+
!parameters.empty? ||
|
|
125
|
+
!cgi_data.empty? ||
|
|
126
|
+
!session_data.empty?
|
|
127
|
+
notice.request do |request|
|
|
128
|
+
request.url(url)
|
|
129
|
+
request.component(controller)
|
|
130
|
+
request.action(action)
|
|
131
|
+
unless parameters.empty?
|
|
132
|
+
request.params do |params|
|
|
133
|
+
xml_vars_for(params, parameters)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
unless session_data.empty?
|
|
137
|
+
request.session do |session|
|
|
138
|
+
xml_vars_for(session, session_data)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
unless cgi_data.empty?
|
|
142
|
+
request.tag!("cgi-data") do |cgi_datum|
|
|
143
|
+
xml_vars_for(cgi_datum, cgi_data)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
notice.tag!("server-environment") do |env|
|
|
149
|
+
env.tag!("project-root", project_root)
|
|
150
|
+
env.tag!("environment-name", environment_name)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
xml.to_s
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Determines if this notice should be ignored
|
|
157
|
+
def ignore?
|
|
158
|
+
ignored_class_names.include?(error_class) ||
|
|
159
|
+
ignore_by_filters.any? {|filter| filter.call(self) }
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Allows properties to be accessed using a hash-like syntax
|
|
163
|
+
#
|
|
164
|
+
# @example
|
|
165
|
+
# notice[:error_message]
|
|
166
|
+
# @param [String] method The given key for an attribute
|
|
167
|
+
# @return The attribute value, or self if given +:request+
|
|
168
|
+
def [](method)
|
|
169
|
+
case method
|
|
170
|
+
when :request
|
|
171
|
+
self
|
|
172
|
+
else
|
|
173
|
+
send(method)
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
private
|
|
178
|
+
|
|
179
|
+
attr_writer :exception, :api_key, :backtrace, :error_class, :error_message,
|
|
180
|
+
:backtrace_filters, :parameters, :params_filters,
|
|
181
|
+
:environment_filters, :session_data, :project_root, :url, :ignore,
|
|
182
|
+
:ignore_by_filters, :notifier_name, :notifier_url, :notifier_version,
|
|
183
|
+
:component, :action, :cgi_data, :environment_name
|
|
184
|
+
|
|
185
|
+
# Arguments given in the initializer
|
|
186
|
+
attr_accessor :args
|
|
187
|
+
|
|
188
|
+
# Gets a property named +attribute+ of an exception, either from an actual
|
|
189
|
+
# exception or a hash.
|
|
190
|
+
#
|
|
191
|
+
# If an exception is available, #from_exception will be used. Otherwise,
|
|
192
|
+
# a key named +attribute+ will be used from the #args.
|
|
193
|
+
#
|
|
194
|
+
# If no exception or hash key is available, +default+ will be used.
|
|
195
|
+
def exception_attribute(attribute, default = nil, &block)
|
|
196
|
+
(exception && from_exception(attribute, &block)) || args[attribute] || default
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Gets a property named +attribute+ from an exception.
|
|
200
|
+
#
|
|
201
|
+
# If a block is given, it will be used when getting the property from an
|
|
202
|
+
# exception. The block should accept and exception and return the value for
|
|
203
|
+
# the property.
|
|
204
|
+
#
|
|
205
|
+
# If no block is given, a method with the same name as +attribute+ will be
|
|
206
|
+
# invoked for the value.
|
|
207
|
+
def from_exception(attribute)
|
|
208
|
+
if block_given?
|
|
209
|
+
yield(exception)
|
|
210
|
+
else
|
|
211
|
+
exception.send(attribute)
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Removes non-serializable data from the given attribute.
|
|
216
|
+
# See #clean_unserializable_data
|
|
217
|
+
def clean_unserializable_data_from(attribute)
|
|
218
|
+
self.send(:"#{attribute}=", clean_unserializable_data(send(attribute)))
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Removes non-serializable data. Allowed data types are strings, arrays,
|
|
222
|
+
# and hashes. All other types are converted to strings.
|
|
223
|
+
# TODO: move this onto Hash
|
|
224
|
+
def clean_unserializable_data(data)
|
|
225
|
+
if data.respond_to?(:to_hash)
|
|
226
|
+
data.inject({}) do |result, (key, value)|
|
|
227
|
+
result.merge(key => clean_unserializable_data(value))
|
|
228
|
+
end
|
|
229
|
+
elsif data.respond_to?(:to_ary)
|
|
230
|
+
data.collect do |value|
|
|
231
|
+
clean_unserializable_data(value)
|
|
232
|
+
end
|
|
233
|
+
else
|
|
234
|
+
data.to_s
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Replaces the contents of params that match params_filters.
|
|
239
|
+
# TODO: extract this to a different class
|
|
240
|
+
def clean_params(from=nil)
|
|
241
|
+
clean_unserializable_data_from(:parameters)
|
|
242
|
+
from ||= parameters
|
|
243
|
+
if params_filters
|
|
244
|
+
from.keys.each do |key|
|
|
245
|
+
from[key] = "[FILTERED]" if params_filters.any? do |filter|
|
|
246
|
+
key.to_s.include?(filter)
|
|
247
|
+
end
|
|
248
|
+
# Recurse inside nested param hashes
|
|
249
|
+
clean_params(from[key]) if from[key].respond_to?(:keys)
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def find_session_data
|
|
255
|
+
self.session_data = args[:session_data] || args[:session] || {}
|
|
256
|
+
self.session_data = session_data[:data] if session_data[:data]
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# Converts the mixed class instances and class names into just names
|
|
260
|
+
# TODO: move this into Configuration or another class
|
|
261
|
+
def ignored_class_names
|
|
262
|
+
ignore.collect do |string_or_class|
|
|
263
|
+
if string_or_class.respond_to?(:name)
|
|
264
|
+
string_or_class.name
|
|
265
|
+
else
|
|
266
|
+
string_or_class
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def xml_vars_for(builder, hash)
|
|
272
|
+
hash.each do |key, value|
|
|
273
|
+
if value.respond_to?(:to_hash)
|
|
274
|
+
builder.var(:key => key){|b| xml_vars_for(b, value.to_hash) }
|
|
275
|
+
elsif value.respond_to?(:to_ary)
|
|
276
|
+
builder.var("[" << (value.map { |v| v.to_s } * ", ") << "]", :key => key)
|
|
277
|
+
else
|
|
278
|
+
builder.var(value.to_s, :key => key)
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
require 'net/http'
|
|
2
|
+
require 'net/https'
|
|
3
|
+
|
|
4
|
+
module Ribbit
|
|
5
|
+
# Sends out the notice to Hoptoad
|
|
6
|
+
class Sender
|
|
7
|
+
|
|
8
|
+
NOTICES_URI = '/notifier_api/v2/notices/'.freeze
|
|
9
|
+
HEADERS = {
|
|
10
|
+
'Content-type' => 'text/xml',
|
|
11
|
+
'Accept' => 'text/xml, application/xml'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
def initialize(options = {})
|
|
15
|
+
[:proxy_host, :proxy_port, :proxy_user, :proxy_pass, :protocol,
|
|
16
|
+
:host, :port, :secure, :http_open_timeout, :http_read_timeout].each do |option|
|
|
17
|
+
instance_variable_set("@#{option}", options[option])
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Sends the notice data off to Hoptoad for processing.
|
|
22
|
+
#
|
|
23
|
+
# @param [String] data The XML notice to be sent off
|
|
24
|
+
def send_to_hoptoad(data)
|
|
25
|
+
log :debug, "Sending request to #{url.to_s}:\n#{data}"
|
|
26
|
+
|
|
27
|
+
http =
|
|
28
|
+
Net::HTTP::Proxy(proxy_host, proxy_port, proxy_user, proxy_pass).
|
|
29
|
+
new(url.host, url.port)
|
|
30
|
+
|
|
31
|
+
http.read_timeout = http_read_timeout
|
|
32
|
+
http.open_timeout = http_open_timeout
|
|
33
|
+
http.use_ssl = secure
|
|
34
|
+
|
|
35
|
+
response = begin
|
|
36
|
+
http.post(url.path, data, HEADERS)
|
|
37
|
+
rescue TimeoutError => e
|
|
38
|
+
log :error, "Timeout while contacting the Hoptoad server."
|
|
39
|
+
nil
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
case response
|
|
43
|
+
when Net::HTTPSuccess then
|
|
44
|
+
log :info, "Success: #{response.class}"
|
|
45
|
+
log :debug, "Response Body: #{response.body}"
|
|
46
|
+
else
|
|
47
|
+
log :error, "Failure: #{response.class}"
|
|
48
|
+
log :debug, "Response Body: #{response.body}"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
attr_reader :proxy_host, :proxy_port, :proxy_user, :proxy_pass, :protocol,
|
|
55
|
+
:host, :port, :secure, :http_open_timeout, :http_read_timeout
|
|
56
|
+
|
|
57
|
+
def url
|
|
58
|
+
URI.parse("#{protocol}://#{host}:#{port}").merge(NOTICES_URI)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def log(level, message)
|
|
62
|
+
logger.send level, LOG_PREFIX + message if logger
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def logger
|
|
66
|
+
Ribbit.logger
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
end
|
|
70
|
+
end
|
data/spec/ribbit_spec.rb
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
|
2
|
+
|
|
3
|
+
describe Ribbit do
|
|
4
|
+
|
|
5
|
+
it "should have a name for the client field in the API" do
|
|
6
|
+
Ribbit.client_name.should_not be_nil
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
it "should have a version number for the client" do
|
|
10
|
+
Ribbit.client_version.should_not be_nil
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
end
|
data/spec/spec.opts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: hoptoad-ribbit
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Glen Mailer
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
|
|
12
|
+
date: 2010-01-05 00:00:00 +00:00
|
|
13
|
+
default_executable:
|
|
14
|
+
dependencies:
|
|
15
|
+
- !ruby/object:Gem::Dependency
|
|
16
|
+
name: builder
|
|
17
|
+
type: :runtime
|
|
18
|
+
version_requirement:
|
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
20
|
+
requirements:
|
|
21
|
+
- - ">="
|
|
22
|
+
- !ruby/object:Gem::Version
|
|
23
|
+
version: 2.0.0
|
|
24
|
+
version:
|
|
25
|
+
- !ruby/object:Gem::Dependency
|
|
26
|
+
name: rspec
|
|
27
|
+
type: :development
|
|
28
|
+
version_requirement:
|
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: 1.2.9
|
|
34
|
+
version:
|
|
35
|
+
description: "\n Provides a Module to handle sending notifications to hoptoad.\n\n Currently includes a Merb adapter, but the intention is to add more later.\n "
|
|
36
|
+
email: glenjamin@gmail.com
|
|
37
|
+
executables: []
|
|
38
|
+
|
|
39
|
+
extensions: []
|
|
40
|
+
|
|
41
|
+
extra_rdoc_files:
|
|
42
|
+
- LICENSE
|
|
43
|
+
- README.rdoc
|
|
44
|
+
files:
|
|
45
|
+
- .document
|
|
46
|
+
- .gitignore
|
|
47
|
+
- LICENSE
|
|
48
|
+
- README.rdoc
|
|
49
|
+
- Rakefile
|
|
50
|
+
- VERSION
|
|
51
|
+
- hoptoad-ribbit.gemspec
|
|
52
|
+
- lib/ribbit.rb
|
|
53
|
+
- lib/ribbit/adapters.rb
|
|
54
|
+
- lib/ribbit/adapters/adapter.rb
|
|
55
|
+
- lib/ribbit/adapters/merb.rb
|
|
56
|
+
- lib/ribbit/adapters/none.rb
|
|
57
|
+
- lib/ribbit/backtrace.rb
|
|
58
|
+
- lib/ribbit/configuration.rb
|
|
59
|
+
- lib/ribbit/notice.rb
|
|
60
|
+
- lib/ribbit/sender.rb
|
|
61
|
+
- spec/ribbit_spec.rb
|
|
62
|
+
- spec/spec.opts
|
|
63
|
+
- spec/spec_helper.rb
|
|
64
|
+
has_rdoc: true
|
|
65
|
+
homepage: http://github.com/glenjamin/hoptoad-notifier
|
|
66
|
+
licenses: []
|
|
67
|
+
|
|
68
|
+
post_install_message:
|
|
69
|
+
rdoc_options:
|
|
70
|
+
- --charset=UTF-8
|
|
71
|
+
require_paths:
|
|
72
|
+
- lib
|
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
74
|
+
requirements:
|
|
75
|
+
- - ">="
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: "0"
|
|
78
|
+
version:
|
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
80
|
+
requirements:
|
|
81
|
+
- - ">="
|
|
82
|
+
- !ruby/object:Gem::Version
|
|
83
|
+
version: "0"
|
|
84
|
+
version:
|
|
85
|
+
requirements: []
|
|
86
|
+
|
|
87
|
+
rubyforge_project:
|
|
88
|
+
rubygems_version: 1.3.5
|
|
89
|
+
signing_key:
|
|
90
|
+
specification_version: 3
|
|
91
|
+
summary: Generic Hoptoad Notifications and an adapter for merb
|
|
92
|
+
test_files:
|
|
93
|
+
- spec/ribbit_spec.rb
|
|
94
|
+
- spec/spec_helper.rb
|