cloudtrapper 0.0.2.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. data/CHANGELOG +823 -0
  2. data/Gemfile +12 -0
  3. data/Guardfile +6 -0
  4. data/INSTALL +20 -0
  5. data/MIT-LICENSE +22 -0
  6. data/README.md +465 -0
  7. data/README_FOR_HEROKU_ADDON.md +94 -0
  8. data/Rakefile +223 -0
  9. data/SUPPORTED_RAILS_VERSIONS +23 -0
  10. data/TESTING.md +33 -0
  11. data/cloudtrapper.gemspec +35 -0
  12. data/features/metal.feature +18 -0
  13. data/features/rack.feature +56 -0
  14. data/features/rails.feature +211 -0
  15. data/features/rails_with_js_notifier.feature +97 -0
  16. data/features/rake.feature +27 -0
  17. data/features/sinatra.feature +29 -0
  18. data/features/step_definitions/file_steps.rb +10 -0
  19. data/features/step_definitions/metal_steps.rb +23 -0
  20. data/features/step_definitions/rack_steps.rb +23 -0
  21. data/features/step_definitions/rails_application_steps.rb +433 -0
  22. data/features/step_definitions/rake_steps.rb +17 -0
  23. data/features/support/airbrake_shim.rb.template +11 -0
  24. data/features/support/env.rb +18 -0
  25. data/features/support/matchers.rb +35 -0
  26. data/features/support/rails.rb +201 -0
  27. data/features/support/rake/Rakefile +68 -0
  28. data/features/support/terminal.rb +107 -0
  29. data/features/user_informer.feature +63 -0
  30. data/generators/cloudtrapper/airbrake_generator.rb +94 -0
  31. data/generators/cloudtrapper/lib/insert_commands.rb +34 -0
  32. data/generators/cloudtrapper/lib/rake_commands.rb +24 -0
  33. data/generators/cloudtrapper/templates/capistrano_hook.rb +6 -0
  34. data/generators/cloudtrapper/templates/cloudtrapper_tasks.rake +25 -0
  35. data/generators/cloudtrapper/templates/initializer.rb +6 -0
  36. data/install.rb +1 -0
  37. data/lib/cloudtrapper/backtrace.rb +100 -0
  38. data/lib/cloudtrapper/capistrano.rb +44 -0
  39. data/lib/cloudtrapper/configuration.rb +281 -0
  40. data/lib/cloudtrapper/notice.rb +348 -0
  41. data/lib/cloudtrapper/rack.rb +55 -0
  42. data/lib/cloudtrapper/rails/action_controller_catcher.rb +30 -0
  43. data/lib/cloudtrapper/rails/controller_methods.rb +74 -0
  44. data/lib/cloudtrapper/rails/error_lookup.rb +33 -0
  45. data/lib/cloudtrapper/rails/javascript_notifier.rb +48 -0
  46. data/lib/cloudtrapper/rails/middleware/exceptions_catcher.rb +29 -0
  47. data/lib/cloudtrapper/rails.rb +40 -0
  48. data/lib/cloudtrapper/rails3_tasks.rb +85 -0
  49. data/lib/cloudtrapper/railtie.rb +48 -0
  50. data/lib/cloudtrapper/rake_handler.rb +66 -0
  51. data/lib/cloudtrapper/sender.rb +116 -0
  52. data/lib/cloudtrapper/shared_tasks.rb +36 -0
  53. data/lib/cloudtrapper/tasks.rb +83 -0
  54. data/lib/cloudtrapper/user_informer.rb +27 -0
  55. data/lib/cloudtrapper/version.rb +3 -0
  56. data/lib/cloudtrapper.rb +155 -0
  57. data/lib/cloudtrapper_tasks.rb +65 -0
  58. data/lib/rails/generators/cloudtrapper/cloudtrapper_generator.rb +100 -0
  59. data/lib/templates/javascript_notifier.erb +15 -0
  60. data/lib/templates/rescue.erb +91 -0
  61. data/rails/init.rb +1 -0
  62. data/resources/README.md +34 -0
  63. data/resources/ca-bundle.crt +3376 -0
  64. data/script/integration_test.rb +38 -0
  65. data/test/backtrace_test.rb +162 -0
  66. data/test/capistrano_test.rb +34 -0
  67. data/test/catcher_test.rb +333 -0
  68. data/test/cloudtrapper_2_2.xsd +78 -0
  69. data/test/cloudtrapper_tasks_test.rb +170 -0
  70. data/test/configuration_test.rb +221 -0
  71. data/test/helper.rb +263 -0
  72. data/test/javascript_notifier_test.rb +52 -0
  73. data/test/logger_test.rb +73 -0
  74. data/test/notice_test.rb +468 -0
  75. data/test/notifier_test.rb +246 -0
  76. data/test/rack_test.rb +58 -0
  77. data/test/rails_initializer_test.rb +36 -0
  78. data/test/recursion_test.rb +10 -0
  79. data/test/sender_test.rb +261 -0
  80. data/test/user_informer_test.rb +29 -0
  81. 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
+
@@ -0,0 +1,3 @@
1
+ module Cloudtrapper
2
+ VERSION = "0.0.2.pre".freeze
3
+ end
@@ -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
+