airbrake 3.1.6 → 3.1.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. data/CHANGELOG +116 -0
  2. data/LICENSE +61 -0
  3. data/README.md +28 -478
  4. data/README_FOR_HEROKU_ADDON.md +13 -5
  5. data/Rakefile +26 -103
  6. data/TESTED_AGAINST +6 -0
  7. data/airbrake.gemspec +13 -14
  8. data/features/metal.feature +27 -12
  9. data/features/rack.feature +59 -59
  10. data/features/rails.feature +79 -100
  11. data/features/rails_with_js_notifier.feature +9 -21
  12. data/features/rake.feature +6 -4
  13. data/features/sinatra.feature +32 -6
  14. data/features/step_definitions/file_steps.rb +4 -0
  15. data/features/step_definitions/rack_steps.rb +9 -5
  16. data/features/step_definitions/rails_application_steps.rb +46 -278
  17. data/features/step_definitions/rake_steps.rb +16 -11
  18. data/features/support/airbrake_shim.rb.template +0 -2
  19. data/features/support/aruba.rb +5 -0
  20. data/features/support/env.rb +12 -6
  21. data/features/support/rails.rb +30 -73
  22. data/features/support/rake/Rakefile +1 -2
  23. data/features/user_informer.feature +12 -19
  24. data/generators/airbrake/airbrake_generator.rb +1 -1
  25. data/lib/airbrake.rb +9 -8
  26. data/lib/airbrake/cli/project_factory.rb +6 -3
  27. data/lib/airbrake/configuration.rb +7 -0
  28. data/lib/airbrake/notice.rb +52 -3
  29. data/lib/airbrake/rack.rb +16 -7
  30. data/lib/airbrake/rails/action_controller_catcher.rb +5 -3
  31. data/lib/airbrake/rails/controller_methods.rb +6 -6
  32. data/lib/airbrake/rails/error_lookup.rb +4 -2
  33. data/lib/airbrake/rails/javascript_notifier.rb +7 -3
  34. data/lib/airbrake/rails/middleware.rb +65 -0
  35. data/lib/airbrake/rails3_tasks.rb +16 -21
  36. data/lib/airbrake/railtie.rb +9 -16
  37. data/lib/airbrake/rake_handler.rb +2 -2
  38. data/lib/airbrake/sender.rb +57 -6
  39. data/lib/airbrake/shared_tasks.rb +24 -11
  40. data/lib/airbrake/sinatra.rb +34 -0
  41. data/lib/airbrake/user_informer.rb +9 -0
  42. data/lib/airbrake/version.rb +1 -1
  43. data/lib/rails/generators/airbrake/airbrake_generator.rb +10 -10
  44. data/{test/airbrake_2_3.xsd → resources/airbrake_2_4.xsd} +1 -0
  45. data/resources/airbrake_3_0.json +52 -0
  46. data/test/catcher_test.rb +198 -240
  47. data/test/configuration_test.rb +2 -0
  48. data/test/helper.rb +123 -53
  49. data/test/notice_test.rb +45 -1
  50. data/test/sender_test.rb +63 -32
  51. metadata +60 -79
  52. data/MIT-LICENSE +0 -22
  53. data/SUPPORTED_RAILS_VERSIONS +0 -38
  54. data/TESTING.md +0 -41
  55. data/features/step_definitions/metal_steps.rb +0 -23
  56. data/features/support/terminal.rb +0 -107
  57. data/lib/airbrake/extensions/blank.rb +0 -73
  58. data/lib/airbrake/rails/middleware/exceptions_catcher.rb +0 -33
data/lib/airbrake/rack.rb CHANGED
@@ -22,6 +22,7 @@ module Airbrake
22
22
  class Rack
23
23
  def initialize(app)
24
24
  @app = app
25
+ Airbrake.configuration.framework = "Rack: #{::Rack.release}"
25
26
  end
26
27
 
27
28
  def ignored_user_agent?(env)
@@ -32,23 +33,31 @@ module Airbrake
32
33
  any? { |ua| ua === env['HTTP_USER_AGENT'] }
33
34
  end
34
35
 
