exceptional 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|