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
@@ -1,17 +1,22 @@
1
+ Given /I've prepared the Rakefile/ do
2
+ rakefile = File.join(PROJECT_ROOT, 'features', 'support', 'rake', 'Rakefile')
3
+ target = File.join(TEMP_DIR, 'Rakefile')
4
+ FileUtils.cp(rakefile, target)
5
+ end
6
+
1
7
  When /I run rake with (.+)/ do |command|
2
- @rake_command = "rake #{command.gsub(' ','_')}"
3
- @rake_result = `cd features/support/rake && GEM_HOME=#{BUILT_GEM_ROOT} #{@rake_command} 2>&1`
8
+ command = "rake #{command.gsub(' ','_')}"
9
+ step %{I run `#{command}`}
10
+ end
11
+
12
+ Then "Airbrake should not catch the exception" do
13
+ step %{I should not see "[airbrake]"}
4
14
  end
5
15
 
6
- Then /Airbrake should (|not) ?catch the exception/ do |condition|
7
- if condition=='not'
8
- @rake_result.should_not =~ /^airbrake/
9
- else
10
- @rake_result.should =~ /^airbrake/
11
- end
16
+ Then "Airbrake should catch the exception" do
17
+ step %{I should see "[airbrake]"}
12
18
  end
13
19
 
14
- Then /Airbrake should send the rake command line as the component name/ do
15
- component = @rake_result.match(/^airbrake (.*)$/)[1]
16
- component.should == @rake_command
20
+ Then /^command "(.*?)" should be reported$/ do |command_name|
21
+ step %{the output should contain "[airbrake] rake #{command_name}"}
17
22
  end
@@ -1,7 +1,5 @@
1
1
  require 'sham_rack'
2
2
 
3
- Airbrake.configuration.logger = Logger.new STDOUT if defined?(Airbrake)
4
-
5
3
  ShamRack.at("api.airbrake.io") do |env|
6
4
  response = <<-end_xml
7
5
  <notice>
@@ -0,0 +1,5 @@
1
+ Before do
2
+ @aruba_timeout_seconds = 30
3
+ @aruba_io_wait_seconds = 5
4
+ @dirs = ["tmp"]
5
+ end
@@ -1,18 +1,24 @@
1
1
  require 'active_support'
2
2
  require 'nokogiri'
3
3
  require 'rspec'
4
+ require "aruba/cucumber"
4
5
 
5
6
  PROJECT_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..', '..')).freeze
6
7
  TEMP_DIR = File.join(PROJECT_ROOT, 'tmp').freeze
7
8
  LOCAL_RAILS_ROOT = File.join(TEMP_DIR, 'rails_root').freeze
8
- BUILT_GEM_ROOT = File.join(TEMP_DIR, 'built_gems').freeze
9
- LOCAL_GEM_ROOT = File.join(TEMP_DIR, 'local_gems').freeze
10
9
  RACK_FILE = File.join(TEMP_DIR, 'rack_app.rb').freeze
11
10
 
12
11
  Before do
13
- FileUtils.mkdir_p(TEMP_DIR)
14
- FileUtils.rm_rf(BUILT_GEM_ROOT)
15
12
  FileUtils.rm_rf(LOCAL_RAILS_ROOT)
16
- FileUtils.rm_f(RACK_FILE)
17
- FileUtils.mkdir_p(BUILT_GEM_ROOT)
18
13
  end
14
+
15
+ When /^I reset Bundler environment variable$/ do
16
+ BUNDLE_ENV_VARS.each do |key|
17
+ ENV[key] = nil
18
+ end
19
+ end
20
+
21
+ def prepend_path(path)
22
+ ENV['PATH'] = path + ":" + ENV['PATH']
23
+ end
24
+
@@ -1,3 +1,20 @@
1
+ BUNDLE_ENV_VARS = %w(RUBYOPT BUNDLE_PATH BUNDLE_BIN_PATH BUNDLE_GEMFILE)
2
+ ORIGINAL_BUNDLE_VARS = Hash[ENV.select{ |key,value| BUNDLE_ENV_VARS.include?(key) }]
3
+
4
+ ENV['RAILS_ENV'] = 'test'
5
+
6
+ Before do
7
+ ENV['BUNDLE_GEMFILE'] = File.join(Dir.pwd, ENV['BUNDLE_GEMFILE']) unless ENV['BUNDLE_GEMFILE'].start_with?(Dir.pwd)
8
+ @framework_version = nil
9
+ end
10
+
11
+ After do |s|
12
+ ORIGINAL_BUNDLE_VARS.each_pair do |key, value|
13
+ ENV[key] = value
14
+ end
15
+ Cucumber.wants_to_quit = true if s.failed?
16
+ end
17
+
1
18
  module RailsHelpers
