contrast-exceptional 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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.rb"]
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,19 @@
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
+ File.copy example_config_file, config_file
19
+ 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,86 @@
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
+
84
+ end
85
+ end
86
+
@@ -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,231 @@
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
+ # Hack to force Rails version prior to 2.0 to use quoted JSON as per the JSON standard... (TODO: could be cleaner!)
16
+ ActiveSupport::JSON.unquote_hash_key_identifiers = false if (defined?(ActiveSupport::JSON) && ActiveSupport::JSON.respond_to?(:unquote_hash_key_identifiers))
17
+
18
+ module Exceptional
19
+ class LicenseException < StandardError; end
20
+ class ConfigurationException < StandardError; end
21
+
22
+ ::PROTOCOL_VERSION = 3
23
+ # Defaults for configuration variables
24
+ ::REMOTE_HOST = "getexceptional.com"
25
+ ::REMOTE_PORT = 80
26
+ ::REMOTE_SSL_PORT = 443
27
+ ::SSL = false
28
+ ::LOG_LEVEL = 'info'
29
+ ::LOG_PATH = nil
30
+ ::WORKER_TIMEOUT = 10 # seconds
31
+ ::MODE = :direct
32
+
33
+ class << self
34
+ attr_accessor :api_key, :log, :deployed_environment, :log_path, :worker,
35
+ :worker_thread, :environment, :application_root
36
+ attr_writer :remote_host, :remote_port, :ssl_enabled, :log_level
37
+
38
+ # rescue any exceptions within the given block,
39
+ # send it to exceptional,
40
+ # then raise
41
+ def rescue(&block)
42
+ begin
43
+ block.call
44
+ rescue Exception => e
45
+ self.catch(e)
46
+ raise(e)
47
+ end
48
+ end
49
+
50
+ # parse an exception into an ExceptionData object
51
+ def parse(exception)
52
+ exception_data = ExceptionData.new
53
+ exception_data.exception_backtrace = exception.backtrace
54
+ exception_data.exception_message = exception.message
55
+ exception_data.exception_class = exception.class.to_s
56
+ exception_data
57
+ end
58
+
59
+ # authenticate with getexceptional.com
60
+ # returns true if the configured api_key is registered and can send data
61
+ # otherwise false
62
+ def authenticate
63
+ begin
64
+ # TODO No data required to authenticate, send a nil string? hacky
65
+ # TODO should retry if a http connection failed
66
+ return @authenticated if @authenticated
67
+ authenticated = call_remote(:authenticate, "")
68
+ @authenticated = authenticated =~ /true/
69
+ rescue
70
+ @authenticated = false
71
+ ensure
72
+ return @authenticated
73
+ end
74
+ end
75
+
76
+ # post the given exception data to getexceptional.com
77
+ def post(exception_data)
78
+ call_remote(:errors, exception_data.to_json)
79
+ end
80
+
81
+ # given a regular ruby Exception class, will parse into an ExceptionData
82
+ # object and post to getexceptional.com
83
+ def catch(exception)
84
+ exception_data = parse(exception)
85
+ exception_data.controller_name = File.basename($0)
86
+ post(exception_data)
87
+ end
88
+
89
+ # used with Rails, takes an exception, controller, request and parameters
90
+ # creates an ExceptionData object
91
+ # if Exceptional is running in :direct mode, will post to getexceptional.com
92
+ # if Exceptional is running in :queue mode, the data will be queued and posted later
93
+ def handle(exception, controller, request, params)
94
+ log! "Handling #{exception.message}", 'info'
95
+ e = parse(exception)
96
+ # Additional data for Rails Exceptions
97
+ e.framework = "rails"
98
+ e.controller_name = controller.controller_name
99
+ e.action_name = controller.action_name
100
+ e.application_root = self.application_root
101
+ e.occurred_at = Time.now.strftime("%Y%m%d %H:%M:%S %Z")
102
+ e.environment = request.env.to_hash
103
+ e.url = "#{request.protocol}#{request.host}#{request.request_uri}"
104
+ # Need to remove rack data from environment hash
105
+ safe_environment = request.env.to_hash
106
+ safe_environment.delete_if { |k,v| k =~ /rack/ }
107
+ e.environment = safe_environment
108
+
109
+ safe_session = {}
110
+ request.session.instance_variables.each do |v|
111
+ next if v =~ /cgi/
112
+ next if v =~ /db/
113
+ # remove prepended @'s
114
+ var = v.sub("@","")
115
+ safe_session[var] = request.session.instance_variable_get(v)
116
+ end
117
+
118
+ e.session = safe_session
119
+ e.parameters = params.to_hash
120
+
121
+ if mode == :queue
122
+ worker.add_exception(e)
123
+ else # :direct mode
124
+ begin
125
+ post e
126
+ rescue Exception => exception
127
+ log! "Error posting data to Exceptional."
128
+ log! exception.message
129
+ log! exception.backtace.join("\n"), 'debug'
130
+ end
131
+ end
132
+ end
133
+
134
+ # TODO these configuration methods & defaults should have their own class
135
+ def remote_host
136
+ @remote_host || ::REMOTE_HOST
137
+ end
138
+
139
+ def remote_port
140
+ @remote_port || default_port
141
+ end
142
+
143
+ def log_level
144
+ @log_level || ::LOG_LEVEL
145
+ end
146
+
147
+ def mode
148
+ deployed_environment ? deployed_environment.determine_mode : ::MODE
149
+ end
150
+
151
+ def default_port
152
+ ssl_enabled? ? ::REMOTE_SSL_PORT : ::REMOTE_PORT
153
+ end
154
+
155
+ def ssl_enabled?
156
+ @ssl_enabled || ::SSL
157
+ end
158
+
159
+ def enabled?
160
+ @enabled
161
+ end
162
+
163
+ def log!(msg, level = 'info')
164
+ to_stderr msg
165
+ log.send level, msg if log
166
+ end
167
+
168
+ def to_stderr(msg)
169
+ if deployed_environment && deployed_environment.server != :unknown
170
+ STDERR.puts "** [Exceptional] " + msg
171
+ end
172
+ end
173
+
174
+ def log_config_info
175
+ log! "API Key: #{api_key}", 'debug'
176
+ log! "Deployed Environment: #{deployed_environment.to_s}", 'debug'
177
+ log! "Remote Host: #{remote_host}:#{remote_port}", 'debug'
178
+ log! "Mode: #{mode}", 'debug'
179
+ log! "Log level: #{log_level}", 'debug'
180
+ log! "Log path: #{log_path}", 'debug'
181
+ end
182
+
183
+ def load_config(file)
184
+ begin
185
+ config = YAML::load(File.open(file))[self.environment]
186
+ @api_key = config['api-key'] unless config['api-key'].nil?
187
+ @ssl_enabled = config['ssl'] unless config['ssl'].nil?
188
+ @log_level = config['log-level'] unless config['log-level'].nil?
189
+ @enabled = config['enabled'] unless config['enabled'].nil?
190
+ @remote_port = config['remote-port'].to_i unless config['remote-port'].nil?
191
+ @remote_host = config['remote-host'] unless config['remote-host'].nil?
192
+ rescue Exception => e
193
+ raise ConfigurationException.new("Unable to load configuration file:#{file} for environment:#{environment}")
194
+ end
195
+ end
196
+
197
+ protected
198
+
199
+ def valid_api_key?
200
+ @api_key && @api_key.length == 40
201
+ end
202
+
203
+
204
+ def call_remote(method, data)
205
+ if @api_key.nil?
206
+ raise LicenseException.new("API Key must be configured")
207
+ end
208
+
209
+ http = Net::HTTP.new(remote_host, remote_port)
210
+ uri = "/#{method.to_s}?&api_key=#{@api_key}&protocol_version=#{::PROTOCOL_VERSION}"
211
+ headers = { 'Content-Type' => 'application/x-gzip', 'Accept' => 'application/x-gzip' }
212
+ compressed_data = CGI::escape(Zlib::Deflate.deflate(data, Zlib::BEST_SPEED))
213
+ response = http.start do |http|
214
+ http.post(uri, compressed_data, headers)
215
+ end
216
+
217
+ if response.kind_of? Net::HTTPSuccess
218
+ return response.body
219
+ else
220
+ raise Exception.new("#{response.code}: #{response.message}")
221
+ end
222
+
223
+ rescue Exception => e
224
+ log! "Error contacting Exceptional: #{e}", 'info'
225
+ log! e.backtrace.join("\n"), 'debug'
226
+ raise e
227
+ end
228
+
229
+ end
230
+
231
+ 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'
@@ -0,0 +1,21 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe Exceptional::Agent::Worker do
4
+
5
+ before(:each) do
6
+ @worker = Exceptional::Agent::Worker.new
7
+ end
8
+
9
+ describe "after initialisation" do
10
+
11
+ it "should default worker timeout" do
12
+ @worker.timeout.should == 10
13
+ end
14
+
15
+ it "should have no exceptions" do
16
+ @worker.exceptions.should == []
17
+ end
18
+
19
+ end
20
+
21
+ end
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: contrast-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.rb