davidjrice-exceptional 0.0.1

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/History.txt ADDED
File without changes
data/Manifest ADDED
@@ -0,0 +1,20 @@
1
+ exceptional.gemspec
2
+ exceptional.yml
3
+ History.txt
4
+ init.rb
5
+ install.rb
6
+ lib/exceptional/agent/worker.rb
7
+ lib/exceptional/deployed_environment.rb
8
+ lib/exceptional/exception_data.rb
9
+ lib/exceptional/integration/rails.rb
10
+ lib/exceptional/rails.rb
11
+ lib/exceptional/version.rb
12
+ lib/exceptional.rb
13
+ Manifest
14
+ Rakefile
15
+ README
16
+ spec/deployed_environment_spec.rb
17
+ spec/exception_data_spec.rb
18
+ spec/exceptional_spec.rb
19
+ spec/spec_helper.rb
20
+ spec/worker_spec.rb
data/README ADDED
@@ -0,0 +1,13 @@
1
+ = Exceptional plugin for Ruby on Rails
2
+
3
+ This plugin posts exception data to Exceptional (http://getexceptional.com). Data about the request, session, environment and a backtrace of the exception is transmitted.
4
+
5
+ = Exceptional
6
+
7
+ Exceptional (http://getexceptional.com) is a web-based exception tracking and managing system for Ruby on Rails folks and their apps. It's currently in early, private beta.
8
+
9
+ = Installation
10
+
11
+ For installation instructions, follow the steps displayed after adding a new app to your account. Please send any questions or comments to hello@getexceptional.com.
12
+
13
+ Copyright © 2008 Contrast.
data/Rakefile ADDED
@@ -0,0 +1,40 @@
1
+ begin
2
+ require 'echoe'
3
+
4
+ Echoe.new('exceptional', '0.0.1') do |p|
5
+ p.rubyforge_name = 'exceptional'
6
+ p.summary = "Exceptional is the core Ruby library for communicating with http://getexceptional.com (hosted error tracking service)"
7
+ p.description = "Exceptional is the core Ruby library for communicating with http://getexceptional.com (hosted error tracking service)"
8
+ p.url = "http://getexceptional.com/"
9
+ p.author = ['David Rice']
10
+ p.email = "david@contrast.ie"
11
+ p.dependencies = ["json"]
12
+ end
13
+
14
+ rescue LoadError => e
15
+ puts "You are missing a dependency required for meta-operations on this gem."
16
+ puts "#{e.to_s.capitalize}."
17
+ end
18
+
19
+ # add spec tasks, if you have rspec installed
20
+ begin
21
+ require 'spec/rake/spectask'
22
+
23
+ Spec::Rake::SpecTask.new("spec") do |t|
24
+ t.spec_files = FileList['spec/**/*_spec.rb']
25
+ t.spec_opts = ['--color']
26
+ end
27
+
28
+ task :test do
29
+ Rake::Task['spec'].invoke
30
+ end
31
+
32
+ Spec::Rake::SpecTask.new("coverage") do |t|
33
+ t.spec_files = FileList['spec/**/*_spec.rb']
34
+ t.spec_opts = ['--color']
35
+ t.rcov = true
36
+ t.rcov_opts = ['--exclude', '^spec,/gems/']
37
+ end
38
+
39
+
40
+ end
@@ -0,0 +1,34 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "exceptional"
3
+ s.version = "0.0.1"
4
+ s.date = "2008-10-13"
5
+ s.summary = "Exceptional is the core Ruby library for communicating with http://getexceptional.com (hosted error tracking service)"
6
+ s.email = "david@getexceptional.com"
7
+ s.homepage = "http://github.com/contrast/exceptional"
8
+ s.description = "Exceptional is the core Ruby library for communicating with http://getexceptional.com (hosted error tracking service)"
9
+ s.has_rdoc = true
10
+ s.authors = ["David Rice", "Paul Campbell"]
11
+ s.files = ["History.txt",
12
+ "Manifest",
13
+ "README",
14
+ "Rakefile",
15
+ "exceptional.gemspec",
16
+ "exceptional.yml",
17
+ "init.rb",
18
+ "install.rb",
19
+ "lib/exceptional/agent/worker.rb",
20
+ "lib/exceptional/deployed_environment.rb",
21
+ "lib/exceptional/exception_data.rb",
22
+ "lib/exceptional/integration/rails.rb",
23
+ "lib/exceptional/rails.rb",
24
+ "lib/exceptional/version.rb",
25
+ "lib/exceptional.rb"]
26
+ s.test_files = ["spec/deployed_environment_spec.rb",
27
+ "spec/exception_data_spec.rb",
28
+ "spec/exceptional_spec.rb",
29
+ "spec/spec_helper.rb",
30
+ "spec/worker_spec.rbrb"]
31
+ s.rdoc_options = ["--main", "README"]
32
+ s.extra_rdoc_files = ["History.txt", "Manifest", "README"]
33
+ s.add_dependency("json", ["> 0.0.0"])
34
+ end
data/exceptional.yml ADDED
@@ -0,0 +1,34 @@
1
+ # here are the settings that are common to all environments
2
+ common: &default_settings
3
+ # You must specify your Exceptional API key here.
4
+ api-key: PASTE_YOUR_API_KEY_HERE
5
+ # Exceptional creates a separate log file from your application's logs
6
+ # available levels are debug, info, warn, error, fatal
7
+ log-level: info
8
+ # The exceptional agent sends data via regular http by default
9
+ # Setting this value to true will send data over SSL, increasing security
10
+ # There will be an additional CPU overhead in encrypting the data, however
11
+ # as long as your deployment environment is not Passenger (mod_rails), this
12
+ # happens in the background so as not to incur a page wait for your users.
13
+ ssl: false
14
+
15
+ development:
16
+ <<: *default_settings
17
+ # Normally no reason to collect exceptions in development
18
+ # NOTE: for trial purposes you may want to enable exceptional in development
19
+ enabled: false
20
+
21
+ test:
22
+ <<: *default_settings
23
+ # No reason to collect exceptions when running tests by default
24
+ enabled: false
25
+
26
+ production:
27
+ <<: *default_settings
28
+ enabled: true
29
+
30
+ staging:
31
+ # It's common development practice to have a staging environment that closely
32
+ # mirrors production, by default catch errors in this environment too.
33
+ <<: *default_settings
34
+ enabled: true
data/init.rb ADDED
@@ -0,0 +1,20 @@
1
+ require 'exceptional'
2
+
3
+ def to_stderr(s)
4
+ STDERR.puts "** [Exceptional] " + s
5
+ end
6
+
7
+ config_file = File.join(RAILS_ROOT,"/config/exceptional.yml")
8
+
9
+ begin
10
+ Exceptional.application_root = RAILS_ROOT
11
+ Exceptional.environment = RAILS_ENV
12
+
13
+ Exceptional.load_config(config_file)
14
+ if Exceptional.enabled?
15
+ Exceptional::Rails.init
16
+ end
17
+ rescue Exception => e
18
+ to_stderr e
19
+ to_stderr "Plugin disabled."
20
+ end
data/install.rb ADDED
@@ -0,0 +1,18 @@
1
+ # This is the post install hook for when Exceptional is installed as a plugin.
2
+ require 'ftools'
3
+
4
+ # puts IO.read(File.join(File.dirname(__FILE__), 'README'))
5
+
6
+ config_file = File.expand_path("#{File.dirname(__FILE__)}/../../../config/exceptional.yml")
7
+ example_config_file = "#{File.dirname(__FILE__)}/exceptional.yml"
8
+
9
+ if File::exists? config_file
10
+ puts "Exceptional config file already exists. Please ensure it is up-to-date with the current format."
11
+ puts "See #{example_config_file}"
12
+ else
13
+ puts "Installing default Exceptional config"
14
+ puts " From #{example_config_file}"
15
+ puts "For exceptional to work you need to configure your API Key"
16
+ puts " See #{example_config_file}"
17
+ puts "If you don't have an API Key, get one at http://getexceptional.com/"
18
+ end
@@ -0,0 +1,56 @@
1
+ # The Worker is used by Exceptional when started in :queue mode
2
+ # It queues and sends exception data asynchronously.
3
+
4
+ module Exceptional::Agent
5
+
6
+ class Worker
7
+
8
+ attr_reader :log, :timeout, :exceptions
9
+
10
+ def initialize(log = Logger.new(STDERR))
11
+ @exceptions = []
12
+ @timeout = ::WORKER_TIMEOUT
13
+ @mutex = Mutex.new
14
+ @log = log
15
+ @log.info "Started Exceptional Worker."
16
+ end
17
+
18
+ def add_exception(data)
19
+ @mutex.synchronize do
20
+ @exceptions << data
21
+ end
22
+ end
23
+
24
+ def run
25
+ while(true) do
26
+ send_any_exception_data
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def exception_to_send
33
+ @mutex.synchronize do
34
+ return @exceptions.shift
35
+ end
36
+ end
37
+
38
+
39
+ def send_any_exception_data
40
+ if @exceptions.empty?
41
+ sleep @timeout
42
+ return
43
+ end
44
+
45
+ data = exception_to_send
46
+
47
+ begin
48
+ Exceptional.post(data)
49
+ rescue Exception => e
50
+ @log.error "Error sending exception data: #{e}"
51
+ @log.debug e.backtrace.join("\n")
52
+ end
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,84 @@
1
+ # This class is used to determine the server environment
2
+ module Exceptional
3
+ class DeployedEnvironment
4
+
5
+ attr_reader :server, :identifier, :hostname#, :environment
6
+
7
+ def initialize
8
+ @server = :unknown
9
+ @identifier = nil
10
+
11
+ @hostname = determine_host
12
+ # @environment = Exceptional.environment
13
+ # @application_root = Exceptional.application_root
14
+
15
+ servers = %w[webrick mongrel thin litespeed passenger]
16
+ while servers.any? && @identifier.nil?
17
+ send 'is_'+(servers.shift)+'?'
18
+ end
19
+
20
+ end
21
+
22
+ def should_start_worker?
23
+ !identifier.nil?
24
+ end
25
+
26
+ def determine_mode
27
+ @server == :passenger ? :direct : :queue
28
+ end
29
+
30
+ def to_s
31
+ "#{@hostname}:#{@identifier} [#{@server}]"
32
+ end
33
+
34
+ def determine_host
35
+ Socket.gethostname
36
+ end
37
+
38
+ def is_webrick?
39
+ if defined?(OPTIONS) && defined?(DEFAULT_PORT) && OPTIONS.respond_to?(:fetch)
40
+ # OPTIONS is set by script/server if launching webrick
41
+ @identifier = OPTIONS.fetch :port, DEFAULT_PORT
42
+ @server = :webrick
43
+ end
44
+ end
45
+
46
+ def is_mongrel?
47
+ if defined? Mongrel::HttpServer
48
+ ObjectSpace.each_object(Mongrel::HttpServer) do |mongrel|
49
+ next unless mongrel.respond_to? :port
50
+ @server = :mongrel
51
+ @identifier = mongrel.port
52
+ end
53
+ end
54
+ end
55
+
56
+ def is_thin?
57
+ if defined? Thin::Server
58
+ ObjectSpace.each_object(Thin::Server) do |thin_server|
59
+ @server = :thin
60
+ backend = thin_server.backend
61
+ if backend.respond_to? :port
62
+ @identifier = backend.port
63
+ elsif backend.respond_to? :socket
64
+ @identifier = backend.socket
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ def is_litespeed?
71
+ if caller.pop =~ /fcgi-bin\/RailsRunner\.rb/
72
+ @server = :litespeed
73
+ @identifier = 'litespeed'
74
+ end
75
+ end
76
+
77
+ def is_passenger?
78
+ if defined? Passenger::AbstractServer
79
+ @server = :passenger
80
+ @identifier = 'passenger'
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,46 @@
1
+ # This class encapsulates data about an exception.
2
+ module Exceptional
3
+ class DataError < StandardError; end
4
+
5
+ class ExceptionData
6
+ ::LANGUAGE = "ruby"
7
+ ::BASE_ATTRS = [:exception_class, :exception_message, :exception_backtrace]
8
+ ::OPTIONAL_ATTRS = [:framework, :controller_name, :action_name, :application_root,
9
+ :url, :occurred_at, :environment, :session, :parameters]
10
+ ::ACCESSIBLE_ATTRS = ::BASE_ATTRS + ::OPTIONAL_ATTRS
11
+ ::ATTRS = [:language] + ::ACCESSIBLE_ATTRS
12
+
13
+ ::ACCESSIBLE_ATTRS.each do |attribute|
14
+ attr_accessor attribute
15
+ end
16
+ attr_reader :language
17
+
18
+ def initialize
19
+ environment = {}
20
+ session = {}
21
+ parameters = {}
22
+ @language = ::LANGUAGE
23
+ end
24
+
25
+ def valid?
26
+ ::BASE_ATTRS.each do |method|
27
+ raise(DataError, "base data #{method} not set") if (self.send(method).nil? || self.send(method).empty?)
28
+ end
29
+ end
30
+
31
+ def to_hash
32
+ hash = {}
33
+ ::ATTRS.each do |attribute|
34
+ value = send(attribute)
35
+ hash[attribute] = value unless (value.nil? || value.empty?)
36
+ end
37
+ hash
38
+ end
39
+
40
+ def to_json
41
+ self.to_hash.to_json
42
+ end
43
+
44
+ end
45
+
46
+ end
@@ -0,0 +1,20 @@
1
+ if defined? ActionController
2
+
3
+ module ActionController
4
+ class Base
5
+ def rescue_action_with_exceptional(exception)
6
+
7
+ params_to_send = (respond_to? :filter_parameters) ? filter_parameters(params) : params
8
+
9
+ Exceptional.handle(exception, self, request, params_to_send)
10
+
11
+ rescue_action_without_exceptional exception
12
+ end
13
+
14
+ alias_method :rescue_action_without_exceptional, :rescue_action
15
+ alias_method :rescue_action, :rescue_action_with_exceptional
16
+ protected :rescue_action
17
+ end
18
+ end
19
+
20
+ end
@@ -0,0 +1,53 @@
1
+ module Exceptional
2
+
3
+ class Rails
4
+
5
+ def self.init
6
+ Exceptional.deployed_environment = DeployedEnvironment.new
7
+ # If no server environment detected, don't perform magic.
8
+ # Means rake, script/console etc perform as expected
9
+ return false unless Exceptional.deployed_environment.should_start_worker?
10
+
11
+ setup_log
12
+ Exceptional.log_config_info
13
+
14
+ if Exceptional.authenticate
15
+
16
+ if Exceptional.mode == :queue
17
+ Exceptional.worker = Agent::Worker.new(Exceptional.log)
18
+ Exceptional.worker_thread = Thread.new do
19
+ Exceptional.worker.run
20
+ end
21
+ end
22
+
23
+ require File.join(File.dirname(__FILE__), 'integration', 'rails')
24
+
25
+
26
+ at_exit do
27
+ if Exceptional.mode == :queue
28
+ Exceptional.worker_thread.terminate if Exceptional.worker_thread
29
+ end
30
+ end
31
+ else
32
+ Exceptional.log! "Plugin not authenticated, check your API Key"
33
+ Exceptional.log! "Disabling Plugin."
34
+ end
35
+ end
36
+
37
+ def self.setup_log
38
+ log_file = "#{Exceptional.application_root}/log/exceptional.log"
39
+
40
+ @log = Logger.new log_file
41
+ @log.level = Logger::INFO
42
+
43
+ allowed_log_levels = ['debug', 'info', 'warn', 'error', 'fatal']
44
+ if Exceptional.log_level && allowed_log_levels.include?(Exceptional.log_level)
45
+ @log.level = eval("Logger::#{Exceptional.log_level.upcase}")
46
+ end
47
+
48
+ Exceptional.log = @log
49
+ end
50
+
51
+ end
52
+
53
+ end
@@ -0,0 +1,9 @@
1
+ module Exceptional #:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 0
5
+ TINY = 1
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
@@ -0,0 +1,227 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+ require 'exceptional/rails'
3
+ require 'exceptional/deployed_environment'
4
+ require 'exceptional/agent/worker'
5
+ require 'exceptional/exception_data'
6
+ require 'exceptional/version'
7
+ require 'rubygems'
8
+
9
+ require 'zlib'
10
+ require 'cgi'
11
+ require 'net/http'
12
+ require 'logger'
13
+ require 'yaml'
14
+ require 'json' unless defined? Rails
15
+
16
+ module Exceptional
17
+ class LicenseException < StandardError; end
18
+ class ConfigurationException < StandardError; end
19
+
20
+ ::PROTOCOL_VERSION = 3
21
+ # Defaults for configuration variables
22
+ ::REMOTE_HOST = "getexceptional.com"
23
+ ::REMOTE_PORT = 80
24
+ ::REMOTE_SSL_PORT = 443
25
+ ::SSL = false
26
+ ::LOG_LEVEL = 'info'
27
+ ::LOG_PATH = nil
28
+ ::WORKER_TIMEOUT = 10 # seconds
29
+ ::MODE = :direct
30
+
31
+ class << self
32
+ attr_accessor :api_key, :log, :deployed_environment, :log_path, :worker,
33
+ :worker_thread, :environment, :application_root
34
+ attr_writer :remote_host, :remote_port, :ssl_enabled, :log_level
35
+
36
+ # rescue any exceptions within the given block,
37
+ # send it to exceptional,
38
+ # then raise
39
+ def rescue(&block)
40
+ begin
41
+ block.call
42
+ rescue Exception => e
43
+ self.catch(e)
44
+ raise(e)
45
+ end
46
+ end
47
+
48
+ # parse an exception into an ExceptionData object
49
+ def parse(exception)
50
+ exception_data = ExceptionData.new
51
+ exception_data.exception_backtrace = exception.backtrace
52
+ exception_data.exception_message = exception.message
53
+ exception_data.exception_class = exception.class.to_s
54
+ exception_data
55
+ end
56
+
57
+ # authenticate with getexceptional.com
58
+ # returns true if the configured api_key is registered and can send data
59
+ # otherwise false
60
+ def authenticate
61
+ begin
62
+ # TODO No data required to authenticate, send a nil string? hacky
63
+ # TODO should retry if a http connection failed
64
+ return @authenticated if @authenticated
65
+ authenticated = call_remote(:authenticate, "")
66
+ @authenticated = authenticated =~ /true/
67
+ rescue
68
+ @authenticated = false
69
+ ensure
70
+ return @authenticated
71
+ end
72
+ end
73
+
74
+ # post the given exception data to getexceptional.com
75
+ def post(exception_data)
76
+ call_remote(:errors, exception_data.to_json)
77
+ end
78
+
79
+ # given a regular ruby Exception class, will parse into an ExceptionData
80
+ # object and post to getexceptional.com
81
+ def catch(exception)
82
+ exception_data = parse(exception)
83
+ post(exception_data)
84
+ end
85
+
86
+ # used with Rails, takes an exception, controller, request and parameters
87
+ # creates an ExceptionData object
88
+ # if Exceptional is running in :direct mode, will post to getexceptional.com
89
+ # if Exceptional is running in :queue mode, the data will be queued and posted later
90
+ def handle(exception, controller, request, params)
91
+ log! "Handling #{exception.message}", 'info'
92
+ e = parse(exception)
93
+ # Additional data for Rails Exceptions
94
+ e.framework = "rails"
95
+ e.controller_name = controller.controller_name
96
+ e.action_name = controller.action_name
97
+ e.application_root = self.application_root
98
+ e.occurred_at = Time.now.to_s
99
+ e.url = "#{request.protocol}#{request.host}#{request.request_uri}"
100
+ # Need to remove rack data from environment hash
101
+ safe_environment = request.env.to_hash
102
+ safe_environment.delete_if { |k,v| k =~ /rack/ }
103
+ e.environment = safe_environment
104
+
105
+ safe_session = {}
106
+ request.session.instance_variables.each do |v|
107
+ next if v =~ /cgi/
108
+ next if v =~ /db/
109
+ # remove prepended @'s
110
+ var = v.sub("@","")
111
+ safe_session[var] = request.session.instance_variable_get(v)
112
+ end
113
+
114
+ e.session = safe_session
115
+ e.parameters = params.to_hash
116
+
117
+ if mode == :queue
118
+ worker.add_exception(e)
119
+ else # :direct mode
120
+ begin
121
+ post e
122
+ rescue
123
+ log! "Error posting data to Exceptional."
124
+ log! e.message
125
+ log! e.backtace.join("\n"), 'debug'
126
+ end
127
+ end
128
+ end
129
+
130
+ # TODO these configuration methods & defaults should have their own class
131
+ def remote_host
132
+ @remote_host || ::REMOTE_HOST
133
+ end
134
+
135
+ def remote_port
136
+ @remote_port || default_port
137
+ end
138
+
139
+ def log_level
140
+ @log_level || ::LOG_LEVEL
141
+ end
142
+
143
+ def mode
144
+ deployed_environment ? deployed_environment.determine_mode : ::MODE
145
+ end
146
+
147
+ def default_port
148
+ ssl_enabled? ? ::REMOTE_SSL_PORT : ::REMOTE_PORT
149
+ end
150
+
151
+ def ssl_enabled?
152
+ @ssl_enabled || ::SSL
153
+ end
154
+
155
+ def enabled?
156
+ @enabled
157
+ end
158
+
159
+ def log!(msg, level = 'info')
160
+ to_stderr msg
161
+ log.send level, msg if log
162
+ end
163
+
164
+ def to_stderr(msg)
165
+ if deployed_environment && deployed_environment.server != :unknown
166
+ STDERR.puts "** [Exceptional] " + msg
167
+ end
168
+ end
169
+
170
+ def log_config_info
171
+ log! "API Key: #{api_key}", 'debug'
172
+ log! "Deployed Environment: #{deployed_environment.to_s}", 'debug'
173
+ log! "Remote Host: #{remote_host}:#{remote_port}", 'debug'
174
+ log! "Mode: #{mode}", 'debug'
175
+ log! "Log level: #{log_level}", 'debug'
176
+ log! "Log path: #{log_path}", 'debug'
177
+ end
178
+
179
+ def load_config(file)
180
+ begin
181
+ config = YAML::load(File.open(file))[self.environment]
182
+ @api_key = config['api-key'] unless config['api-key'].nil?
183
+ @ssl_enabled = config['ssl'] unless config['ssl'].nil?
184
+ @log_level = config['log-level'] unless config['log-level'].nil?
185
+ @enabled = config['enabled'] unless config['enabled'].nil?
186
+ @remote_port = config['remote-port'].to_i unless config['remote-port'].nil?
187
+ @remote_host = config['remote-host'] unless config['remote-host'].nil?
188
+ rescue Exception => e
189
+ raise ConfigurationException.new("Unable to load configuration file:#{file} for environment:#{environment}")
190
+ end
191
+ end
192
+
193
+ protected
194
+
195
+ def valid_api_key?
196
+ @api_key && @api_key.length == 40
197
+ end
198
+
199
+
200
+ def call_remote(method, data)
201
+ if @api_key.nil?
202
+ raise LicenseException.new("API Key must be configured")
203
+ end
204
+
205
+ http = Net::HTTP.new(remote_host, remote_port)
206
+ uri = "/#{method.to_s}?&api_key=#{@api_key}&protocol_version=#{::PROTOCOL_VERSION}"
207
+ headers = { 'Content-Type' => 'application/x-gzip', 'Accept' => 'application/x-gzip' }
208
+ compressed_data = CGI::escape(Zlib::Deflate.deflate(data, Zlib::BEST_SPEED))
209
+ response = http.start do |http|
210
+ http.post(uri, compressed_data, headers)
211
+ end
212
+
213
+ if response.kind_of? Net::HTTPSuccess
214
+ return response.body
215
+ else
216
+ raise Exception.new("#{response.code}: #{response.message}")
217
+ end
218
+
219
+ rescue Exception => e
220
+ log! "Error contacting Exceptional: #{e}", 'info'
221
+ log! e.backtrace.join("\n"), 'debug'
222
+ raise e
223
+ end
224
+
225
+ end
226
+
227
+ end
@@ -0,0 +1,168 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe Exceptional::DeployedEnvironment do
4
+
5
+ describe "mongrel" do
6
+
7
+ before(:all) do
8
+ class << self
9
+ module ::Mongrel
10
+ class HttpServer
11
+ def port; 3000; end
12
+ end
13
+ end
14
+ end
15
+
16
+ Mongrel::HttpServer.new
17
+ @deployed_environment = Exceptional::DeployedEnvironment.new
18
+ end
19
+
20
+ it "server should be mongrel" do
21
+ @deployed_environment.server.should == :mongrel
22
+ end
23
+
24
+ it "identifier should be 3000" do
25
+ @deployed_environment.identifier.should == 3000
26
+ end
27
+
28
+ it "to_s should match" do
29
+ pending
30
+ @deployed_environment.to_s.should match(/(:3000 \[:mongrel\])/)
31
+ end
32
+
33
+ it "mode should be queue" do
34
+ @deployed_environment.determine_mode.should == :queue
35
+ end
36
+
37
+ after(:all) do
38
+ ObjectSpace.garbage_collect
39
+ end
40
+
41
+ end
42
+
43
+ describe "webrick" do
44
+
45
+ before(:all) do
46
+ class MockOptions
47
+ def fetch(*args)
48
+ 3001
49
+ end
50
+ end
51
+
52
+ class << self
53
+ ::OPTIONS = MockOptions.new
54
+ ::DEFAULT_PORT = 3000
55
+ end
56
+ @deployed_environment = Exceptional::DeployedEnvironment.new
57
+ end
58
+
59
+ it "server should be webrick" do
60
+ @deployed_environment.server.should == :webrick
61
+ end
62
+
63
+ it "identifier should be 3001" do
64
+ @deployed_environment.identifier.should == 3001
65
+ end
66
+
67
+ it "mode should be queue" do
68
+ @deployed_environment.determine_mode.should == :queue
69
+ end
70
+
71
+ after(:all) do
72
+ ObjectSpace.garbage_collect
73
+ Object.class_eval { remove_const :OPTIONS }
74
+ Object.class_eval { remove_const :DEFAULT_PORT }
75
+ end
76
+
77
+ end
78
+
79
+ describe "thin" do
80
+
81
+ before(:all) do
82
+ class << self
83
+ module ::Thin
84
+ class Server
85
+ def backend; self; end
86
+ def socket; "/socket/file.000"; end
87
+ end
88
+ end
89
+ end
90
+ Thin::Server.new
91
+
92
+ @deployed_environment = Exceptional::DeployedEnvironment.new
93
+ end
94
+
95
+ it "server should be thin" do
96
+ @deployed_environment.server.should == :thin
97
+ end
98
+
99
+ it "identifier should be the socket file" do
100
+ @deployed_environment.identifier.should == '/socket/file.000'
101
+ end
102
+
103
+ it "mode should be queue" do
104
+ @deployed_environment.determine_mode.should == :queue
105
+ end
106
+
107
+ after(:all) do
108
+ ObjectSpace.garbage_collect
109
+ end
110
+
111
+ end
112
+
113
+ describe "litespeed" do
114
+
115
+ before(:all) do
116
+ @deployed_environment = Exceptional::DeployedEnvironment.new
117
+ end
118
+
119
+ # Hmmph, how to determine if we're running under litespeed...
120
+ it "server should be unknown" do
121
+ @deployed_environment.server.should == :unknown
122
+ end
123
+
124
+ it "identifier should be nil" do
125
+ @deployed_environment.identifier.should == nil
126
+ end
127
+
128
+ it "mode should be queue" do
129
+ @deployed_environment.determine_mode.should == :queue
130
+ end
131
+
132
+ after(:all) do
133
+ ObjectSpace.garbage_collect
134
+ end
135
+
136
+ end
137
+
138
+ describe "passenger" do
139
+
140
+ before(:all) do
141
+ class << self
142
+ module ::Passenger
143
+ const_set "AbstractServer", 0
144
+ end
145
+ end
146
+ @deployed_environment = Exceptional::DeployedEnvironment.new
147
+ end
148
+
149
+ it "server should be passenger" do
150
+ @deployed_environment.server.should == :passenger
151
+ end
152
+
153
+ # Would be nicer to identify passenger by some
154
+ it "identifier should be passenger" do
155
+ @deployed_environment.identifier.should == 'passenger'
156
+ end
157
+
158
+ it "mode should be queue" do
159
+ @deployed_environment.determine_mode.should == :direct
160
+ end
161
+
162
+ after(:all) do
163
+ ObjectSpace.garbage_collect
164
+ end
165
+
166
+ end
167
+
168
+ end
@@ -0,0 +1,34 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe Exceptional::ExceptionData do
4
+
5
+ describe "with valid base data" do
6
+
7
+ before(:each) do
8
+ @exception_data = Exceptional::ExceptionData.new
9
+ @exception_data.exception_class = "Error"
10
+ @exception_data.exception_message = "There was an error"
11
+ @exception_data.exception_backtrace = "/var/www/app/fail.rb:42 Error('There was an error')"
12
+ end
13
+
14
+ it "language should be ruby" do
15
+ @exception_data.language.should == "ruby"
16
+ end
17
+
18
+ it "should be valid" do
19
+ @exception_data.should be_valid
20
+ end
21
+
22
+ it "should convert to hash" do
23
+ @exception_data.to_hash.should == {:exception_class => "Error", :exception_message => "There was an error",
24
+ :exception_backtrace => "/var/www/app/fail.rb:42 Error('There was an error')",
25
+ :language => "ruby"}
26
+ end
27
+
28
+ it "should convert to json" do
29
+ @exception_data.to_json.class.should == String
30
+ end
31
+
32
+ end
33
+
34
+ end
@@ -0,0 +1,67 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe Exceptional do
4
+
5
+ describe "with no configuration" do
6
+ it "should connect to getexceptional.com by default" do
7
+ Exceptional.remote_host.should == "getexceptional.com"
8
+ end
9
+
10
+ it "should connect to port 80 by default" do
11
+ Exceptional.remote_port.should == 80
12
+ end
13
+
14
+ it "should parse exception into exception data object" do
15
+ exception = mock(Exception, :message => "Something bad has happened", :backtrace => ["/app/controllers/buggy_controller.rb:29:in `index'"])
16
+ exception_data = Exceptional.parse(exception)
17
+ exception_data.kind_of?(Exceptional::ExceptionData).should be_true
18
+ exception_data.exception_message.should == exception.message
19
+ exception_data.exception_backtrace.should == exception.backtrace
20
+ exception_data.exception_class.should == exception.class.to_s
21
+ end
22
+
23
+ it "should post exception" do
24
+ exception_data = mock(Exceptional::ExceptionData, :message => "Something bad has happened", :backtrace => ["/app/controllers/buggy_controller.rb:29:in `index'"], :class => Exception)
25
+ Exceptional.should_receive(:call_remote, :with => [:errors, exception_data])
26
+ Exceptional.post(exception_data)
27
+ end
28
+
29
+ it "should catch exception" do
30
+ exception = mock(Exception, :message => "Something bad has happened", :backtrace => ["/app/controllers/buggy_controller.rb:29:in `index'"])
31
+ exception_data = mock(Exceptional::ExceptionData, :message => "Something bad has happened", :backtrace => ["/app/controllers/buggy_controller.rb:29:in `index'"], :class => Exception)
32
+ Exceptional.should_receive(:parse, :with => [exception]).and_return(exception_data)
33
+ Exceptional.should_receive(:post, :with => [exception_data])
34
+ Exceptional.catch(exception)
35
+ end
36
+
37
+ it "should raise a license exception if api key is not set" do
38
+ exception_data = mock(Exceptional::ExceptionData, :message => "Something bad has happened", :backtrace => ["/app/controllers/buggy_controller.rb:29:in `index'"], :class => Exception)
39
+ Exceptional.api_key.should == nil
40
+ lambda { Exceptional.post(exception_data) }.should raise_error(Exceptional::LicenseException)
41
+ end
42
+
43
+ end
44
+
45
+ describe "with a custom host" do
46
+
47
+ it "should overwrite default host" do
48
+ Exceptional.remote_host = "localhost"
49
+ Exceptional.remote_host.should == "localhost"
50
+ end
51
+
52
+ it "should overwrite default port" do
53
+ Exceptional.remote_port = 3000
54
+ Exceptional.remote_port.should == 3000
55
+ Exceptional.remote_port = nil
56
+ end
57
+ end
58
+
59
+ describe "with ssl enabled" do
60
+
61
+ it "should connect to port 443" do
62
+ Exceptional.ssl_enabled = true
63
+ Exceptional.remote_port.should == 443
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,2 @@
1
+ require 'rubygems'
2
+ require File.dirname(__FILE__) + '/../lib/exceptional'
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: davidjrice-exceptional
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - David Rice
8
+ - Paul Campbell
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2008-10-13 00:00:00 -07:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: json
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ description: Exceptional is the core Ruby library for communicating with http://getexceptional.com (hosted error tracking service)
26
+ email: david@getexceptional.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - History.txt
33
+ - Manifest
34
+ - README
35
+ files:
36
+ - History.txt
37
+ - Manifest
38
+ - README
39
+ - Rakefile
40
+ - exceptional.gemspec
41
+ - exceptional.yml
42
+ - init.rb
43
+ - install.rb
44
+ - lib/exceptional/agent/worker.rb
45
+ - lib/exceptional/deployed_environment.rb
46
+ - lib/exceptional/exception_data.rb
47
+ - lib/exceptional/integration/rails.rb
48
+ - lib/exceptional/rails.rb
49
+ - lib/exceptional/version.rb
50
+ - lib/exceptional.rb
51
+ has_rdoc: true
52
+ homepage: http://github.com/contrast/exceptional
53
+ post_install_message:
54
+ rdoc_options:
55
+ - --main
56
+ - README
57
+ require_paths:
58
+ - lib
59
+ required_ruby_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
64
+ version:
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: "0"
70
+ version:
71
+ requirements: []
72
+
73
+ rubyforge_project:
74
+ rubygems_version: 1.2.0
75
+ signing_key:
76
+ specification_version: 2
77
+ summary: Exceptional is the core Ruby library for communicating with http://getexceptional.com (hosted error tracking service)
78
+ test_files:
79
+ - spec/deployed_environment_spec.rb
80
+ - spec/exception_data_spec.rb
81
+ - spec/exceptional_spec.rb
82
+ - spec/spec_helper.rb
83
+ - spec/worker_spec.rbrb