35
- def notify_airbrake(exception,env)
36
- Airbrake.notify_or_ignore(exception,:rack_env => env) unless ignored_user_agent?(env)
36
+ def notify_airbrake(exception, env)
37
+ unless ignored_user_agent?(env)
38
+ Airbrake.notify_or_ignore(exception, :rack_env => env)
39
+ end
37
40
  end
38
41
 
39
42
  def call(env)
43
+ @env = env
40
44
  begin
41
- response = @app.call(env)
45
+ response = @app.call(@env)
42
46
  rescue Exception => raised
43
- env['airbrake.error_id'] = notify_airbrake(raised,env)
44
- raise
47
+ @env['airbrake.error_id'] = notify_airbrake(raised, @env)
48
+ raise raised
45
49
  end
46
50
 
47
- if env['rack.exception']
48
- env['airbrake.error_id'] = notify_airbrake(env['rack.exception'],env)
51
+ if framework_exception
52
+ @env['airbrake.error_id'] = notify_airbrake(framework_exception, @env)
49
53
  end
50
54
 
51
55
  response
52
56
  end
57
+
58
+ def framework_exception
59
+ @env['rack.exception']
60
+ end
61
+
53
62
  end
54
63
  end
@@ -2,10 +2,12 @@ module Airbrake
2
2
  module Rails
3
3
  module ActionControllerCatcher
4
4
 
5
- # Sets up an alias chain to catch exceptions when Rails does
5
+ # Sets up an alias chain to catch exceptions for Rails 2
6
6
  def self.included(base) #:nodoc:
7
- base.send(:alias_method, :rescue_action_in_public_without_airbrake, :rescue_action_in_public)
8
- base.send(:alias_method, :rescue_action_in_public, :rescue_action_in_public_with_airbrake)
7
+ if base.method_defined?(:rescue_action_in_public)
8
+ base.send(:alias_method, :rescue_action_in_public_without_airbrake, :rescue_action_in_public)
9
+ base.send(:alias_method, :rescue_action_in_public, :rescue_action_in_public_with_airbrake)
10
+ end
9
11
  end
10
12
 
11
13
  private
@@ -20,7 +20,7 @@ module Airbrake
20
20
  # inside the controller. Otherwise it works like Airbrake.notify.
21
21
  def notify_airbrake(hash_or_exception)
22
22
  unless airbrake_local_request?
23
- Airbrake.notify(hash_or_exception, airbrake_request_data)
23
+ Airbrake.notify_or_ignore(hash_or_exception, airbrake_request_data)
24
24
  end
25
25
  end
26
26
 
@@ -74,11 +74,11 @@ module Airbrake
74
74
 
75
75
  def airbrake_current_user
76
76
  user = begin current_user rescue current_member end
77
- user.attributes.select do |k, v|
78
- Airbrake.configuration.
79
- user_attributes.map(&:to_sym).
80
- include? k.to_sym unless v.blank?
81
- end.symbolize_keys
77
+ h = {}
78
+ Airbrake.configuration.user_attributes.map(&:to_sym).each do |attr|
79
+ h[attr.to_sym] = user.send(attr) if user.respond_to? attr
80
+ end
81
+ h
82
82
  rescue NoMethodError, NameError
83
83
  {}
84
84
  end
@@ -4,8 +4,10 @@ module Airbrake
4
4
 
5
5
  # Sets up an alias chain to catch exceptions when Rails does
6
6
  def self.included(base) #:nodoc:
7
- base.send(:alias_method, :rescue_action_locally_without_airbrake, :rescue_action_locally)
8
- base.send(:alias_method, :rescue_action_locally, :rescue_action_locally_with_airbrake)
7
+ if base.method_defined?(:rescue_action_locally)
8
+ base.send(:alias_method, :rescue_action_locally_without_airbrake, :rescue_action_locally)
9
+ base.send(:alias_method, :rescue_action_locally, :rescue_action_locally_with_airbrake)
10
+ end
9
11
  end
10
12
 
11
13
  private
@@ -7,9 +7,7 @@ module Airbrake
7
7
 
8
8
  private
9
9
 
10
- def airbrake_javascript_notifier
11
- return unless Airbrake.configuration.public?
12
-
10
+ def airbrake_javascript_notifier_options
13
11
  path = File.join File.dirname(__FILE__), '..', '..', 'templates', 'javascript_notifier.erb'
