airbrake 3.1.6 → 3.1.7

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.
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