2
19
  def rails_root_exists?
3
20
  File.exists?(environment_path)
@@ -64,14 +81,6 @@ module RailsHelpers
64
81
  File.join(rails_root, 'Rakefile')
65
82
  end
66
83
 
67
- def bundle_gem(gem_name, version = nil)
68
- File.open(gemfile_path, 'a') do |file|
69
- gem = "gem '#{gem_name}'"
70
- gem += ", '#{version}'" if version
71
- file.puts(gem)
72
- end
73
- end
74
-
75
84
  def config_gem(gem_name, version = nil)
76
85
  run = "Rails::Initializer.run do |config|"
77
86
  insert = " config.gem '#{gem_name}'"
@@ -113,8 +122,7 @@ module RailsHelpers
113
122
  File.open(rakefile_path, 'wb') { |file| file.write(content) }
114
123
  end
115
124
 
116
- def perform_request(uri, environment = 'production')
117
- if rails3?
125
+ def perform_request(uri, environment = 'production')
118
126
  request_script = <<-SCRIPT
119
127
  require File.expand_path('../config/environment', __FILE__)
120
128
 
@@ -126,76 +134,25 @@ module RailsHelpers
126
134
  response = response.last if response.last.is_a?(ActionDispatch::Response)
127
135
 
128
136
  if response.is_a?(Array)
129
- puts response.join
137
+ puts "Status: " + response.first.to_s
138
+ puts "Headers: " + response.second.to_s
139
+ if response.last.respond_to?(:each)
140
+ # making it work even with Rack::BodyProxy
141
+ body = ""
142
+ response.last.each do |chunk|
143
+ body << chunk
144
+ end
145
+ response.pop
146
+ response << body
147
+ end
148
+ puts "Body: " + response.last.to_s
130
149
  else
131
150
  puts response.body
132
151
  end
133
152
  SCRIPT
134
153
  File.open(File.join(rails_root, 'request.rb'), 'w') { |file| file.write(request_script) }