14
12
  host = Airbrake.configuration.host.dup
15
13
  port = Airbrake.configuration.port
@@ -28,6 +26,12 @@ module Airbrake
28
26
  :url => request.url
29
27
  }
30
28
  }
29
+ end
30
+
31
+ def airbrake_javascript_notifier
32
+ return unless Airbrake.configuration.public?
33
+
34
+ options = airbrake_javascript_notifier_options
31
35
 
32
36
  res = if @template
33
37
  @template.render(options)
@@ -0,0 +1,65 @@
1
+ module Airbrake
2
+ module Rails
3
+ # Rack middleware for Rails applications. Any errors raised by the upstream
4
+ # application will be delivered to Airbrake and re-raised.
5
+ #
6
+ class Middleware
7
+ def initialize(app)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ @env = env
13
+
14
+ begin
15
+ response = @app.call(@env)
16
+ rescue Exception => exception
17
+ @env['airbrake.error_id'] = notify_airbrake(exception)
18
+ raise exception
19
+ end
20
+
21
+ if framework_exception
22
+ @env["airbrake.error_id"] = notify_airbrake(framework_exception)
23
+ end
24
+
25
+ response
26
+ end
27
+
28
+ private
29
+
30
+ def controller
31
+ @env["action_controller.instance"]
32
+ end
33
+
34
+ def after_airbrake_handler(exception)
35
+ if defined?(controller.rescue_action_in_public_without_airbrake)
36
+ controller.rescue_action_in_public_without_airbrake(exception)
37
+ end
38
+ end
39
+
40
+ def notify_airbrake(exception)
41
+ unless ignored_user_agent?
42
+ error_id = Airbrake.notify_or_ignore(exception, request_data)
43
+ after_airbrake_handler(exception)
44
+ error_id
45
+ end
46
+ end
47
+
48
+ def request_data
49
+ controller.try(:airbrake_request_data) || {:rack_env => @env}
50
+ end
51
+
52
+ def ignored_user_agent?
53
+ true if Airbrake.
54
+ configuration.
55
+ ignore_user_agent.
56
+ flatten.
57
+ any? { |ua| ua === @env['HTTP_USER_AGENT'] }
58
+ end
59
+
60
+ def framework_exception
61
+ @env["action_dispatch.exception"]
62
+ end
63
+ end
64
+ end
65
+ end
@@ -20,20 +20,28 @@ namespace :airbrake do
20
20
  end
21
21
  end
22
22
 
23
+ # Sets up verbose logging
23
24
  Rails.logger.level = Logger::DEBUG
24
25
  Airbrake.configure(true) do |config|
25
26
  config.logger = Rails.logger
26
27
  end
27
28
 
29
+ # Override Rails exception middleware, so we stop cluttering STDOUT
30
+ class ActionDispatch::DebugExceptions; def call(env); @app.call(env); end; end
31
+ class ActionDispatch::ShowExceptions; def call(env); @app.call(env); end; end
32
+
28
33
  require './app/controllers/application_controller'
29
34
 
30
35
  class AirbrakeTestingException < RuntimeError; end
31
36
 
37
+ # Checks if api_key is set
32
38
  unless Airbrake.configuration.api_key
33
39
  puts "Airbrake needs an API key configured! Check the README to see how to add it."
34
40
  exit
35
41
  end
36
42
 
43
+ # Enables Airbrake reporting on all environments,
44
+ # so we don't have to worry about invoking the task in production
37
45
  Airbrake.configuration.development_environments = []
38
46
 
39
47
  puts "Configuration:"
@@ -52,44 +60,31 @@ namespace :airbrake do
52
60
  prepend_before_filter :test_airbrake
53
61
  def test_airbrake
54
62
  puts "Raising '#{exception_class.name}' to simulate application failure."
55
- raise exception_class.new, 'Testing airbrake via "rake airbrake:test". If you can see this, it works.'
63
+ raise exception_class.new, "\nTesting airbrake via \"rake airbrake:test\"."\
64
+ " If you can see this, it works."
56
65
  end
57
66
 
58
- # def rescue_action(exception)
59
- # rescue_action_in_public exception
60
- # end
61
-
62
67
  # Ensure we actually have an action to go to.
