cloudtrapper 0.0.2.pre
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/CHANGELOG +823 -0
- data/Gemfile +12 -0
- data/Guardfile +6 -0
- data/INSTALL +20 -0
- data/MIT-LICENSE +22 -0
- data/README.md +465 -0
- data/README_FOR_HEROKU_ADDON.md +94 -0
- data/Rakefile +223 -0
- data/SUPPORTED_RAILS_VERSIONS +23 -0
- data/TESTING.md +33 -0
- data/cloudtrapper.gemspec +35 -0
- data/features/metal.feature +18 -0
- data/features/rack.feature +56 -0
- data/features/rails.feature +211 -0
- data/features/rails_with_js_notifier.feature +97 -0
- data/features/rake.feature +27 -0
- data/features/sinatra.feature +29 -0
- data/features/step_definitions/file_steps.rb +10 -0
- data/features/step_definitions/metal_steps.rb +23 -0
- data/features/step_definitions/rack_steps.rb +23 -0
- data/features/step_definitions/rails_application_steps.rb +433 -0
- data/features/step_definitions/rake_steps.rb +17 -0
- data/features/support/airbrake_shim.rb.template +11 -0
- data/features/support/env.rb +18 -0
- data/features/support/matchers.rb +35 -0
- data/features/support/rails.rb +201 -0
- data/features/support/rake/Rakefile +68 -0
- data/features/support/terminal.rb +107 -0
- data/features/user_informer.feature +63 -0
- data/generators/cloudtrapper/airbrake_generator.rb +94 -0
- data/generators/cloudtrapper/lib/insert_commands.rb +34 -0
- data/generators/cloudtrapper/lib/rake_commands.rb +24 -0
- data/generators/cloudtrapper/templates/capistrano_hook.rb +6 -0
- data/generators/cloudtrapper/templates/cloudtrapper_tasks.rake +25 -0
- data/generators/cloudtrapper/templates/initializer.rb +6 -0
- data/install.rb +1 -0
- data/lib/cloudtrapper/backtrace.rb +100 -0
- data/lib/cloudtrapper/capistrano.rb +44 -0
- data/lib/cloudtrapper/configuration.rb +281 -0
- data/lib/cloudtrapper/notice.rb +348 -0
- data/lib/cloudtrapper/rack.rb +55 -0
- data/lib/cloudtrapper/rails/action_controller_catcher.rb +30 -0
- data/lib/cloudtrapper/rails/controller_methods.rb +74 -0
- data/lib/cloudtrapper/rails/error_lookup.rb +33 -0
- data/lib/cloudtrapper/rails/javascript_notifier.rb +48 -0
- data/lib/cloudtrapper/rails/middleware/exceptions_catcher.rb +29 -0
- data/lib/cloudtrapper/rails.rb +40 -0
- data/lib/cloudtrapper/rails3_tasks.rb +85 -0
- data/lib/cloudtrapper/railtie.rb +48 -0
- data/lib/cloudtrapper/rake_handler.rb +66 -0
- data/lib/cloudtrapper/sender.rb +116 -0
- data/lib/cloudtrapper/shared_tasks.rb +36 -0
- data/lib/cloudtrapper/tasks.rb +83 -0
- data/lib/cloudtrapper/user_informer.rb +27 -0
- data/lib/cloudtrapper/version.rb +3 -0
- data/lib/cloudtrapper.rb +155 -0
- data/lib/cloudtrapper_tasks.rb +65 -0
- data/lib/rails/generators/cloudtrapper/cloudtrapper_generator.rb +100 -0
- data/lib/templates/javascript_notifier.erb +15 -0
- data/lib/templates/rescue.erb +91 -0
- data/rails/init.rb +1 -0
- data/resources/README.md +34 -0
- data/resources/ca-bundle.crt +3376 -0
- data/script/integration_test.rb +38 -0
- data/test/backtrace_test.rb +162 -0
- data/test/capistrano_test.rb +34 -0
- data/test/catcher_test.rb +333 -0
- data/test/cloudtrapper_2_2.xsd +78 -0
- data/test/cloudtrapper_tasks_test.rb +170 -0
- data/test/configuration_test.rb +221 -0
- data/test/helper.rb +263 -0
- data/test/javascript_notifier_test.rb +52 -0
- data/test/logger_test.rb +73 -0
- data/test/notice_test.rb +468 -0
- data/test/notifier_test.rb +246 -0
- data/test/rack_test.rb +58 -0
- data/test/rails_initializer_test.rb +36 -0
- data/test/recursion_test.rb +10 -0
- data/test/sender_test.rb +261 -0
- data/test/user_informer_test.rb +29 -0
- metadata +301 -0
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'cloudtrapper'
|
2
|
+
require File.join(File.dirname(__FILE__), 'shared_tasks')
|
3
|
+
|
4
|
+
namespace :cloudtrapper do
|
5
|
+
desc "Verify your gem installation by sending a test exception to the cloudtrapper service"
|
6
|
+
task :test => [:environment] do
|
7
|
+
Rails.logger = defined?(ActiveSupport::TaggedLogging) ?
|
8
|
+
ActiveSupport::TaggedLogging.new(Logger.new(STDOUT)) :
|
9
|
+
Logger.new(STDOUT)
|
10
|
+
|
11
|
+
Rails.logger.level = Logger::DEBUG
|
12
|
+
Cloudtrapper.configure(true) do |config|
|
13
|
+
config.logger = Rails.logger
|
14
|
+
end
|
15
|
+
|
16
|
+
require './app/controllers/application_controller'
|
17
|
+
|
18
|
+
class CloudtrapperTestingException < RuntimeError; end
|
19
|
+
|
20
|
+
unless Cloudtrapper.configuration.api_key
|
21
|
+
puts "Cloudtrapper needs an API key configured! Check the README to see how to add it."
|
22
|
+
exit
|
23
|
+
end
|
24
|
+
|
25
|
+
Cloudtrapper.configuration.development_environments = []
|
26
|
+
|
27
|
+
puts "Configuration:"
|
28
|
+
Cloudtrapper.configuration.to_hash.each do |key, value|
|
29
|
+
puts sprintf("%25s: %s", key.to_s, value.inspect.slice(0, 55))
|
30
|
+
end
|
31
|
+
|
32
|
+
unless defined?(ApplicationController)
|
33
|
+
puts "No ApplicationController found"
|
34
|
+
exit
|
35
|
+
end
|
36
|
+
|
37
|
+
puts 'Setting up the Controller.'
|
38
|
+
class ApplicationController
|
39
|
+
# This is to bypass any filters that may prevent access to the action.
|
40
|
+
prepend_before_filter :test_cloudtrapper
|
41
|
+
def test_cloudtrapper
|
42
|
+
puts "Raising '#{exception_class.name}' to simulate application failure."
|
43
|
+
raise exception_class.new, 'Testing cloudtrapper via "rake cloudtrapper:test". If you can see this, it works.'
|
44
|
+
end
|
45
|
+
|
46
|
+
# def rescue_action(exception)
|
47
|
+
# rescue_action_in_public exception
|
48
|
+
# end
|
49
|
+
|
50
|
+
# Ensure we actually have an action to go to.
|
51
|
+
def verify; end
|
52
|
+
|
53
|
+
# def consider_all_requests_local
|
54
|
+
# false
|
55
|
+
# end
|
56
|
+
|
57
|
+
# def local_request?
|
58
|
+
# false
|
59
|
+
# end
|
60
|
+
|
61
|
+
def exception_class
|
62
|
+
exception_name = ENV['EXCEPTION'] || "CloudtrapperTestingException"
|
63
|
+
Object.const_get(exception_name)
|
64
|
+
rescue
|
65
|
+
Object.const_set(exception_name, Class.new(Exception))
|
66
|
+
end
|
67
|
+
|
68
|
+
def logger
|
69
|
+
nil
|
70
|
+
end
|
71
|
+
end
|
72
|
+
class CloudtrapperVerificationController < ApplicationController; end
|
73
|
+
|
74
|
+
Rails.application.routes_reloader.execute_if_updated
|
75
|
+
Rails.application.routes.draw do
|
76
|
+
match 'verify' => 'application#verify', :as => 'verify'
|
77
|
+
end
|
78
|
+
|
79
|
+
puts 'Processing request.'
|
80
|
+
env = Rack::MockRequest.env_for("/verify")
|
81
|
+
|
82
|
+
Rails.application.call(env)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'cloudtrapper'
|
2
|
+
require 'rails'
|
3
|
+
|
4
|
+
module Cloudtrapper
|
5
|
+
class Railtie < ::Rails::Railtie
|
6
|
+
rake_tasks do
|
7
|
+
require 'cloudtrapper/rake_handler'
|
8
|
+
require "cloudtrapper/rails3_tasks"
|
9
|
+
end
|
10
|
+
|
11
|
+
initializer "cloudtrapper.use_rack_middleware" do |app|
|
12
|
+
app.config.middleware.insert 0, "Cloudtrapper::UserInformer"
|
13
|
+
app.config.middleware.insert_after "Cloudtrapper::UserInformer","Cloudtrapper::Rack"
|
14
|
+
end
|
15
|
+
|
16
|
+
config.after_initialize do
|
17
|
+
Cloudtrapper.configure(true) do |config|
|
18
|
+
config.logger ||= ::Rails.logger
|
19
|
+
config.environment_name ||= ::Rails.env
|
20
|
+
config.project_root ||= ::Rails.root
|
21
|
+
config.framework = "Rails: #{::Rails::VERSION::STRING}"
|
22
|
+
end
|
23
|
+
|
24
|
+
ActiveSupport.on_load(:action_controller) do
|
25
|
+
# Lazily load action_controller methods
|
26
|
+
#
|
27
|
+
require 'cloudtrapper/rails/javascript_notifier'
|
28
|
+
require 'cloudtrapper/rails/controller_methods'
|
29
|
+
|
30
|
+
include Cloudtrapper::Rails::JavascriptNotifier
|
31
|
+
include Cloudtrapper::Rails::ControllerMethods
|
32
|
+
end
|
33
|
+
|
34
|
+
if defined?(::ActionDispatch::DebugExceptions)
|
35
|
+
# We should catch the exceptions in ActionDispatch::DebugExceptions in Rails 3.2.x.
|
36
|
+
#
|
37
|
+
require 'cloudtrapper/rails/middleware/exceptions_catcher'
|
38
|
+
::ActionDispatch::DebugExceptions.send(:include,Cloudtrapper::Rails::Middleware::ExceptionsCatcher)
|
39
|
+
elsif defined?(::ActionDispatch::ShowExceptions)
|
40
|
+
# ActionDispatch::DebugExceptions is not defined in Rails 3.0.x and 3.1.x so
|
41
|
+
# catch the exceptions in ShowExceptions.
|
42
|
+
#
|
43
|
+
require 'cloudtrapper/rails/middleware/exceptions_catcher'
|
44
|
+
::ActionDispatch::ShowExceptions.send(:include,Cloudtrapper::Rails::Middleware::ExceptionsCatcher)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# Patch Rake::Application to handle errors with Cloudtrapper
|
2
|
+
module Cloudtrapper::RakeHandler
|
3
|
+
def self.included(klass)
|
4
|
+
klass.class_eval do
|
5
|
+
include Rake087Methods unless defined?(Rake::VERSION) && Rake::VERSION >= '0.9.0'
|
6
|
+
alias_method :display_error_message_without_cloudtrapper, :display_error_message
|
7
|
+
alias_method :display_error_message, :display_error_message_with_cloudtrapper
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def display_error_message_with_cloudtrapper(ex)
|
12
|
+
if Cloudtrapper.sender && Cloudtrapper.configuration &&
|
13
|
+
(Cloudtrapper.configuration.rescue_rake_exceptions ||
|
14
|
+
(Cloudtrapper.configuration.rescue_rake_exceptions===nil && !self.tty_output?))
|
15
|
+
|
16
|
+
Cloudtrapper.notify_or_ignore(ex, :component => reconstruct_command_line, :cgi_data => ENV)
|
17
|
+
end
|
18
|
+
|
19
|
+
display_error_message_without_cloudtrapper(ex)
|
20
|
+
end
|
21
|
+
|
22
|
+
def reconstruct_command_line
|
23
|
+
"rake #{ARGV.join( ' ' )}"
|
24
|
+
end
|
25
|
+
|
26
|
+
# This module brings Rake 0.8.7 error handling to 0.9.0 standards
|
27
|
+
module Rake087Methods
|
28
|
+
# Method taken from Rake 0.9.0 source
|
29
|
+
#
|
30
|
+
# Provide standard exception handling for the given block.
|
31
|
+
def standard_exception_handling
|
32
|
+
begin
|
33
|
+
yield
|
34
|
+
rescue SystemExit => ex
|
35
|
+
# Exit silently with current status
|
36
|
+
raise
|
37
|
+
rescue OptionParser::InvalidOption => ex
|
38
|
+
$stderr.puts ex.message
|
39
|
+
exit(false)
|
40
|
+
rescue Exception => ex
|
41
|
+
# Exit with error message
|
42
|
+
display_error_message(ex)
|
43
|
+
exit(false)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Method extracted from Rake 0.8.7 source
|
48
|
+
def display_error_message(ex)
|
49
|
+
$stderr.puts "#{name} aborted!"
|
50
|
+
$stderr.puts ex.message
|
51
|
+
if options.trace
|
52
|
+
$stderr.puts ex.backtrace.join("\n")
|
53
|
+
else
|
54
|
+
$stderr.puts ex.backtrace.find {|str| str =~ /#{@rakefile}/ } || ""
|
55
|
+
$stderr.puts "(See full trace by running task with --trace)"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
Rake.application.instance_eval do
|
62
|
+
class << self
|
63
|
+
include Cloudtrapper::RakeHandler
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module Cloudtrapper
|
2
|
+
# Sends out the notice to Cloudtrapper
|
3
|
+
class Sender
|
4
|
+
|
5
|
+
NOTICES_URI = '/api/notices/'.freeze
|
6
|
+
HTTP_ERRORS = [Timeout::Error,
|
7
|
+
Errno::EINVAL,
|
8
|
+
Errno::ECONNRESET,
|
9
|
+
EOFError,
|
10
|
+
Net::HTTPBadResponse,
|
11
|
+
Net::HTTPHeaderSyntaxError,
|
12
|
+
Net::ProtocolError,
|
13
|
+
Errno::ECONNREFUSED].freeze
|
14
|
+
|
15
|
+
def initialize(options = {})
|
16
|
+
[ :proxy_host,
|
17
|
+
:proxy_port,
|
18
|
+
:proxy_user,
|
19
|
+
:proxy_pass,
|
20
|
+
:protocol,
|
21
|
+
:host,
|
22
|
+
:port,
|
23
|
+
:secure,
|
24
|
+
:use_system_ssl_cert_chain,
|
25
|
+
:http_open_timeout,
|
26
|
+
:http_read_timeout
|
27
|
+
].each do |option|
|
28
|
+
instance_variable_set("@#{option}", options[option])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Sends the notice data off to Cloudtrapper for processing.
|
33
|
+
#
|
34
|
+
# @param [String] data The XML notice to be sent off
|
35
|
+
def send_to_cloudtrapper(data)
|
36
|
+
http = setup_http_connection
|
37
|
+
|
38
|
+
response = begin
|
39
|
+
http.post(url.path, data, HEADERS)
|
40
|
+
rescue *HTTP_ERRORS => e
|
41
|
+
log :error, "Unable to contact the Cloudtrapper server. HTTP Error=#{e}"
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
|
45
|
+
case response
|
46
|
+
when Net::HTTPSuccess then
|
47
|
+
log :info, "Success: #{response.class}", response
|
48
|
+
else
|
49
|
+
log :error, "Failure: #{response.class}", response
|
50
|
+
end
|
51
|
+
|
52
|
+
if response && response.respond_to?(:body)
|
53
|
+
error_id = response.body.match(%r{<id[^>]*>(.*?)</id>})
|
54
|
+
error_id[1] if error_id
|
55
|
+
end
|
56
|
+
rescue => e
|
57
|
+
log :error, "[Cloudtrapper::Sender#send_to_cloudtrapper] Cannot send notification. Error: #{e.class} - #{e.message}\nBacktrace:\n#{e.backtrace.join("\n\t")}"
|
58
|
+
nil
|
59
|
+
end
|
60
|
+
|
61
|
+
attr_reader :proxy_host,
|
62
|
+
:proxy_port,
|
63
|
+
:proxy_user,
|
64
|
+
:proxy_pass,
|
65
|
+
:protocol,
|
66
|
+
:host,
|
67
|
+
:port,
|
68
|
+
:secure,
|
69
|
+
:use_system_ssl_cert_chain,
|
70
|
+
:http_open_timeout,
|
71
|
+
:http_read_timeout
|
72
|
+
|
73
|
+
alias_method :secure?, :secure
|
74
|
+
alias_method :use_system_ssl_cert_chain?, :use_system_ssl_cert_chain
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def url
|
79
|
+
URI.parse("#{protocol}://#{host}:#{port}").merge(NOTICES_URI)
|
80
|
+
end
|
81
|
+
|
82
|
+
def log(level, message, response = nil)
|
83
|
+
logger.send level, LOG_PREFIX + message if logger
|
84
|
+
Cloudtrapper.report_environment_info
|
85
|
+
Cloudtrapper.report_response_body(response.body) if response && response.respond_to?(:body)
|
86
|
+
end
|
87
|
+
|
88
|
+
def logger
|
89
|
+
Cloudtrapper.logger
|
90
|
+
end
|
91
|
+
|
92
|
+
def setup_http_connection
|
93
|
+
http =
|
94
|
+
Net::HTTP::Proxy(proxy_host, proxy_port, proxy_user, proxy_pass).
|
95
|
+
new(url.host, url.port)
|
96
|
+
|
97
|
+
http.read_timeout = http_read_timeout
|
98
|
+
http.open_timeout = http_open_timeout
|
99
|
+
|
100
|
+
if secure?
|
101
|
+
http.use_ssl = true
|
102
|
+
|
103
|
+
http.ca_file = Cloudtrapper.configuration.ca_bundle_path
|
104
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
105
|
+
else
|
106
|
+
http.use_ssl = false
|
107
|
+
end
|
108
|
+
|
109
|
+
http
|
110
|
+
rescue => e
|
111
|
+
log :error, "[Cloudtrapper::Sender#setup_http_connection] Failure initializing the HTTP connection.\nError: #{e.class} - #{e.message}\nBacktrace:\n#{e.backtrace.join("\n\t")}"
|
112
|
+
raise e
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
namespace :cloudtrapper do
|
2
|
+
desc "Notify Cloudtrapper of a new deploy."
|
3
|
+
task :deploy => :environment do
|
4
|
+
require 'cloudtrapper_tasks'
|
5
|
+
CloudtrapperTasks.deploy(:rails_env => ENV['TO'],
|
6
|
+
:scm_revision => ENV['REVISION'],
|
7
|
+
:scm_repository => ENV['REPO'],
|
8
|
+
:local_username => ENV['USER'],
|
9
|
+
:api_key => ENV['API_KEY'],
|
10
|
+
:dry_run => ENV['DRY_RUN'])
|
11
|
+
end
|
12
|
+
|
13
|
+
task :log_stdout do
|
14
|
+
require 'logger'
|
15
|
+
RAILS_DEFAULT_LOGGER = Logger.new(STDOUT)
|
16
|
+
end
|
17
|
+
|
18
|
+
namespace :heroku do
|
19
|
+
desc "Install Heroku deploy notifications addon"
|
20
|
+
task :add_deploy_notification => [:environment] do
|
21
|
+
|
22
|
+
def heroku_var(var)
|
23
|
+
`heroku config | grep -E "#{var.upcase}" | awk '{ print $3; }'`.strip
|
24
|
+
end
|
25
|
+
|
26
|
+
heroku_rails_env = heroku_var("rails_env")
|
27
|
+
heroku_api_key = heroku_var("(hoptoad|cloudtrapper)_api_key").split.find {|x| x unless x.blank?} ||
|
28
|
+
Cloudtrapper.configuration.api_key
|
29
|
+
|
30
|
+
command = %Q(heroku addons:add deployhooks:http --url="http://cloudtrapper.io/deploys.txt?deploy[rails_env]=#{heroku_rails_env}&api_key=#{heroku_api_key}")
|
31
|
+
|
32
|
+
puts "\nRunning:\n#{command}\n"
|
33
|
+
puts `#{command}`
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'cloudtrapper'
|
2
|
+
require File.join(File.dirname(__FILE__), 'shared_tasks')
|
3
|
+
|
4
|
+
namespace :cloudtrapper do
|
5
|
+
desc "Verify your gem installation by sending a test exception to the cloudtrapper service"
|
6
|
+
task :test => ['cloudtrapper:log_stdout', :environment] do
|
7
|
+
RAILS_DEFAULT_LOGGER.level = Logger::DEBUG
|
8
|
+
|
9
|
+
require 'action_controller/test_process'
|
10
|
+
|
11
|
+
Dir["app/controllers/application*.rb"].each { |file| require(File.expand_path(file)) }
|
12
|
+
|
13
|
+
class CloudtrapperTestingException < RuntimeError; end
|
14
|
+
|
15
|
+
unless Cloudtrapper.configuration.api_key
|
16
|
+
puts "Cloudtrapper needs an API key configured! Check the README to see how to add it."
|
17
|
+
exit
|
18
|
+
end
|
19
|
+
|
20
|
+
Cloudtrapper.configuration.development_environments = []
|
21
|
+
|
22
|
+
catcher = Cloudtrapper::Rails::ActionControllerCatcher
|
23
|
+
in_controller = ApplicationController.included_modules.include?(catcher)
|
24
|
+
in_base = ActionController::Base.included_modules.include?(catcher)
|
25
|
+
if !in_controller || !in_base
|
26
|
+
puts "Rails initialization did not occur"
|
27
|
+
exit
|
28
|
+
end
|
29
|
+
|
30
|
+
puts "Configuration:"
|
31
|
+
Cloudtrapper.configuration.to_hash.each do |key, value|
|
32
|
+
puts sprintf("%25s: %s", key.to_s, value.inspect.slice(0, 55))
|
33
|
+
end
|
34
|
+
|
35
|
+
unless defined?(ApplicationController)
|
36
|
+
puts "No ApplicationController found"
|
37
|
+
exit
|
38
|
+
end
|
39
|
+
|
40
|
+
puts 'Setting up the Controller.'
|
41
|
+
class ApplicationController
|
42
|
+
# This is to bypass any filters that may prevent access to the action.
|
43
|
+
prepend_before_filter :test_cloudtrapper
|
44
|
+
def test_cloudtrapper
|
45
|
+
puts "Raising '#{exception_class.name}' to simulate application failure."
|
46
|
+
raise exception_class.new, 'Testing cloudtrapper via "rake cloudtrapper:test". If you can see this, it works.'
|
47
|
+
end
|
48
|
+
|
49
|
+
def rescue_action(exception)
|
50
|
+
rescue_action_in_public exception
|
51
|
+
end
|
52
|
+
|
53
|
+
# Ensure we actually have an action to go to.
|
54
|
+
def verify; end
|
55
|
+
|
56
|
+
def consider_all_requests_local
|
57
|
+
false
|
58
|
+
end
|
59
|
+
|
60
|
+
def local_request?
|
61
|
+
false
|
62
|
+
end
|
63
|
+
|
64
|
+
def exception_class
|
65
|
+
exception_name = ENV['EXCEPTION'] || "CloudtrapperTestingException"
|
66
|
+
exception_name.split("::").inject(Object){|klass, name| klass.const_get(name)}
|
67
|
+
rescue
|
68
|
+
Object.const_set(exception_name.gsub(/:+/, "_"), Class.new(Exception))
|
69
|
+
end
|
70
|
+
|
71
|
+
def logger
|
72
|
+
nil
|
73
|
+
end
|
74
|
+
end
|
75
|
+
class CloudtrapperVerificationController < ApplicationController; end
|
76
|
+
|
77
|
+
puts 'Processing request.'
|
78
|
+
request = ActionController::TestRequest.new("REQUEST_URI" => "/cloudtrapper_verification_controller")
|
79
|
+
response = ActionController::TestResponse.new
|
80
|
+
CloudtrapperVerificationController.new.process(request, response)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Cloudtrapper
|
2
|
+
class UserInformer
|
3
|
+
def initialize(app)
|
4
|
+
@app = app
|
5
|
+
end
|
6
|
+
|
7
|
+
def replacement(with)
|
8
|
+
Cloudtrapper.configuration.user_information.gsub(/\{\{\s*error_id\s*\}\}/, with.to_s)
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(env)
|
12
|
+
status, headers, body = @app.call(env)
|
13
|
+
if env['cloudtrapper.error_id'] && Cloudtrapper.configuration.user_information
|
14
|
+
new_body = []
|
15
|
+
replace = replacement(env['cloudtrapper.error_id'])
|
16
|
+
body.each do |chunk|
|
17
|
+
new_body << chunk.gsub("<!-- AIRBRAKE ERROR -->", replace)
|
18
|
+
end
|
19
|
+
body.close if body.respond_to?(:close)
|
20
|
+
headers['Content-Length'] = new_body.sum(&:bytesize).to_s
|
21
|
+
body = new_body
|
22
|
+
end
|
23
|
+
[status, headers, body]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
data/lib/cloudtrapper.rb
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'net/https'
|
3
|
+
require 'rubygems'
|
4
|
+
begin
|
5
|
+
require 'active_support'
|
6
|
+
require 'active_support/core_ext'
|
7
|
+
rescue LoadError
|
8
|
+
require 'activesupport'
|
9
|
+
require 'activesupport/core_ext'
|
10
|
+
end
|
11
|
+
require 'cloudtrapper/version'
|
12
|
+
require 'cloudtrapper/configuration'
|
13
|
+
require 'cloudtrapper/notice'
|
14
|
+
require 'cloudtrapper/sender'
|
15
|
+
require 'cloudtrapper/backtrace'
|
16
|
+
require 'cloudtrapper/rack'
|
17
|
+
require 'cloudtrapper/user_informer'
|
18
|
+
|
19
|
+
require 'cloudtrapper/railtie' if defined?(Rails::Railtie)
|
20
|
+
|
21
|
+
module Cloudtrapper
|
22
|
+
API_VERSION = "2.2"
|
23
|
+
LOG_PREFIX = "** [Cloudtrapper] "
|
24
|
+
|
25
|
+
HEADERS = {
|
26
|
+
'Content-type' => 'text/xml',
|
27
|
+
'Accept' => 'text/xml, application/xml'
|
28
|
+
}
|
29
|
+
|
30
|
+
class << self
|
31
|
+
# The sender object is responsible for delivering formatted data to the Cloudtrapper server.
|
32
|
+
# Must respond to #send_to_cloudtrapper. See Cloudtrapper::Sender.
|
33
|
+
attr_accessor :sender
|
34
|
+
|
35
|
+
# A Cloudtrapper configuration object. Must act like a hash and return sensible
|
36
|
+
# values for all Cloudtrapper configuration options. See Cloudtrapper::Configuration.
|
37
|
+
attr_writer :configuration
|
38
|
+
|
39
|
+
# Tell the log that the Notifier is good to go
|
40
|
+
def report_ready
|
41
|
+
write_verbose_log("Notifier #{VERSION} ready to catch errors")
|
42
|
+
end
|
43
|
+
|
44
|
+
# Prints out the environment info to the log for debugging help
|
45
|
+
def report_environment_info
|
46
|
+
write_verbose_log("Environment Info: #{environment_info}")
|
47
|
+
end
|
48
|
+
|
49
|
+
# Prints out the response body from Cloudtrapper for debugging help
|
50
|
+
def report_response_body(response)
|
51
|
+
write_verbose_log("Response from Cloudtrapper: \n#{response}")
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns the Ruby version, Rails version, and current Rails environment
|
55
|
+
def environment_info
|
56
|
+
info = "[Ruby: #{RUBY_VERSION}]"
|
57
|
+
info << " [#{configuration.framework}]" if configuration.framework
|
58
|
+
info << " [Env: #{configuration.environment_name}]" if configuration.environment_name
|
59
|
+
end
|
60
|
+
|
61
|
+
# Writes out the given message to the #logger
|
62
|
+
def write_verbose_log(message)
|
63
|
+
logger.info LOG_PREFIX + message if logger
|
64
|
+
end
|
65
|
+
|
66
|
+
# Look for the Rails logger currently defined
|
67
|
+
def logger
|
68
|
+
self.configuration.logger
|
69
|
+
end
|
70
|
+
|
71
|
+
# Call this method to modify defaults in your initializers.
|
72
|
+
#
|
73
|
+
# @example
|
74
|
+
# Cloudtrapper.configure do |config|
|
75
|
+
# config.api_key = '1234567890abcdef'
|
76
|
+
# config.secure = false
|
77
|
+
# end
|
78
|
+
def configure(silent = false)
|
79
|
+
yield(configuration)
|
80
|
+
self.sender = Sender.new(configuration)
|
81
|
+
report_ready unless silent
|
82
|
+
self.sender
|
83
|
+
end
|
84
|
+
|
85
|
+
# The configuration object.
|
86
|
+
# @see Cloudtrapper.configure
|
87
|
+
def configuration
|
88
|
+
@configuration ||= Configuration.new
|
89
|
+
end
|
90
|
+
|
91
|
+
# Sends an exception manually using this method, even when you are not in a controller.
|
92
|
+
#
|
93
|
+
# @param [Exception] exception The exception you want to notify Cloudtrapper about.
|
94
|
+
# @param [Hash] opts Data that will be sent to Cloudtrapper.
|
95
|
+
#
|
96
|
+
# @option opts [String] :api_key The API key for this project. The API key is a unique identifier that Cloudtrapper uses for identification.
|
97
|
+
# @option opts [String] :error_message The error returned by the exception (or the message you want to log).
|
98
|
+
# @option opts [String] :backtrace A backtrace, usually obtained with +caller+.
|
99
|
+
# @option opts [String] :rack_env The Rack environment.
|
100
|
+
# @option opts [String] :session The contents of the user's session.
|
101
|
+
# @option opts [String] :environment_name The application environment name.
|
102
|
+
def notify(exception, opts = {})
|
103
|
+
send_notice(build_notice_for(exception, opts))
|
104
|
+
end
|
105
|
+
|
106
|
+
# Sends the notice unless it is one of the default ignored exceptions
|
107
|
+
# @see Cloudtrapper.notify
|
108
|
+
def notify_or_ignore(exception, opts = {})
|
109
|
+
notice = build_notice_for(exception, opts)
|
110
|
+
send_notice(notice) unless notice.ignore?
|
111
|
+
end
|
112
|
+
|
113
|
+
def build_lookup_hash_for(exception, options = {})
|
114
|
+
notice = build_notice_for(exception, options)
|
115
|
+
|
116
|
+
result = {}
|
117
|
+
result[:action] = notice.action rescue nil
|
118
|
+
result[:component] = notice.component rescue nil
|
119
|
+
result[:error_class] = notice.error_class if notice.error_class
|
120
|
+
result[:environment_name] = 'production'
|
121
|
+
|
122
|
+
unless notice.backtrace.lines.empty?
|
123
|
+
result[:file] = notice.backtrace.lines.first.file
|
124
|
+
result[:line_number] = notice.backtrace.lines.first.number
|
125
|
+
end
|
126
|
+
|
127
|
+
result
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def send_notice(notice)
|
133
|
+
if configuration.public?
|
134
|
+
sender.send_to_cloudtrapper(notice.to_xml)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def build_notice_for(exception, opts = {})
|
139
|
+
exception = unwrap_exception(exception)
|
140
|
+
opts = opts.merge(:exception => exception) if exception.is_a?(Exception)
|
141
|
+
opts = opts.merge(exception.to_hash) if exception.respond_to?(:to_hash)
|
142
|
+
Notice.new(configuration.merge(opts))
|
143
|
+
end
|
144
|
+
|
145
|
+
def unwrap_exception(exception)
|
146
|
+
if exception.respond_to?(:original_exception)
|
147
|
+
exception.original_exception
|
148
|
+
elsif exception.respond_to?(:continued_exception)
|
149
|
+
exception.continued_exception
|
150
|
+
else
|
151
|
+
exception
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'uri'
|
3
|
+
require 'active_support'
|
4
|
+
|
5
|
+
# Capistrano tasks for notifying Cloudtrapper of deploys
|
6
|
+
module CloudtrapperTasks
|
7
|
+
|
8
|
+
# Alerts Cloudtrapper of a deploy.
|
9
|
+
#
|
10
|
+
# @param [Hash] opts Data about the deploy that is set to Cloudtrapper
|
11
|
+
#
|
12
|
+
# @option opts [String] :api_key Api key of you Cloudtrapper application
|
13
|
+
# @option opts [String] :rails_env Environment of the deploy (production, staging)
|
14
|
+
# @option opts [String] :scm_revision The given revision/sha that is being deployed
|
15
|
+
# @option opts [String] :scm_repository Address of your repository to help with code lookups
|
16
|
+
# @option opts [String] :local_username Who is deploying
|
17
|
+
def self.deploy(opts = {})
|
18
|
+
api_key = opts.delete(:api_key) || Cloudtrapper.configuration.api_key
|
19
|
+
if api_key.blank?
|
20
|
+
puts "I don't seem to be configured with an API key. Please check your configuration."
|
21
|
+
return false
|
22
|
+
end
|
23
|
+
|
24
|
+
if opts[:rails_env].blank?
|
25
|
+
puts "I don't know to which Rails environment you are deploying (use the TO=production option)."
|
26
|
+
return false
|
27
|
+
end
|
28
|
+
|
29
|
+
dry_run = opts.delete(:dry_run)
|
30
|
+
params = {'api_key' => api_key}
|
31
|
+
opts.each {|k,v| params["deploy[#{k}]"] = v }
|
32
|
+
|
33
|
+
host = Cloudtrapper.configuration.host || 'api.cloudtrapper.io'
|
34
|
+
port = Cloudtrapper.configuration.port
|
35
|
+
|
36
|
+
proxy = Net::HTTP.Proxy(Cloudtrapper.configuration.proxy_host,
|
37
|
+
Cloudtrapper.configuration.proxy_port,
|
38
|
+
Cloudtrapper.configuration.proxy_user,
|
39
|
+
Cloudtrapper.configuration.proxy_pass)
|
40
|
+
http = proxy.new(host, port)
|
41
|
+
|
42
|
+
|
43
|
+
|
44
|
+
# Handle Security
|
45
|
+
if Cloudtrapper.configuration.secure?
|
46
|
+
http.use_ssl = true
|
47
|
+
http.ca_file = Cloudtrapper.configuration.ca_bundle_path
|
48
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
49
|
+
end
|
50
|
+
|
51
|
+
post = Net::HTTP::Post.new("/deploys.txt")
|
52
|
+
post.set_form_data(params)
|
53
|
+
|
54
|
+
if dry_run
|
55
|
+
puts http.inspect, params.inspect
|
56
|
+
return true
|
57
|
+
else
|
58
|
+
response = http.request(post)
|
59
|
+
|
60
|
+
puts response.body
|
61
|
+
return Net::HTTPSuccess === response
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|