135
- @terminal.cd(rails_root)
136
- @terminal.run("ruby -rthread ./script/rails runner -e #{environment} request.rb")
137
- elsif rails_uses_rack?
138
- request_script = <<-SCRIPT
139
- require File.expand_path('../config/environment', __FILE__)
140
-
141
- env = Rack::MockRequest.env_for(#{uri.inspect})
142
- app = Rack::Lint.new(ActionController::Dispatcher.new)
143
-
144
- status, headers, body = app.call(env)
145
-
146
- response = ""
147
- if body.respond_to?(:to_str)
148
- response << body
149
- else
150
- body.each { |part| response << part }
151
- end
152
-
153
- puts response
154
- SCRIPT
155
- File.open(File.join(rails_root, 'request.rb'), 'w') { |file| file.write(request_script) }
156
- @terminal.cd(rails_root)
157
- @terminal.run("ruby -rthread ./script/runner -e #{environment} request.rb")
158
- else
159
- uri = URI.parse(uri)
160
- request_script = <<-SCRIPT
161
- require 'cgi'
162
- class CGIWrapper < CGI
163
- def initialize(*args)
164
- @env_table = {}
165
- @stdinput = $stdin
166
- super(*args)
167
- end
168
- attr_reader :env_table
169
- end
170
- $stdin = StringIO.new("")
171
- cgi = CGIWrapper.new
172
- cgi.env_table.update({
173
- 'HTTPS' => 'off',
174
- 'REQUEST_METHOD' => "GET",
175
- 'HTTP_HOST' => #{[uri.host, uri.port].join(':').inspect},
176
- 'SERVER_PORT' => #{uri.port.inspect},
177
- 'REQUEST_URI' => #{uri.request_uri.inspect},
178
- 'PATH_INFO' => #{uri.path.inspect},
179
- 'QUERY_STRING' => #{uri.query.inspect}
180
- })
181
- require 'dispatcher' unless defined?(ActionController::Dispatcher)
182
- Dispatcher.dispatch(cgi)
183
- SCRIPT
184
- File.open(File.join(rails_root, 'request.rb'), 'w') { |file| file.write(request_script) }
185
- @terminal.cd(rails_root)
186
- @terminal.run("ruby -rthread ./script/runner -e #{environment} request.rb")
187
- end
188
154
  end
189
155
 
190
- def monkeypatch_old_version
191
- monkeypatchin= <<-MONKEYPATCHIN
192
-
193
- MissingSourceFile::REGEXPS << [/^cannot load such file -- (.+)$/i, 1]
194
-
195
- MONKEYPATCHIN
196
-
197
- File.open(File.join(rails_root,"config","initializers", 'monkeypatchin.rb'), 'w') { |file| file.write(monkeypatchin) }
198
- end
199
156
  end
200
157
 
201
158
  World(RailsHelpers)
@@ -45,8 +45,7 @@ end
45
45
 
46
46
  module Airbrake
47
47
  def self.notify_or_ignore(*args)
48
- # TODO if you need to check more params, you'll have to use json.dump or something
49
- $stderr.puts "airbrake #{args[1][:component]}"
48
+ $stderr.puts "[airbrake] #{args[1][:component]}"
50
49
  end
51
50
  end
52
51
 
@@ -1,13 +1,12 @@
1
1
  Feature: Inform the user of the airbrake notice that was just created
2
2
 
3
3
  Background:
4
- Given I have built and installed the "airbrake" gem
4
+ Given I successfully run `rails new rails_root -O --skip-gemfile`
5
+ And I cd to "rails_root"
6
+ And I configure the Airbrake shim
5
7
 
6
8
  Scenario: Rescue an exception in a controller
7
- When I generate a new Rails application
8
- And I configure the Airbrake shim
9
- And I configure my application to require the "airbrake" gem
10
- And I run the airbrake generator with "-k myapikey"
9
+ When I run `rails generate airbrake -k myapikey`
11
10
  And I define a response for "TestController#index":
12
11
  """
13
12
  raise RuntimeError, "some message"
@@ -17,18 +16,15 @@ Feature: Inform the user of the airbrake notice that was just created
17
16
  <!-- AIRBRAKE ERROR -->
18
17
  """
19
18
  And I route "/test/index" to "test#index"
20
- And I perform a request to "http://example.com:123/test/index?param=value"
19
+ And I perform a request to "http://example.com:123/test/index?param=value" in the "production" environment
21
20
  Then I should see "Airbrake Error b6817316-9c45-ed26-45eb-780dbb86aadb"
22
21
 
23
22
  Scenario: Rescue an exception in a controller with a custom error string
24
- When I generate a new Rails application
25
- And I configure the Airbrake shim
26
- And I configure my application to require the "airbrake" gem
27
- And I configure the notifier to use the following configuration lines:
23
+ When I configure the notifier to use the following configuration lines:
28
24
  """
29
25
  config.user_information = 'Error #{{ error_id }}'
30
26
  """
31
- And I run the airbrake generator with "-k myapikey"
27
+ And I run `rails generate airbrake -k myapikey`
32
28
  And I define a response for "TestController#index":
33
29
  """
34
30
  raise RuntimeError, "some message"
@@ -38,18 +34,15 @@ Feature: Inform the user of the airbrake notice that was just created
38
34
  <!-- AIRBRAKE ERROR -->
39
35
  """
40
36
  And I route "/test/index" to "test#index"
41
- And I perform a request to "http://example.com:123/test/index?param=value"
37
+ And I perform a request to "http://example.com:123/test/index?param=value" in the "production" environment
42
38
  Then I should see "Error #b6817316-9c45-ed26-45eb-780dbb86aadb"
43
39
 
44
- Scenario: Don't inform them user
45
- When I generate a new Rails application
46
- And I configure the Airbrake shim
47
- And I configure my application to require the "airbrake" gem
48
- And I configure the notifier to use the following configuration lines:
40
+ Scenario: Don't inform the user
41
+ When I configure the notifier to use the following configuration lines:
49
42
  """
50
43
  config.user_information = false
51
44
  """
52
- And I run the airbrake generator with "-k myapikey"
45
+ And I run `rails generate airbrake -k myapikey`
53
46
  And I define a response for "TestController#index":
54
47
  """
55
48
  raise RuntimeError, "some message"
@@ -59,5 +52,5 @@ Feature: Inform the user of the airbrake notice that was just created
59
52
  <!-- AIRBRAKE ERROR -->
60
53
  """
61
54
  And I route "/test/index" to "test#index"
62
- And I perform a request to "http://example.com:123/test/index?param=value"
55
+ And I perform a request to "http://example.com:123/test/index?param=value" in the "production" environment
63
56
  Then I should not see "Airbrake Error b6817316-9c45-ed26-45eb-780dbb86aadb"
@@ -34,7 +34,7 @@ class AirbrakeGenerator < Rails::Generator::Base
34
34
  end
35
35
  end
36
36
  determine_api_key if heroku?
37
- m.rake "airbrake:test --trace", :generate_only => true
37
+ m.rake "airbrake:test", :generate_only => true
38
38
  end
39
39
  end
40
40
 
data/lib/airbrake.rb CHANGED
@@ -1,27 +1,27 @@
1
- require "girl_friday"
1
+ begin
2
+ require "girl_friday"
3
+ rescue LoadError
4
+ end
5
+
2
6
  require 'net/http'
3
7
  require 'net/https'
4
8
  require 'rubygems'
5
- require 'airbrake/extensions/blank'
9
+ require 'active_support/core_ext/object/blank'
6
10
  require 'airbrake/version'
7
11
  require 'airbrake/configuration'
8
12
  require 'airbrake/notice'
9
13
  require 'airbrake/sender'
10
14
  require 'airbrake/backtrace'
11
15
  require 'airbrake/rack'
16
+ require 'airbrake/sinatra'
12
17
  require 'airbrake/user_informer'
13
18
 
14
19
  require 'airbrake/railtie' if defined?(Rails::Railtie)
15
20
 
16
21
  module Airbrake
17
- API_VERSION = "2.3"
22
+ API_VERSION = "2.4"
18
23
  LOG_PREFIX = "** [Airbrake] "
19
24
 
20
- HEADERS = {
21
- 'Content-type' => 'text/xml',
22
- 'Accept' => 'text/xml, application/xml'
23
- }
24
-
25
25
  class << self
26
26
  # The sender object is responsible for delivering formatted data to the Airbrake server.
27
27
  # Must respond to #send_to_airbrake. See Airbrake::Sender.
@@ -133,6 +133,7 @@ module Airbrake
133
133
  if configuration.public?
134
134
  if configuration.async?
135
135
  configuration.async.call(notice)
136
+ nil # make sure we never set env["airbrake.error_id"] for async notices
136
137
  else
137
138
  sender.send_to_airbrake(notice)
138
139
  end
@@ -13,11 +13,14 @@ class ProjectFactory
13
13
 
14
14
  def create_projects_from_xml(xml)
15
15
  xml.split("\n").each do |line|
16
- /<name[^>]*>(?<name>.*?)<\/name>/ =~ line
16
+ /<name[^>]*>(.*)<\/name>/ =~ line
17
+ name = $1
17
18
  project.name = name.capitalize if name
18
- /<id[^>]*>(?<id>.*?)<\/id>/ =~ line
19
+ /<id[^>]*>(.*)<\/id>/ =~ line
20
+ id = $1
19
21
  project.id = id if id
20
- /<api-key[^>]*>(?<api_key>.*?)<\/api-key>/ =~ line
22
+ /<api-key[^>]*>(.*)<\/api-key>/ =~ line
23
+ api_key = $1
21
24
  project.api_key = api_key if api_key
22
25
  check_project
23
26
  end
@@ -107,6 +107,9 @@ module Airbrake
107
107
  # User attributes that are being captured
108
108
  attr_accessor :user_attributes
109
109
 
110
+ # Only used for JSON API
111
+ attr_accessor :project_id
112
+
110
113
 
111
114
  DEFAULT_PARAMS_FILTERS = %w(password password_confirmation).freeze
112
115
 
@@ -135,6 +138,7 @@ module Airbrake
135
138
  'ActionController::RoutingError',
136
139
  'ActionController::InvalidAuthenticityToken',
137
140
  'CGI::Session::CookieStore::TamperedWithCookie',
141
+ 'ActionController::UnknownHttpMethod',
138
142
  'ActionController::UnknownAction',
139
143
  'AbstractController::ActionNotFound',
140
144
  'Mongoid::Errors::DocumentNotFound']