63
68
  def verify; end
64
69
 
65
- # def consider_all_requests_local
66
- # false
67
- # end
68
-
69
- # def local_request?
70
- # false
71
- # end
72
-
73
70
  def exception_class
74
71
  exception_name = ENV['EXCEPTION'] || "AirbrakeTestingException"
75
72
  Object.const_get(exception_name)
76
73
  rescue
77
74
  Object.const_set(exception_name, Class.new(Exception))
78
75
  end
79
-
80
- def logger
81
- nil
82
- end
83
76
  end
84
- class AirbrakeVerificationController < ApplicationController; end
85
77
 
86
- Rails.application.routes_reloader.execute_if_updated
87
78
  Rails.application.routes.draw do
88
- match 'verify' => 'application#verify', :as => 'verify'
79
+ get 'verify' => 'application#verify', :as => 'verify'
89
80
  end
90
81
 
91
82
  puts 'Processing request.'
92
- env = Rack::MockRequest.env_for("/verify")
83
+
84
+ config = Rails.application.config
85
+ protocol = (config.respond_to?(:force_ssl) && config.force_ssl) ? 'https' : 'http'
86
+
87
+ env = Rack::MockRequest.env_for("#{protocol}://www.example.com/verify")
93
88
 
94
89
  Rails.application.call(env)
95
90
 
@@ -1,16 +1,18 @@
1
1
  require 'airbrake'
2
2
  require 'rails'
3
3
 
4
+ require 'airbrake/rails/middleware'
5
+
4
6
  module Airbrake
5
7
  class Railtie < ::Rails::Railtie
6
8
  rake_tasks do
7
9
  require 'airbrake/rake_handler'
8
- require "airbrake/rails3_tasks"
10
+ require 'airbrake/rails3_tasks'
9
11
  end
10
12
 
11
- initializer "airbrake.use_rack_middleware" do |app|
13
+ initializer "airbrake.middleware" do |app|
14
+ app.config.middleware.use "Airbrake::Rails::Middleware"
12
15
  app.config.middleware.insert 0, "Airbrake::UserInformer"
13
- app.config.middleware.insert_after "Airbrake::UserInformer","Airbrake::Rack"
14
16
  end
15
17
 
16
18
  config.after_initialize do
@@ -24,24 +26,15 @@ module Airbrake
24
26
  ActiveSupport.on_load(:action_controller) do
25
27
  # Lazily load action_controller methods
26
28
  #
27
- require 'airbrake/rails/javascript_notifier'
28
29
  require 'airbrake/rails/controller_methods'
29
30
 
30
- include Airbrake::Rails::JavascriptNotifier
31
31
  include Airbrake::Rails::ControllerMethods
32
32
  end
33
33
 
