hoptoad-ribbit 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,23 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## EDITOR
5
+ *.kpf
6
+ *.tmproj
7
+ tmtags
8
+
9
+ ## EMACS
10
+ *~
11
+ \#*
12
+ .\#*
13
+
14
+ ## VIM
15
+ *.swp
16
+
17
+ ## PROJECT::GENERAL
18
+ coverage
19
+ rdoc
20
+ pkg
21
+
22
+ ## PROJECT::SPECIFIC
23
+ *.ignore
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.
@@ -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.
@@ -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
+
@@ -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
@@ -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
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,9 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'ribbit'
4
+ require 'spec'
5
+ require 'spec/autorun'
6
+
7
+ Spec::Runner.configure do |config|
8
+
9
+ end
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