@@ -306,6 +310,9 @@ module Airbrake
306
310
  Airbrake.sender.send_to_airbrake(notice)
307
311
  end
308
312
  lambda {|notice| queue << notice}
313
+ rescue NameError
314
+ warn "[AIRBRAKE] You can't use the default async handler without girl_friday."\
315
+ " Please make sure you have girl_friday installed."
309
316
  end
310
317
  end
311
318
  end
@@ -172,17 +172,17 @@ module Airbrake
172
172
  request.url(url)
173
173
  request.component(controller)
174
174
  request.action(action)
175
- unless parameters.nil? || parameters.empty?
175
+ unless parameters.blank?
176
176
  request.params do |params|
177
177
  xml_vars_for(params, parameters)
178
178
  end
179
179
  end
180
- unless session_data.nil? || session_data.empty?
180
+ unless session_data.blank?
181
181
  request.session do |session|
182
182
  xml_vars_for(session, session_data)
183
183
  end
184
184
  end
185
- unless cgi_data.nil? || cgi_data.empty?
185
+ unless cgi_data.blank?
186
186
  request.tag!("cgi-data") do |cgi_datum|
187
187
  xml_vars_for(cgi_datum, cgi_data)
188
188
  end
@@ -202,10 +202,54 @@ module Airbrake
202
202
  u.tag!("username",user[:username])
