exceptional 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/bin/exceptional +36 -0
- data/exceptional.gemspec +15 -0
- data/init.rb +24 -0
- data/install.rb +19 -0
- data/lib/exceptional.rb +33 -0
- data/lib/exceptional/application_environment.rb +62 -0
- data/lib/exceptional/catcher.rb +12 -0
- data/lib/exceptional/config.rb +85 -0
- data/lib/exceptional/exception_data.rb +95 -0
- data/lib/exceptional/integration/rack.rb +14 -0
- data/lib/exceptional/integration/rails.rb +26 -0
- data/lib/exceptional/integration/tester.rb +17 -0
- data/lib/exceptional/log_factory.rb +39 -0
- data/lib/exceptional/remote.rb +51 -0
- data/lib/exceptional/startup.rb +14 -0
- data/rails/init.rb +1 -0
- data/spec/bin/ginger +54 -0
- data/spec/exceptional/catcher_spec.rb +13 -0
- data/spec/exceptional/config_spec.rb +63 -0
- data/spec/exceptional/exception_data_spec.rb +123 -0
- data/spec/exceptional/remote_spec.rb +31 -0
- data/spec/exceptional/startup_spec.rb +15 -0
- data/spec/exceptional_rescue_spec.rb +17 -0
- data/spec/fixtures/exceptional.yml +10 -0
- data/spec/fixtures/exceptional_old.yml +35 -0
- data/spec/ginger_scenarios.rb +39 -0
- data/spec/rails_integration_spec.rb +68 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/standalone_spec.rb +9 -0
- data/tasks/exceptional_tasks.rake +11 -0
- metadata +92 -0
data/bin/exceptional
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
args = ARGV.dup
|
4
|
+
ARGV.clear
|
5
|
+
command = args.shift.strip rescue 'help'
|
6
|
+
|
7
|
+
case command
|
8
|
+
when 'help'
|
9
|
+
puts <<USAGE
|
10
|
+
help # show this usage
|
11
|
+
test # send a test exception to exceptional
|
12
|
+
install <api_key> # create config/exceptional.yml with your api_key. Overrites existing one.
|
13
|
+
USAGE
|
14
|
+
when 'test'
|
15
|
+
puts "Loading Rails environment."
|
16
|
+
require(File.join('config', 'boot'))
|
17
|
+
require(File.join(RAILS_ROOT, 'config', 'environment'))
|
18
|
+
require "exceptional/integration/tester"
|
19
|
+
unless Exceptional::Config.api_key.blank?
|
20
|
+
Exceptional::Integration.test
|
21
|
+
end
|
22
|
+
when 'install'
|
23
|
+
api_key = args[0]
|
24
|
+
if (api_key.nil?)
|
25
|
+
puts 'missing required paramater <api-key>. Check your app configuration at http://getexceptional.com'
|
26
|
+
else
|
27
|
+
unless File.file?('config/environment.rb')
|
28
|
+
puts "Run this command from the root of your rails application"
|
29
|
+
else
|
30
|
+
config_file = File.open('config/exceptional.yml', 'w')
|
31
|
+
config_file.puts("api-key: #{api_key}\n")
|
32
|
+
config_file.close
|
33
|
+
puts "Config file written as config/exceptional.yml"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/exceptional.gemspec
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
Gem::Specification.new do |s|
|
3
|
+
s.name = %q{exceptional}
|
4
|
+
s.version = "2.0.0"
|
5
|
+
s.authors = ["Contrast"]
|
6
|
+
s.summary = %q{Exceptional is the core Ruby library for communicating with http://getexceptional.com (hosted error tracking service)}
|
7
|
+
s.description = %q{Exceptional is the core Ruby library for communicating with http://getexceptional.com (hosted error tracking service). Use it to find out about errors that happen in your live app. It captures lots of helpful information to help you fix the errors.}
|
8
|
+
s.email = %q{hello@contrast.ie}
|
9
|
+
s.files = Dir['lib/**/*'] + Dir['spec/**/*'] + Dir['spec/**/*'] + Dir['rails/**/*'] + Dir['tasks/**/*'] + Dir['*.rb'] + ["exceptional.gemspec"]
|
10
|
+
s.homepage = %q{http://getexceptional.com/}
|
11
|
+
s.require_paths = ["lib"]
|
12
|
+
s.executables << 'exceptional'
|
13
|
+
s.rubyforge_project = %q{exceptional}
|
14
|
+
s.add_dependency('json', ">= 1.0.0")
|
15
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'exceptional'
|
2
|
+
|
3
|
+
# If old plugin still installed then we don't want to install this one.
|
4
|
+
# In production environments we should continue to work as before, but in development/test we should
|
5
|
+
# advise how to correct the problem and exit
|
6
|
+
if defined?(Exceptional::VERSION::STRING) && %w(development test).include?(RAILS_ENV)
|
7
|
+
message = %Q(
|
8
|
+
***********************************************************************
|
9
|
+
You seem to still have an old version of the exceptional plugin installed.
|
10
|
+
Remove it from /vendor/plugins and try again.
|
11
|
+
***********************************************************************
|
12
|
+
)
|
13
|
+
puts message
|
14
|
+
exit -1
|
15
|
+
else
|
16
|
+
begin
|
17
|
+
Exceptional::Config.load(File.join(RAILS_ROOT, "/config/exceptional.yml"))
|
18
|
+
Exceptional::Startup.announce
|
19
|
+
require File.join('exceptional', 'integration', 'rails')
|
20
|
+
rescue => e
|
21
|
+
STDERR.puts "Problem starting Exceptional Plugin. Your app will run as normal."
|
22
|
+
STDERR.puts e
|
23
|
+
end
|
24
|
+
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
|
data/lib/exceptional.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
2
|
+
|
3
|
+
require 'exceptional/catcher'
|
4
|
+
require 'exceptional/startup'
|
5
|
+
require 'exceptional/log_factory'
|
6
|
+
require 'exceptional/config'
|
7
|
+
require 'exceptional/application_environment'
|
8
|
+
require 'exceptional/exception_data'
|
9
|
+
require 'exceptional/remote'
|
10
|
+
require 'exceptional/integration/rack'
|
11
|
+
|
12
|
+
module Exceptional
|
13
|
+
PROTOCOL_VERSION = 5
|
14
|
+
VERSION = '0.2.0'
|
15
|
+
CLIENT_NAME = 'getexceptional-rails-plugin'
|
16
|
+
|
17
|
+
def self.logger
|
18
|
+
::Exceptional::LogFactory.logger
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.configure(api_key)
|
22
|
+
Exceptional::Config.api_key = api_key
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.rescue(&block)
|
26
|
+
begin
|
27
|
+
block.call
|
28
|
+
rescue Exception => e
|
29
|
+
Exceptional::Catcher.handle(e)
|
30
|
+
raise(e)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
|
3
|
+
module Exceptional
|
4
|
+
class ApplicationEnvironment
|
5
|
+
def self.to_hash
|
6
|
+
hash = {
|
7
|
+
'client' => {
|
8
|
+
'name' => Exceptional::CLIENT_NAME,
|
9
|
+
'version' => Exceptional::VERSION,
|
10
|
+
'protocol_version' => Exceptional::PROTOCOL_VERSION
|
11
|
+
},
|
12
|
+
'application_environment' => {
|
13
|
+
'environment' => environment,
|
14
|
+
'env' => extract_environment(ENV),
|
15
|
+
'host' => get_hostname,
|
16
|
+
'run_as_user' => get_username,
|
17
|
+
'application_root_directory' => application_root,
|
18
|
+
'language' => 'ruby',
|
19
|
+
'language_version' => "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} #{RUBY_RELEASE_DATE} #{RUBY_PLATFORM}",
|
20
|
+
'framework' => framework,
|
21
|
+
'libraries_loaded' => libraries_loaded
|
22
|
+
}
|
23
|
+
}
|
24
|
+
hash
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.framework
|
28
|
+
defined?(RAILS_ENV) ? "rails" : nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.environment
|
32
|
+
Config.application_environment
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.application_root
|
36
|
+
Config.application_root
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.extract_environment(env)
|
40
|
+
env.reject{|k, v| k =~ /^HTTP_/}
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.get_hostname
|
44
|
+
require 'socket' unless defined?(Socket)
|
45
|
+
Socket.gethostname
|
46
|
+
rescue
|
47
|
+
'UNKNOWN'
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.get_username
|
51
|
+
ENV['LOGNAME'] || ENV['USER'] || ENV['USERNAME'] || ENV['APACHE_RUN_USER'] || 'UNKNOWN'
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.libraries_loaded
|
55
|
+
begin
|
56
|
+
return Hash[*Gem.loaded_specs.map{|name, gem_specification| [name, gem_specification.version.to_s]}.flatten]
|
57
|
+
rescue
|
58
|
+
end
|
59
|
+
{}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Exceptional
|
2
|
+
class Config
|
3
|
+
class << self
|
4
|
+
DEFAULTS = {
|
5
|
+
:ssl_enabled => false,
|
6
|
+
:remote_host_http => 'api.getexceptional.com',
|
7
|
+
:remote_port_http => 80,
|
8
|
+
:remote_host_https => 'getexceptional.appspot.com',
|
9
|
+
:remote_port_https => 443,
|
10
|
+
:http_open_timeout => 2,
|
11
|
+
:http_read_timeout => 4,
|
12
|
+
:disabled_by_default => %w(development test)
|
13
|
+
}
|
14
|
+
|
15
|
+
attr_accessor :api_key
|
16
|
+
attr_accessor :http_proxy_host, :http_proxy_port, :http_proxy_username, :http_proxy_password
|
17
|
+
attr_writer :ssl_enabled
|
18
|
+
|
19
|
+
def load(config_file=nil)
|
20
|
+
if (config_file && File.file?(config_file))
|
21
|
+
begin
|
22
|
+
config = YAML::load(File.open(config_file))
|
23
|
+
env_config = config[application_environment] || {}
|
24
|
+
@api_key = config['api-key'] || env_config['api-key']
|
25
|
+
|
26
|
+
@http_proxy_host = config['http-proxy-host']
|
27
|
+
@http_proxy_port = config['http-proxy-port']
|
28
|
+
@http_proxy_username = config['http-proxy-username']
|
29
|
+
@http_proxy_password = config['http-proxy-password']
|
30
|
+
@http_open_timeout = config['http-open-timeout']
|
31
|
+
@http_read_timeout = config['http-read-timeout']
|
32
|
+
|
33
|
+
@ssl_enabled = config['ssl'] || env_config['ssl']
|
34
|
+
@enabled = env_config['enabled']
|
35
|
+
@remote_port = config['remote-port'].to_i unless config['remote-port'].nil?
|
36
|
+
@remote_host = config['remote-host'] unless config['remote-host'].nil?
|
37
|
+
rescue Exception => e
|
38
|
+
raise ConfigurationException.new("Unable to load configuration #{config_file} for environment #{application_environment} : #{e.message}")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def api_key
|
44
|
+
return @api_key unless @api_key.nil?
|
45
|
+
@api_key ||= ENV['EXCEPTIONAL_API_KEY'] unless ENV['EXCEPTIONAL_API_KEY'].nil?
|
46
|
+
end
|
47
|
+
|
48
|
+
def application_environment
|
49
|
+
ENV['RACK_ENV'] || ENV['RAILS_ENV']|| 'development'
|
50
|
+
end
|
51
|
+
|
52
|
+
def should_send_to_api?
|
53
|
+
@enabled ||= DEFAULTS[:disabled_by_default].include?(application_environment) ? false : true
|
54
|
+
end
|
55
|
+
|
56
|
+
def application_root
|
57
|
+
defined?(RAILS_ROOT) ? RAILS_ROOT : Dir.pwd
|
58
|
+
end
|
59
|
+
|
60
|
+
def ssl_enabled?
|
61
|
+
@ssl_enabled ||= DEFAULTS[:ssl_enabled]
|
62
|
+
end
|
63
|
+
|
64
|
+
def remote_host
|
65
|
+
@remote_host ||= ssl_enabled? ? DEFAULTS[:remote_host_https] : DEFAULTS[:remote_host_http]
|
66
|
+
end
|
67
|
+
|
68
|
+
def remote_port
|
69
|
+
@remote_port ||= ssl_enabled? ? DEFAULTS[:remote_port_https] : DEFAULTS[:remote_port_http]
|
70
|
+
end
|
71
|
+
|
72
|
+
def reset
|
73
|
+
@enabled = @ssl_enabled = @remote_host = @remote_port = @api_key = nil
|
74
|
+
end
|
75
|
+
|
76
|
+
def http_open_timeout
|
77
|
+
@http_open_timeout ||= DEFAULTS[:http_open_timeout]
|
78
|
+
end
|
79
|
+
|
80
|
+
def http_read_timeout
|
81
|
+
@http_read_timeout ||= DEFAULTS[:http_read_timeout]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
|
3
|
+
module Exceptional
|
4
|
+
class ExceptionData
|
5
|
+
def initialize(exception, controller=nil, request=nil)
|
6
|
+
@exception = exception
|
7
|
+
@request = request
|
8
|
+
@controller = controller
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_hash
|
12
|
+
hash = ::Exceptional::ApplicationEnvironment.to_hash
|
13
|
+
hash.merge!({
|
14
|
+
'exception' => {
|
15
|
+
'exception_class' => @exception.class.to_s,
|
16
|
+
'message' => @exception.message,
|
17
|
+
'backtrace' => @exception.backtrace,
|
18
|
+
'occurred_at' => Time.now.strftime("%Y%m%d %H:%M:%S %Z")
|
19
|
+
}
|
20
|
+
})
|
21
|
+
unless @request.nil?
|
22
|
+
hash.merge!({
|
23
|
+
'request' => {
|
24
|
+
'url' => "#{@request.protocol}#{@request.host}#{@request.request_uri}",
|
25
|
+
'controller' => @controller.class.to_s,
|
26
|
+
'action' => @request.parameters['action'],
|
27
|
+
'parameters' => filter_paramaters(@request.parameters),
|
28
|
+
'request_method' => @request.request_method.to_s,
|
29
|
+
'remote_ip' => @request.remote_ip,
|
30
|
+
'headers' => extract_http_headers(@request.env),
|
31
|
+
'session' => Exceptional::ExceptionData.sanitize_session(@request)
|
32
|
+
}
|
33
|
+
})
|
34
|
+
end
|
35
|
+
hash
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_json
|
39
|
+
to_hash.to_json
|
40
|
+
end
|
41
|
+
|
42
|
+
def uniqueness_hash
|
43
|
+
return nil if @exception.backtrace.blank?
|
44
|
+
Digest::MD5.hexdigest(@exception.backtrace.join)
|
45
|
+
end
|
46
|
+
|
47
|
+
def filter_paramaters(hash)
|
48
|
+
if @controller.respond_to?(:filter_parameters)
|
49
|
+
@controller.send(:filter_parameters, hash)
|
50
|
+
else
|
51
|
+
hash
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def extract_http_headers(env)
|
56
|
+
headers = {}
|
57
|
+
env.select{|k, v| k =~ /^HTTP_/}.each do |name, value|
|
58
|
+
proper_name = name.sub(/^HTTP_/, '').split('_').map{|upper_case| upper_case.capitalize}.join('-')
|
59
|
+
headers[proper_name] = value
|
60
|
+
end
|
61
|
+
unless headers['Cookie'].nil?
|
62
|
+
headers['Cookie'] = headers['Cookie'].sub(/_session=\S+/, '_session=[FILTERED]')
|
63
|
+
end
|
64
|
+
headers
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.sanitize_hash(hash)
|
68
|
+
case hash
|
69
|
+
when Hash
|
70
|
+
hash.inject({}) do |result, (key, value)|
|
71
|
+
result.update(key => sanitize_hash(value))
|
72
|
+
end
|
73
|
+
when Fixnum, Array, String, Bignum
|
74
|
+
hash
|
75
|
+
else
|
76
|
+
hash.to_s
|
77
|
+
end
|
78
|
+
rescue
|
79
|
+
{}
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.sanitize_session(request)
|
83
|
+
session = request.session
|
84
|
+
session_hash = {}
|
85
|
+
session_hash['session_id'] = request.session_options ? request.session_options[:id] : nil
|
86
|
+
session_hash['session_id'] ||= session.respond_to?(:session_id) ? session.session_id : session.instance_variable_get("@session_id")
|
87
|
+
session_hash['data'] = session.respond_to?(:to_hash) ? session.to_hash : session.instance_variable_get("@data") || {}
|
88
|
+
session_hash['session_id'] ||= session_hash['data'][:session_id]
|
89
|
+
session_hash['data'].delete(:session_id)
|
90
|
+
ExceptionData.sanitize_hash(session_hash)
|
91
|
+
rescue
|
92
|
+
{}
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# force Rails < 2.0 to use quote keys as per the JSON standard...
|
2
|
+
if defined?(ActiveSupport) && defined?(ActiveSupport::JSON) && ActiveSupport::JSON.respond_to?(:unquote_hash_key_identifiers)
|
3
|
+
ActiveSupport::JSON.unquote_hash_key_identifiers = false
|
4
|
+
end
|
5
|
+
|
6
|
+
if defined? ActionController
|
7
|
+
module ActionController
|
8
|
+
class Base
|
9
|
+
def rescue_action_with_exceptional(exception)
|
10
|
+
unless exception_handled_by_rescue_from?(exception)
|
11
|
+
Exceptional::Catcher.handle(exception, self, request)
|
12
|
+
end
|
13
|
+
rescue_action_without_exceptional exception
|
14
|
+
end
|
15
|
+
|
16
|
+
alias_method :rescue_action_without_exceptional, :rescue_action
|
17
|
+
alias_method :rescue_action, :rescue_action_with_exceptional
|
18
|
+
protected :rescue_action
|
19
|
+
|
20
|
+
private
|
21
|
+
def exception_handled_by_rescue_from?(exception)
|
22
|
+
respond_to?(:handler_for_rescue) && handler_for_rescue(exception)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Exceptional
|
2
|
+
module Integration
|
3
|
+
class ExceptionalTestException <StandardError;
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.test
|
7
|
+
data = Exceptional::ExceptionData.new(ExceptionalTestException.new)
|
8
|
+
unless Exceptional::Remote.error(data)
|
9
|
+
puts "Problem sending error to Exceptional. Check your api key"
|
10
|
+
else
|
11
|
+
puts "Exception sent successfully"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Exceptional
|
4
|
+
class LogFactory
|
5
|
+
def self.logger
|
6
|
+
@logger ||= create_logger_with_fallback
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
def self.create_logger_with_fallback
|
11
|
+
begin
|
12
|
+
log_dir = File.join(Config.application_root, 'log')
|
13
|
+
Dir.mkdir(log_dir) unless File.directory?(log_dir)
|
14
|
+
log_path = File.join(log_dir, "/exceptional.log")
|
15
|
+
log = Logger.new(log_path)
|
16
|
+
log.level = Logger::INFO
|
17
|
+
def log.format_message(severity, timestamp, progname, msg)
|
18
|
+
"[#{severity.upcase}] (#{[Kernel.caller[2].split('/').last]}) #{timestamp.utc.to_s} - #{msg2str(msg).gsub(/\n/, '').lstrip}\n"
|
19
|
+
end
|
20
|
+
def log.msg2str(msg)
|
21
|
+
case msg
|
22
|
+
when ::String
|
23
|
+
msg
|
24
|
+
when ::Exception
|
25
|
+
"#{ msg.message } (#{ msg.class }): " <<
|
26
|
+
(msg.backtrace || []).join(" | ")
|
27
|
+
else
|
28
|
+
msg.inspect
|
29
|
+
end
|
30
|
+
end
|
31
|
+
log
|
32
|
+
rescue
|
33
|
+
return Rails.logger if defined?(Rails) && defined?(Rails.logger)
|
34
|
+
return RAILS_DEFAULT_LOGGER if defined?(RAILS_DEFAULT_LOGGER)
|
35
|
+
return Logger.new(STDERR)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
require 'cgi'
|
3
|
+
require 'net/http'
|
4
|
+
require 'digest/md5'
|
5
|
+
|
6
|
+
module Exceptional
|
7
|
+
class Remote
|
8
|
+
class << self
|
9
|
+
def startup_announce(startup_data)
|
10
|
+
url = "/api/announcements?api_key=#{::Exceptional::Config.api_key}&protocol_version=#{::Exceptional::PROTOCOL_VERSION}"
|
11
|
+
compressed = Zlib::Deflate.deflate(startup_data.to_json, Zlib::BEST_SPEED)
|
12
|
+
call_remote(url, compressed)
|
13
|
+
end
|
14
|
+
|
15
|
+
def error(exception_data)
|
16
|
+
Exceptional.logger.info "Notifying Exceptional about an error"
|
17
|
+
uniqueness_hash = exception_data.uniqueness_hash
|
18
|
+
hash_param = uniqueness_hash.nil? ? nil: "&hash=#{uniqueness_hash}"
|
19
|
+
url = "/api/errors?api_key=#{::Exceptional::Config.api_key}&protocol_version=#{::Exceptional::PROTOCOL_VERSION}#{hash_param}"
|
20
|
+
compressed = Zlib::Deflate.deflate(exception_data.to_json, Zlib::BEST_SPEED)
|
21
|
+
call_remote(url, compressed)
|
22
|
+
end
|
23
|
+
|
24
|
+
def call_remote(url, data)
|
25
|
+
config = Exceptional::Config
|
26
|
+
optional_proxy = Net::HTTP::Proxy(config.http_proxy_host,
|
27
|
+
config.http_proxy_port,
|
28
|
+
config.http_proxy_username,
|
29
|
+
config.http_proxy_password)
|
30
|
+
client = optional_proxy.new(config.remote_host, config.remote_port)
|
31
|
+
client.open_timeout = config.http_open_timeout
|
32
|
+
client.read_timeout = config.http_read_timeout
|
33
|
+
client.use_ssl = config.ssl_enabled?
|
34
|
+
begin
|
35
|
+
response = client.post(url, data)
|
36
|
+
case response
|
37
|
+
when Net::HTTPSuccess
|
38
|
+
Exceptional.logger.info('Successful')
|
39
|
+
return true
|
40
|
+
else
|
41
|
+
Exceptional.logger.error('Failed')
|
42
|
+
end
|
43
|
+
rescue Exception => e
|
44
|
+
Exceptional.logger.error('Problem notifying Exceptional about the error')
|
45
|
+
Exceptional.logger.error(e)
|
46
|
+
end
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Exceptional
|
2
|
+
class StartupException < StandardError;
|
3
|
+
end
|
4
|
+
class Startup
|
5
|
+
class << self
|
6
|
+
def announce
|
7
|
+
if Config.api_key.blank?
|
8
|
+
raise StartupException, 'API Key must be configured (/config/exceptional.yml)'
|
9
|
+
end
|
10
|
+
Remote.startup_announce(::Exceptional::ApplicationEnvironment.to_hash)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__) , '../init.rb')
|
data/spec/bin/ginger
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
begin
|
5
|
+
require 'ginger'
|
6
|
+
rescue LoadError
|
7
|
+
puts <<-INSTALL_GINGER
|
8
|
+
Install ginger to test plugin against multiple environments:
|
9
|
+
|
10
|
+
sudo gem install freelancing-god-ginger --source=http://gems.github.com
|
11
|
+
|
12
|
+
More details: http://github.com/freelancing-god/ginger
|
13
|
+
INSTALL_GINGER
|
14
|
+
exit 0
|
15
|
+
end
|
16
|
+
|
17
|
+
require 'rake'
|
18
|
+
|
19
|
+
if ARGV.length == 0
|
20
|
+
puts <<-USAGE
|
21
|
+
ginger #{Ginger::Version::String}
|
22
|
+
Use ginger to run specs for each scenario defined. Scenarios must be set out in
|
23
|
+
a file called ginger_scenarios.rb wherever this tool is run. Once they're
|
24
|
+
defined, then you can run this tool and provide the rake task that would
|
25
|
+
normally be called.
|
26
|
+
|
27
|
+
Examples:
|
28
|
+
ginger spec
|
29
|
+
ginger test
|
30
|
+
ginger spec:models
|
31
|
+
USAGE
|
32
|
+
exit 0
|
33
|
+
end
|
34
|
+
|
35
|
+
file_path = File.join Dir.pwd, ".ginger"
|
36
|
+
|
37
|
+
File.delete(file_path) if File.exists?(file_path)
|
38
|
+
|
39
|
+
scenarios = Ginger::Configuration.instance.scenarios
|
40
|
+
puts "No Ginger Scenarios defined" if scenarios.empty?
|
41
|
+
|
42
|
+
scenarios.each_with_index do |scenario, index|
|
43
|
+
puts <<-SCENARIO
|
44
|
+
|
45
|
+
-------------------
|
46
|
+
Ginger Scenario: #{scenario.name || index+1}
|
47
|
+
-------------------
|
48
|
+
SCENARIO
|
49
|
+
|
50
|
+
File.open('.ginger', 'w') { |f| f.write index.to_s }
|
51
|
+
system("rake #{ARGV.join(" ")}") || system("rake.bat #{ARGV.join(" ")}")
|
52
|
+
end
|
53
|
+
|
54
|
+
File.delete(file_path) if File.exists?(file_path)
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
describe Exceptional::Catcher do
|
4
|
+
it "should create exception_data object and send json to the api" do
|
5
|
+
Exceptional::Config.should_receive(:should_send_to_api?).and_return(true)
|
6
|
+
exception = mock('exception')
|
7
|
+
controller = mock('controller')
|
8
|
+
request = mock('request')
|
9
|
+
Exceptional::ExceptionData.should_receive(:new).with(exception,controller,request).and_return(data = mock('exception_data'))
|
10
|
+
Exceptional::Remote.should_receive(:error).with(data)
|
11
|
+
Exceptional::Catcher.handle(exception,controller,request)
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
describe Exceptional::Config, 'defaults' do
|
4
|
+
before :each do
|
5
|
+
Exceptional::Config.reset
|
6
|
+
end
|
7
|
+
it "have sensible defaults" do
|
8
|
+
Exceptional::Config.ssl_enabled?.should == false
|
9
|
+
Exceptional::Config.remote_host.should == 'api.getexceptional.com'
|
10
|
+
Exceptional::Config.remote_port.should == 80
|
11
|
+
Exceptional::Config.application_root.should == Dir.pwd
|
12
|
+
Exceptional::Config.http_proxy_host.should be_nil
|
13
|
+
Exceptional::Config.http_proxy_port.should be_nil
|
14
|
+
Exceptional::Config.http_proxy_username.should be_nil
|
15
|
+
Exceptional::Config.http_proxy_password.should be_nil
|
16
|
+
Exceptional::Config.http_open_timeout.should == 2
|
17
|
+
Exceptional::Config.http_read_timeout.should == 4
|
18
|
+
end
|
19
|
+
it "have correct defaults when ssl_enabled" do
|
20
|
+
Exceptional::Config.ssl_enabled = true
|
21
|
+
Exceptional::Config.remote_host.should == 'getexceptional.appspot.com'
|
22
|
+
Exceptional::Config.remote_port.should == 443
|
23
|
+
end
|
24
|
+
it "be enabled based on environment by default" do
|
25
|
+
%w(development test).each do |env|
|
26
|
+
Exceptional::Config.stub!(:application_environment).and_return(env)
|
27
|
+
Exceptional::Config.should_send_to_api?.should == false
|
28
|
+
end
|
29
|
+
%w(production staging).each do |env|
|
30
|
+
Exceptional::Config.stub!(:application_environment).and_return(env)
|
31
|
+
Exceptional::Config.should_send_to_api?.should == true
|
32
|
+
end
|
33
|
+
end
|
34
|
+
context 'production environment' do
|
35
|
+
before :each do
|
36
|
+
Exceptional::Config.stub!(:application_environment).and_return('production')
|
37
|
+
end
|
38
|
+
it "allow a new simpler format for exception.yml" do
|
39
|
+
Exceptional::Config.load('spec/fixtures/exceptional.yml')
|
40
|
+
Exceptional::Config.api_key.should == 'abc123'
|
41
|
+
Exceptional::Config.ssl_enabled?.should == true
|
42
|
+
Exceptional::Config.remote_host.should == 'example.com'
|
43
|
+
Exceptional::Config.remote_port.should == 123
|
44
|
+
Exceptional::Config.should_send_to_api?.should == true
|
45
|
+
Exceptional::Config.http_proxy_host.should == 'annoying-proxy.example.com'
|
46
|
+
Exceptional::Config.http_proxy_port.should == 1066
|
47
|
+
Exceptional::Config.http_proxy_username.should == 'bob'
|
48
|
+
Exceptional::Config.http_proxy_password.should == 'jack'
|
49
|
+
Exceptional::Config.http_open_timeout.should == 5
|
50
|
+
Exceptional::Config.http_read_timeout.should == 10
|
51
|
+
end
|
52
|
+
it "allow olded format for exception.yml" do
|
53
|
+
Exceptional::Config.load('spec/fixtures/exceptional_old.yml')
|
54
|
+
Exceptional::Config.api_key.should == 'abc123'
|
55
|
+
Exceptional::Config.ssl_enabled?.should == true
|
56
|
+
Exceptional::Config.should_send_to_api?.should == true
|
57
|
+
end
|
58
|
+
it "load api_key from environment variable" do
|
59
|
+
ENV.should_receive(:[]).with('EXCEPTIONAL_API_KEY').any_number_of_times.and_return('98765')
|
60
|
+
Exceptional::Config.api_key.should == '98765'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
require 'digest/md5'
|
3
|
+
|
4
|
+
class Exceptional::FunkyError < StandardError
|
5
|
+
def backtrace
|
6
|
+
'backtrace'
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe Exceptional::ExceptionData, 'when no request/controller/params' do
|
11
|
+
before :each do
|
12
|
+
ENV['LOGNAME'] = 'bob'
|
13
|
+
ENV['SOMEVAR'] = 'something'
|
14
|
+
ENV['HTTP_SOMETHING'] = 'should be stripped'
|
15
|
+
RAILS_ENV = 'test' unless defined?(RAILS_ENV)
|
16
|
+
Time.stub!(:now).and_return(Time.mktime(1970,1,1))
|
17
|
+
error = Exceptional::FunkyError.new('some message')
|
18
|
+
data = Exceptional::ExceptionData.new(error)
|
19
|
+
@hash = data.to_hash
|
20
|
+
end
|
21
|
+
|
22
|
+
it "capture exception details" do
|
23
|
+
error_hash = @hash['exception']
|
24
|
+
error_hash['exception_class'].should == 'Exceptional::FunkyError'
|
25
|
+
error_hash['message'].should == 'some message'
|
26
|
+
error_hash['backtrace'].should == 'backtrace'
|
27
|
+
error_hash['occurred_at'].should == Time.now.strftime("%Y%m%d %H:%M:%S %Z")
|
28
|
+
client_hash = @hash['client']
|
29
|
+
client_hash['name'].should == Exceptional::CLIENT_NAME
|
30
|
+
client_hash['version'].should == Exceptional::VERSION
|
31
|
+
client_hash['protocol_version'].should == Exceptional::PROTOCOL_VERSION
|
32
|
+
end
|
33
|
+
|
34
|
+
it "capture application_environment" do
|
35
|
+
application_env_hash = @hash['application_environment']
|
36
|
+
application_env_hash['environment'].should == 'test'
|
37
|
+
application_env_hash['env'].should_not be_nil
|
38
|
+
application_env_hash['env']['SOMEVAR'].should == 'something'
|
39
|
+
application_env_hash['host'].should == `hostname`.strip
|
40
|
+
application_env_hash['run_as_user'].should == 'bob'
|
41
|
+
application_env_hash['application_root_directory'].should == Dir.pwd
|
42
|
+
application_env_hash['language'].should == 'ruby'
|
43
|
+
application_env_hash['language_version'].should == "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} #{RUBY_RELEASE_DATE} #{RUBY_PLATFORM}"
|
44
|
+
application_env_hash['framework'].should == "rails"
|
45
|
+
application_env_hash['libraries_loaded']['rails'].should =~ /\d\.\d\.\d/
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe Exceptional::ExceptionData, 'with request/controller/params' do
|
50
|
+
class Exceptional::SomeController < ActionController::Base
|
51
|
+
filter_parameter_logging :filter_me
|
52
|
+
end
|
53
|
+
|
54
|
+
before :each do
|
55
|
+
@controller = Exceptional::SomeController.new
|
56
|
+
@request = ActionController::TestRequest.new({'action' => 'some_action' })
|
57
|
+
@request.request_uri = '/some_path?var1=abc'
|
58
|
+
@request.stub!(:parameters).and_return({'var1' => 'abc', 'action' => 'some_action', 'filter_me' => 'private'})
|
59
|
+
@request.stub!(:request_method).and_return(:get)
|
60
|
+
@request.stub!(:remote_ip).and_return('1.2.3.4')
|
61
|
+
@request.stub!(:env).and_return({'SOME_VAR' => 'abc', 'HTTP_CONTENT_TYPE' => 'text/html'})
|
62
|
+
error = Exceptional::FunkyError.new('some message')
|
63
|
+
data = Exceptional::ExceptionData.new(error, @controller, @request)
|
64
|
+
@hash = data.to_hash
|
65
|
+
end
|
66
|
+
|
67
|
+
it "captures request" do
|
68
|
+
request_hash = @hash['request']
|
69
|
+
request_hash['url'].should == 'http://test.host/some_path?var1=abc'
|
70
|
+
request_hash['controller'].should == 'Exceptional::SomeController'
|
71
|
+
request_hash['action'].should == 'some_action'
|
72
|
+
request_hash['parameters'].should == {'var1' => 'abc', 'action' => 'some_action', 'filter_me' => '[FILTERED]'}
|
73
|
+
request_hash['request_method'].should == 'get'
|
74
|
+
request_hash['remote_ip'].should == '1.2.3.4'
|
75
|
+
request_hash['headers'].should == {'Content-Type' => 'text/html'}
|
76
|
+
end
|
77
|
+
|
78
|
+
it "filter out objects that aren't jsonable" do
|
79
|
+
class Crazy
|
80
|
+
def initialize
|
81
|
+
@bar = self
|
82
|
+
end
|
83
|
+
end
|
84
|
+
crazy = Crazy.new
|
85
|
+
input = {'crazy' => crazy, :simple => '123', :some_hash => {'1' => '2'}, :array => ['1','2']}
|
86
|
+
Exceptional::ExceptionData.sanitize_hash(input).should == {'crazy' => crazy.to_s, :simple => '123', :some_hash => {'1' => '2'}, :array => ['1','2']}
|
87
|
+
end
|
88
|
+
|
89
|
+
it "handles session objects with various interfaces" do
|
90
|
+
class SessionWithInstanceVariables
|
91
|
+
def initialize
|
92
|
+
@data = {'a' => '1'}
|
93
|
+
@session_id = '123'
|
94
|
+
end
|
95
|
+
end
|
96
|
+
request = ActionController::TestRequest.new
|
97
|
+
session = SessionWithInstanceVariables.new
|
98
|
+
request.stub!(:session).and_return(session)
|
99
|
+
request.stub!(:session_options).and_return({})
|
100
|
+
Exceptional::ExceptionData.sanitize_session(request).should == {'session_id' => '123', 'data' => {'a' => '1'}}
|
101
|
+
session = mock('session', :session_id => '123', :instance_variable_get => {'a' => '1'})
|
102
|
+
request.stub!(:session).and_return(session)
|
103
|
+
Exceptional::ExceptionData.sanitize_session(request).should == {'session_id' => '123', 'data' => {'a' => '1'}}
|
104
|
+
session = mock('session', :session_id => nil, :to_hash => {:session_id => '123', 'a' => '1'})
|
105
|
+
request.stub!(:session).and_return(session)
|
106
|
+
Exceptional::ExceptionData.sanitize_session(request).should == {'session_id' => '123', 'data' => {'a' => '1'}}
|
107
|
+
request.stub!(:session_options).and_return({:id => 'xyz'})
|
108
|
+
Exceptional::ExceptionData.sanitize_session(request).should == {'session_id' => 'xyz', 'data' => {'a' => '1'}}
|
109
|
+
end
|
110
|
+
|
111
|
+
it "filter session cookies from headers" do
|
112
|
+
@request.stub!(:env).and_return({'SOME_VAR' => 'abc', 'HTTP_COOKIE' => '_something_else=faafsafafasfa; _myapp-lick-nation_session=BAh7DDoMbnVtYmVyc1sJaQZpB2kIaQk6FnNvbWVfY3Jhenlfb2JqZWN0bzobU3Bpa2VDb250cm9sbGVyOjpDcmF6eQY6CUBiYXJABzoTc29tZXRoaW5nX2Vsc2UiCGNjYzoKYXBwbGUiDUJyYWVidXJuOgloYXNoewdpBmkHaQhpCToPc2Vzc2lvbl9pZCIlMmJjZTM4MjVjMThkNzYxOWEyZDA4NTJhNWY1NGQzMmU6C3RvbWF0byIJQmVlZg%3D%3D--66fb4606851f06bf409b8bc4ba7aea47a0259bf7'})
|
113
|
+
@hash = Exceptional::ExceptionData.new(Exceptional::FunkyError.new('some message'), @controller, @request).to_hash
|
114
|
+
@hash['request']['headers'].should == {'Cookie' => '_something_else=faafsafafasfa; _myapp-lick-nation_session=[FILTERED]'}
|
115
|
+
end
|
116
|
+
|
117
|
+
it "creates a uniqueness_hash from backtrace" do
|
118
|
+
myException = Exception.new
|
119
|
+
myException.stub!(:backtrace).and_return(['123'])
|
120
|
+
data = Exceptional::ExceptionData.new(myException)
|
121
|
+
data.uniqueness_hash.should == Digest::MD5.hexdigest('123')
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
require 'zlib'
|
3
|
+
require 'digest/md5'
|
4
|
+
|
5
|
+
describe Exceptional::Remote do
|
6
|
+
before :each do
|
7
|
+
Exceptional::Config.reset
|
8
|
+
Exceptional::Config.api_key = 'abc123'
|
9
|
+
end
|
10
|
+
|
11
|
+
it "calls remote with api_key, protocol_version and json" do
|
12
|
+
expected_url = "/api/errors?api_key=abc123&protocol_version=#{Exceptional::PROTOCOL_VERSION}"
|
13
|
+
expected_data = mock('data',:uniqueness_hash => nil, :to_json => '123')
|
14
|
+
Exceptional::Remote.should_receive(:call_remote).with(expected_url, Zlib::Deflate.deflate(expected_data.to_json,Zlib::BEST_SPEED))
|
15
|
+
Exceptional::Remote.error(expected_data)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "adds hash of backtrace as paramater if it is present" do
|
19
|
+
expected_url = "/api/errors?api_key=abc123&protocol_version=#{Exceptional::PROTOCOL_VERSION}&hash=blah"
|
20
|
+
expected_data = mock('data',:uniqueness_hash => 'blah', :to_json => '123')
|
21
|
+
Exceptional::Remote.should_receive(:call_remote).with(expected_url, Zlib::Deflate.deflate(expected_data.to_json,Zlib::BEST_SPEED))
|
22
|
+
Exceptional::Remote.error(expected_data)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "calls remote for startup" do
|
26
|
+
expected_url = "/api/announcements?api_key=abc123&protocol_version=#{Exceptional::PROTOCOL_VERSION}"
|
27
|
+
startup_data = mock('data',:to_json => '123')
|
28
|
+
Exceptional::Remote.should_receive(:call_remote).with(expected_url, Zlib::Deflate.deflate(startup_data.to_json,Zlib::BEST_SPEED))
|
29
|
+
Exceptional::Remote.startup_announce(startup_data)
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
describe Exceptional::Startup, 'announce_and_authenticate' do
|
4
|
+
it "raise StartupException if api_key is nil" do
|
5
|
+
Exceptional::Config.api_key = ''
|
6
|
+
lambda { Exceptional::Startup.announce }.should raise_error(Exceptional::StartupException, /API Key/)
|
7
|
+
end
|
8
|
+
it "calls Remote announce" do
|
9
|
+
Exceptional::Config.api_key = '123'
|
10
|
+
Exceptional::Remote.should_receive(:startup_announce).with(hash_including({'client' => { 'name' => Exceptional::CLIENT_NAME,
|
11
|
+
'version' => Exceptional::VERSION,
|
12
|
+
'protocol_version' => Exceptional::PROTOCOL_VERSION}}))
|
13
|
+
Exceptional::Startup.announce
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
context 'resuce errors from within a block' do
|
4
|
+
class FunkyException < StandardError; end
|
5
|
+
it "send them to catcher and reraise" do
|
6
|
+
to_raise = FunkyException.new
|
7
|
+
Exceptional::Catcher.should_receive(:handle).with(to_raise)
|
8
|
+
begin
|
9
|
+
Exceptional.rescue do
|
10
|
+
raise to_raise
|
11
|
+
end
|
12
|
+
fail "expected to raise"
|
13
|
+
rescue FunkyException => e
|
14
|
+
e.should == to_raise
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,35 @@
|
|
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: abc123
|
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
|
+
ssl: true
|
30
|
+
|
31
|
+
staging:
|
32
|
+
# It's common development practice to have a staging environment that closely
|
33
|
+
# mirrors production, by default catch errors in this environment too.
|
34
|
+
<<: *default_settings
|
35
|
+
enabled: true
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'ginger'
|
2
|
+
|
3
|
+
class ScenarioWithName < Ginger::Scenario
|
4
|
+
attr_accessor :name
|
5
|
+
def initialize(name)
|
6
|
+
@name = name
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def create_scenario(version)
|
11
|
+
scenario = ScenarioWithName.new("Rails #{version}")
|
12
|
+
scenario[/^active_?support$/] = version
|
13
|
+
scenario[/^active_?record$/] = version
|
14
|
+
scenario[/^action_?pack$/] = version
|
15
|
+
scenario[/^action_?controller$/] = version
|
16
|
+
scenario[/^rails$/] = version
|
17
|
+
scenario
|
18
|
+
end
|
19
|
+
|
20
|
+
Ginger.configure do |config|
|
21
|
+
config.aliases["active_record"] = "activerecord"
|
22
|
+
config.aliases["active_support"] = "activesupport"
|
23
|
+
config.aliases["action_controller"] = "actionpack"
|
24
|
+
|
25
|
+
rails_1_2_6 = ScenarioWithName.new("Rails 1.2.6")
|
26
|
+
rails_1_2_6[/^active_?support$/] = "1.4.4"
|
27
|
+
rails_1_2_6[/^active_?record$/] = "1.15.6"
|
28
|
+
rails_1_2_6[/^action_?pack$/] = "1.13.6"
|
29
|
+
rails_1_2_6[/^action_?controller$/] = "1.13.6"
|
30
|
+
rails_1_2_6[/^rails$/] = "1.2.6"
|
31
|
+
|
32
|
+
config.scenarios << rails_1_2_6
|
33
|
+
config.scenarios << create_scenario("2.0.2")
|
34
|
+
config.scenarios << create_scenario("2.1.2")
|
35
|
+
config.scenarios << create_scenario("2.2.2")
|
36
|
+
config.scenarios << create_scenario("2.3.2")
|
37
|
+
config.scenarios << create_scenario("2.3.3")
|
38
|
+
config.scenarios << create_scenario("2.3.4")
|
39
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'exceptional', 'integration', 'rails')
|
3
|
+
|
4
|
+
describe Exceptional, 'version number' do
|
5
|
+
it "be available proramatically" do
|
6
|
+
Exceptional::VERSION.should == '0.2.0'
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe ActiveSupport::JSON, 'standards compliant json' do
|
11
|
+
it "quote keys" do
|
12
|
+
{:a => '123'}.to_json.gsub(/ /,'').should == '{"a":"123"}'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class TestingController < ActionController::Base
|
17
|
+
def raises_something
|
18
|
+
raise StandardError
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe TestingController do
|
23
|
+
before :each do
|
24
|
+
@controller = TestingController.new
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'handle exception with Exceptional::Catcher' do
|
28
|
+
Exceptional::Catcher.should_receive(:handle).with(an_instance_of(StandardError), @controller, an_instance_of(ActionController::TestRequest))
|
29
|
+
send_request(:raises_something)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "still return an error response to the user" do
|
33
|
+
Exceptional::Catcher.stub!(:handle)
|
34
|
+
send_request(:raises_something)
|
35
|
+
@response.code.should == '500'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
if ActionController::Base.respond_to?(:rescue_from)
|
40
|
+
class CustomError < StandardError; end
|
41
|
+
class TestingWithRescueFromController < ActionController::Base
|
42
|
+
rescue_from CustomError, :with => :custom_handler
|
43
|
+
def raises_custom_error
|
44
|
+
raise CustomError.new
|
45
|
+
end
|
46
|
+
def raises_other_error
|
47
|
+
raise StandardError.new
|
48
|
+
end
|
49
|
+
def custom_handler
|
50
|
+
head :ok
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe TestingWithRescueFromController do
|
55
|
+
before :each do
|
56
|
+
@controller = TestingWithRescueFromController.new
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'not handle exception with Exceptional that is dealt with by rescue_from' do
|
60
|
+
Exceptional::Catcher.should_not_receive(:handle)
|
61
|
+
send_request(:raises_custom_error)
|
62
|
+
end
|
63
|
+
it 'handle exception with Exceptional that is not dealt with by rescue_from' do
|
64
|
+
Exceptional::Catcher.should_receive(:handle)
|
65
|
+
send_request(:raises_other_error)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
begin
|
3
|
+
require 'ginger'
|
4
|
+
rescue LoadError
|
5
|
+
raise "cant load ginger"
|
6
|
+
end
|
7
|
+
gem 'rails'
|
8
|
+
require File.dirname(__FILE__) + '/../lib/exceptional' unless defined?(Exceptional)
|
9
|
+
|
10
|
+
ENV['RAILS_ENV'] = 'test'
|
11
|
+
|
12
|
+
require 'action_controller'
|
13
|
+
require 'action_controller/test_process'
|
14
|
+
|
15
|
+
def send_request(action = nil)
|
16
|
+
@request = ActionController::TestRequest.new
|
17
|
+
@request.action = action ? action.to_s : ""
|
18
|
+
@response = ActionController::TestResponse.new
|
19
|
+
@controller.process(@request, @response)
|
20
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'exceptional', 'integration', 'rails')
|
3
|
+
|
4
|
+
describe Exceptional do
|
5
|
+
it "set the api key" do
|
6
|
+
Exceptional.configure('api-key')
|
7
|
+
Exceptional::Config.api_key.should == 'api-key'
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
namespace :exceptional do
|
2
|
+
desc 'Send a test exception to Exceptional.'
|
3
|
+
task :test => :environment do
|
4
|
+
unless Exceptional::Config.api_key.blank?
|
5
|
+
puts "Sending test exception to Exceptional"
|
6
|
+
require "exceptional/integration/tester"
|
7
|
+
Exceptional::Integration.test
|
8
|
+
puts "Done."
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
metadata
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: exceptional
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 2.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Contrast
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-11-13 00:00:00 +00:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: json
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.0.0
|
24
|
+
version:
|
25
|
+
description: Exceptional is the core Ruby library for communicating with http://getexceptional.com (hosted error tracking service). Use it to find out about errors that happen in your live app. It captures lots of helpful information to help you fix the errors.
|
26
|
+
email: hello@contrast.ie
|
27
|
+
executables:
|
28
|
+
- exceptional
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files: []
|
32
|
+
|
33
|
+
files:
|
34
|
+
- lib/exceptional/application_environment.rb
|
35
|
+
- lib/exceptional/catcher.rb
|
36
|
+
- lib/exceptional/config.rb
|
37
|
+
- lib/exceptional/exception_data.rb
|
38
|
+
- lib/exceptional/integration/rack.rb
|
39
|
+
- lib/exceptional/integration/rails.rb
|
40
|
+
- lib/exceptional/integration/tester.rb
|
41
|
+
- lib/exceptional/log_factory.rb
|
42
|
+
- lib/exceptional/remote.rb
|
43
|
+
- lib/exceptional/startup.rb
|
44
|
+
- lib/exceptional.rb
|
45
|
+
- spec/bin/ginger
|
46
|
+
- spec/exceptional/catcher_spec.rb
|
47
|
+
- spec/exceptional/config_spec.rb
|
48
|
+
- spec/exceptional/exception_data_spec.rb
|
49
|
+
- spec/exceptional/remote_spec.rb
|
50
|
+
- spec/exceptional/startup_spec.rb
|
51
|
+
- spec/exceptional_rescue_spec.rb
|
52
|
+
- spec/fixtures/exceptional.yml
|
53
|
+
- spec/fixtures/exceptional_old.yml
|
54
|
+
- spec/ginger_scenarios.rb
|
55
|
+
- spec/rails_integration_spec.rb
|
56
|
+
- spec/spec_helper.rb
|
57
|
+
- spec/standalone_spec.rb
|
58
|
+
- rails/init.rb
|
59
|
+
- tasks/exceptional_tasks.rake
|
60
|
+
- init.rb
|
61
|
+
- install.rb
|
62
|
+
- exceptional.gemspec
|
63
|
+
has_rdoc: true
|
64
|
+
homepage: http://getexceptional.com/
|
65
|
+
licenses: []
|
66
|
+
|
67
|
+
post_install_message:
|
68
|
+
rdoc_options: []
|
69
|
+
|
70
|
+
require_paths:
|
71
|
+
- lib
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: "0"
|
77
|
+
version:
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: "0"
|
83
|
+
version:
|
84
|
+
requirements: []
|
85
|
+
|
86
|
+
rubyforge_project: exceptional
|
87
|
+
rubygems_version: 1.3.5
|
88
|
+
signing_key:
|
89
|
+
specification_version: 3
|
90
|
+
summary: Exceptional is the core Ruby library for communicating with http://getexceptional.com (hosted error tracking service)
|
91
|
+
test_files: []
|
92
|
+
|