34
- if defined?(::ActionDispatch::DebugExceptions)
35
- # We should catch the exceptions in ActionDispatch::DebugExceptions in Rails 3.2.x.
36
- #
37
- require 'airbrake/rails/middleware/exceptions_catcher'
38
- ::ActionDispatch::DebugExceptions.send(:include,Airbrake::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 'airbrake/rails/middleware/exceptions_catcher'
44
- ::ActionDispatch::ShowExceptions.send(:include,Airbrake::Rails::Middleware::ExceptionsCatcher)
34
+ if defined?(::ActionController::Base)
35
+ require 'airbrake/rails/javascript_notifier'
36
+
37
+ ::ActionController::Base.send(:include, Airbrake::Rails::JavascriptNotifier)
45
38
  end
46
39
  end
47
40
  end
@@ -13,14 +13,14 @@ module Airbrake::RakeHandler
13
13
  (Airbrake.configuration.rescue_rake_exceptions ||
14
14
  (Airbrake.configuration.rescue_rake_exceptions===nil && !self.tty_output?))
15
15
 
16
- Airbrake.notify_or_ignore(ex, :component => reconstruct_command_line, :cgi_data => environment_info)
16
+ Airbrake.notify_or_ignore(ex, :component => 'rake', :action => reconstruct_command_line, :cgi_data => environment_info)
17
17
  end
18
18
 
19
19
  display_error_message_without_airbrake(ex)
20
20
  end
21
21
 
22
22
  def reconstruct_command_line
23
- "rake #{ARGV.join( ' ' )}"
23
+ ARGV.join( ' ' )
24
24
  end
25
25
 
26
26
  def environment_info
@@ -2,7 +2,17 @@ module Airbrake
2
2
  # Sends out the notice to Airbrake
3
3
  class Sender
4
4
 
5
- NOTICES_URI = '/notifier_api/v2/notices/'.freeze
5
+ NOTICES_URI = '/notifier_api/v2/notices'.freeze
6
+ HEADERS = {
7
+ :xml => {
8
+ 'Content-type' => 'text/xml',
9
+ 'Accept' => 'text/xml, application/xml'
10
+ },:json => {
11
+ 'Content-Type' => 'application/json',
12
+ 'Accept' => 'application/json'
13
+ }}
14
+
15
+ JSON_API_URI = '/api/v3/projects'.freeze
6
16
  HTTP_ERRORS = [Timeout::Error,
7
17
  Errno::EINVAL,
8
18
  Errno::ECONNRESET,
@@ -23,21 +33,26 @@ module Airbrake
23
33
  :secure,
24
34
  :use_system_ssl_cert_chain,
25
35
  :http_open_timeout,
26
- :http_read_timeout
36
+ :http_read_timeout,
37
+ :project_id,
38
+ :api_key
27
39
  ].each do |option|
28
40
  instance_variable_set("@#{option}", options[option])
29
41
  end
30
42
  end
31
43
 
44
+
32
45
  # Sends the notice data off to Airbrake for processing.
33
46
  #
34
47
  # @param [Notice or String] notice The notice to be sent off
35
48
  def send_to_airbrake(notice)
36
- data = notice.respond_to?(:to_xml) ? notice.to_xml : notice
49
+ data = prepare_notice(notice)
37
50
  http = setup_http_connection
38
51
 
39
52
  response = begin
40
- http.post(url.path, data, HEADERS)
53
+ http.post(url.respond_to?(:path) ? url.path : url,
54
+ data,
55
+ headers)
41
56
  rescue *HTTP_ERRORS => e
42
57
  log :level => :error,
43
58
  :message => "Unable to contact the Airbrake server. HTTP Error=#{e}"
@@ -78,15 +93,46 @@ module Airbrake
78
93
  :secure,
79
94
  :use_system_ssl_cert_chain,
80
95
  :http_open_timeout,
81
- :http_read_timeout
96
+ :http_read_timeout,
97
+ :project_id,
98
+ :api_key
82
99
 
83
100
  alias_method :secure?, :secure
84
101
  alias_method :use_system_ssl_cert_chain?, :use_system_ssl_cert_chain
85
102
 
86
103
  private
87
104
 
105
+ def prepare_notice(notice)
106
+ if json_api_enabled?
107
+ begin
108
+ JSON.parse(notice)
109
+ notice
110
+ rescue
111
+ notice.to_json
112
+ end
113
+ else
114
+ notice.respond_to?(:to_xml) ? notice.to_xml : notice
115
+ end
116
+ end
117
+
118
+ def api_url
119
+ if json_api_enabled?
120
+ "#{JSON_API_URI}/#{project_id}/notices?key=#{api_key}"
121
+ else
122
+ NOTICES_URI
123
+ end
124
+ end
125
+
126
+ def headers
127
+ if json_api_enabled?
128
+ HEADERS[:json]
129
+ else
130
+ HEADERS[:xml]
131
+ end
132
+ end
133
+
88
134
  def url
89
- URI.parse("#{protocol}://#{host}:#{port}").merge(NOTICES_URI)
135
+ URI.parse("#{protocol}://#{host}:#{port}").merge(api_url)
90
136
  end
91
137
 
92
138
  def log(opts = {})
@@ -124,5 +170,10 @@ module Airbrake
124
170
  "Error: #{e.class} - #{e.message}\nBacktrace:\n#{e.backtrace.join("\n\t")}"
125
171
  raise e
126
172
  end
173
+
174
+ def json_api_enabled?
175
+ !!(host =~ /collect.airbrake.io/) &&
176
+ project_id.present?
177
+ end
127
178
  end
128
179
  end