203
203
  end
204
204
  end
205
+ unless framework.blank?
206
+ notice.tag!("framework", framework)
207
+ end
205
208
  end
206
209
  xml.to_s
207
210
  end
208
211
 
212
+ def to_json
213
+ {
214
+ 'notifier' => {
215
+ 'name' => 'airbrake',
216
+ 'version' => Airbrake::VERSION,
217
+ 'url' => 'https://github.com/airbrake/airbrake'
218
+ },
219
+ 'errors' => [{
220
+ 'type' => error_class,
221
+ 'message' => error_message,
222
+ 'backtrace' => backtrace.lines.map do |line|
223
+ {
224
+ 'file' => line.file,
225
+ 'line' => line.number.to_i,
226
+ 'function' => line.method
227
+ }
228
+ end
229
+ }],
230
+ 'context' => {}.tap do |hash|
231
+ if url || controller || action || !parameters.blank? || !cgi_data.blank? || !session_data.blank?
232
+ hash['url'] = url
233
+ hash['component'] = controller
234
+ hash['action'] = action
235
+ hash['rootDirectory'] = File.dirname(project_root)
236
+ hash['environment'] = environment_name
237
+ end
238
+ end.tap do |hash|
239
+ next if user.blank?
240
+
241
+ hash['userId'] = user[:id]
242
+ hash['userName'] = user[:name]
243
+ hash['userEmail'] = user[:email]
244
+ end
245
+
246
+ }.tap do |hash|
247
+ hash['environment'] = cgi_data unless cgi_data.blank?
248
+ hash['params'] = parameters unless parameters.blank?
249
+ hash['session'] = session_data unless session_data.blank?
250
+ end.to_json
251
+ end
252
+
209
253
  # Determines if this notice should be ignored
210
254
  def ignore?
211
255
  ignored_class_names.include?(error_class) ||
@@ -300,6 +344,7 @@ module Airbrake
300
344
  def clean_rack_request_data
301
345
  if cgi_data
302
346
  cgi_data.delete("rack.request.form_vars")
347
+ cgi_data.delete("action_dispatch.secret_token")
303
348
  end
304
349
  end
305
350
 
@@ -379,6 +424,10 @@ module Airbrake
379
424
  Socket.gethostname
380
425
  end
381
426
 
427
+ def framework
428
+ Airbrake.configuration.framework
429
+ end
430
+
382
431
  def to_s
383
432
  content = []
384
433
  self.class.attr_readers.each do |attr|