airbrake 3.1.2 → 3.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/CHANGELOG +56 -0
  2. data/Gemfile +0 -9
  3. data/README.md +71 -3
  4. data/SUPPORTED_RAILS_VERSIONS +15 -0
  5. data/TESTING.md +8 -0
  6. data/airbrake.gemspec +10 -5
  7. data/features/rack.feature +4 -0
  8. data/features/rails.feature +66 -5
  9. data/features/step_definitions/rails_application_steps.rb +52 -7
  10. data/features/support/airbrake_shim.rb.template +5 -0
  11. data/generators/airbrake/lib/rake_commands.rb +1 -1
  12. data/generators/airbrake/templates/airbrake_tasks.rake +1 -1
  13. data/lib/airbrake.rb +14 -10
  14. data/lib/airbrake/backtrace.rb +8 -0
  15. data/lib/airbrake/capistrano.rb +0 -1
  16. data/lib/airbrake/configuration.rb +31 -4
  17. data/lib/airbrake/notice.rb +51 -9
  18. data/lib/airbrake/rack.rb +0 -1
  19. data/lib/airbrake/rails/controller_methods.rb +18 -7
  20. data/lib/airbrake/rails/javascript_notifier.rb +0 -1
  21. data/lib/airbrake/rails/middleware/exceptions_catcher.rb +10 -6
  22. data/lib/airbrake/rails3_tasks.rb +1 -2
  23. data/lib/airbrake/rake_handler.rb +3 -4
  24. data/lib/airbrake/sender.rb +35 -23
  25. data/lib/airbrake/shared_tasks.rb +2 -1
  26. data/lib/airbrake/version.rb +1 -1
  27. data/test/{airbrake_2_2.xsd → airbrake_2_3.xsd} +11 -1
  28. data/test/airbrake_tasks_test.rb +3 -3
  29. data/test/backtrace_test.rb +2 -2
  30. data/test/capistrano_test.rb +6 -6
  31. data/test/catcher_test.rb +2 -2
  32. data/test/configuration_test.rb +17 -2
  33. data/test/helper.rb +14 -14
  34. data/test/javascript_notifier_test.rb +1 -2
  35. data/test/logger_test.rb +8 -2
  36. data/test/notice_test.rb +98 -76
  37. data/test/notifier_test.rb +34 -4
  38. data/test/rack_test.rb +1 -1
  39. data/test/rails_initializer_test.rb +1 -1
  40. data/test/recursion_test.rb +1 -1
  41. data/test/sender_test.rb +22 -21
  42. data/test/user_informer_test.rb +1 -1
  43. metadata +176 -31
@@ -1,7 +1,8 @@
1
1
  namespace :airbrake do
2
2
  desc "Notify Airbrake of a new deploy."
3
- task :deploy => :environment do
3
+ task :deploy do
4
4
  require 'airbrake_tasks'
5
+ load File.join(Rails.root, 'config', 'initializers','airbrake.rb') if defined?(Rails.root)
5
6
  AirbrakeTasks.deploy(:rails_env => ENV['TO'],
6
7
  :scm_revision => ENV['REVISION'],
7
8
  :scm_repository => ENV['REPO'],
@@ -1,3 +1,3 @@
1
1
  module Airbrake
2
- VERSION = "3.1.2".freeze
2
+ VERSION = "3.1.3".freeze
3
3
  end
@@ -9,6 +9,7 @@
9
9
  <xs:element name="error" type="error"/>
10
10
  <xs:element name="request" type="request" minOccurs="0"/>
11
11
  <xs:element name="server-environment" type="serverEnvironment"/>
12
+ <xs:element name="current-user" type="current-user" minOccurs="0"/>
12
13
  </xs:all>
13
14
  <xs:attribute name="version" type="xs:string" use="required"/>
14
15
  </xs:complexType>
@@ -32,7 +33,7 @@
32
33
 
33
34
  <xs:complexType name="backtrace">
34
35
  <xs:sequence>
35
- <xs:element name="line" maxOccurs="unbounded">
36
+ <xs:element name="line" maxOccurs="unbounded" minOccurs="0">
36
37
  <xs:complexType>
37
38
  <xs:attribute name="file" type="xs:string" use="required"/>
38
39
  <xs:attribute name="number" type="xs:string" use="required"/>
@@ -75,4 +76,13 @@
75
76
  </xs:sequence>
76
77
  </xs:complexType>
77
78
 
79
+ <xs:complexType name="current-user">
80
+ <xs:all>
81
+ <xs:element name="id" type="xs:string"/>
82
+ <xs:element name="name" type="xs:string" minOccurs="0"/>
83
+ <xs:element name="email" type="xs:string" minOccurs="0"/>
84
+ <xs:element name="username" type="xs:string" minOccurs="0"/>
85
+ </xs:all>
86
+ </xs:complexType>
87
+
78
88
  </xs:schema>
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/helper'
1
+ require File.expand_path '../helper', __FILE__
2
2
  require 'rubygems'
3
3
 
4
4
  require File.dirname(__FILE__) + '/../lib/airbrake_tasks'
@@ -54,7 +54,7 @@ class AirbrakeTasksTest < Test::Unit::TestCase
54
54
 
55
55
  @options = { :rails_env => "staging", :dry_run => false }
56
56
  end
57
-
57
+
58
58
  context "performing a dry run" do
59
59
  setup { @output = AirbrakeTasks.deploy(@options.merge(:dry_run => true)) }
60
60
 
@@ -140,7 +140,7 @@ class AirbrakeTasksTest < Test::Unit::TestCase
140
140
  before_should "post to the custom host" do
141
141
  @post = stub("post", :set_form_data => nil)
142
142
  @http_proxy = stub("proxy", :request => @response)
143
-
143
+
144
144
  @http_proxy_class = stub("proxy_class", :new => @http_proxy)
145
145
  @http_proxy_class.expects(:new).with("custom.host", 80).returns(@http_proxy)
146
146
  Net::HTTP.expects(:Proxy).with(any_parameters).returns(@http_proxy_class)
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/helper'
1
+ require File.expand_path '../helper', __FILE__
2
2
 
3
3
  class BacktraceTest < Test::Unit::TestCase
4
4
 
@@ -20,7 +20,7 @@ class BacktraceTest < Test::Unit::TestCase
20
20
  assert_equal 'app/controllers/users_controller.rb', line.file
21
21
  assert_equal 'index', line.method
22
22
  end
23
-
23
+
24
24
  should "parse a windows backtrace into lines" do
25
25
  array = [
26
26
  "C:/Program Files/Server/app/models/user.rb:13:in `magic'",
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/helper'
1
+ require File.expand_path '../helper', __FILE__
2
2
 
3
3
  require 'capistrano/configuration'
4
4
  require 'airbrake/capistrano'
@@ -7,16 +7,16 @@ class CapistranoTest < Test::Unit::TestCase
7
7
  def setup
8
8
  super
9
9
  reset_config
10
-
10
+
11
11
  @configuration = Capistrano::Configuration.new
12
12
  Airbrake::Capistrano.load_into(@configuration)
13
13
  @configuration.dry_run = true
14
14
  end
15
-
15
+
16
16
  should "define airbrake:deploy task" do
17
17
  assert_not_nil @configuration.find_task('airbrake:deploy')
18
18
  end
19
-
19
+
20
20
  should "log when calling airbrake:deploy task" do
21
21
  @configuration.set(:current_revision, '084505b1c0e0bcf1526e673bb6ac99fbcb18aecc')
22
22
  @configuration.set(:repository, 'repository')
@@ -24,10 +24,10 @@ class CapistranoTest < Test::Unit::TestCase
24
24
  io = StringIO.new
25
25
  logger = Capistrano::Logger.new(:output => io)
26
26
  logger.level = Capistrano::Logger::MAX_LEVEL
27
-
27
+
28
28
  @configuration.logger = logger
29
29
  @configuration.find_and_execute_task('airbrake:deploy')
30
-
30
+
31
31
  assert io.string.include?('** Notifying Airbrake of Deploy')
32
32
  assert io.string.include?('** Airbrake Notification Complete')
33
33
  end
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/helper'
1
+ require File.expand_path '../helper', __FILE__
2
2
 
3
3
  class ActionControllerCatcherTest < Test::Unit::TestCase
4
4
 
@@ -75,7 +75,7 @@ class ActionControllerCatcherTest < Test::Unit::TestCase
75
75
  end
76
76
 
77
77
  def last_sent_notice_xml
78
- sender.collected.last
78
+ sender.collected.last.to_xml
79
79
  end
80
80
 
81
81
  def last_sent_notice_document
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/helper'
1
+ require File.expand_path '../helper', __FILE__
2
2
 
3
3
  class ConfigurationTest < Test::Unit::TestCase
4
4
 
@@ -29,6 +29,20 @@ class ConfigurationTest < Test::Unit::TestCase
29
29
  Airbrake::Configuration::IGNORE_DEFAULT
30
30
  assert_config_default :development_lookup, true
31
31
  assert_config_default :framework, 'Standalone'
32
+ assert_config_default :async, nil
33
+ end
34
+
35
+ should "set GirlFriday-callable for async=true" do
36
+ config = Airbrake::Configuration.new
37
+ config.async = true
38
+ assert config.async.respond_to?(:call)
39
+ end
40
+
41
+ should "set provided-callable for async {}" do
42
+ config = Airbrake::Configuration.new
43
+ config.async {|notice| :ok}
44
+ assert config.async.respond_to?(:call)
45
+ assert_equal :ok, config.async.call
32
46
  end
33
47
 
34
48
  should "provide default values for secure connections" do
@@ -70,6 +84,7 @@ class ConfigurationTest < Test::Unit::TestCase
70
84
  assert_config_overridable :environment_name
71
85
  assert_config_overridable :development_lookup
72
86
  assert_config_overridable :logger
87
+ assert_config_overridable :async
73
88
  end
74
89
 
75
90
  should "have an api key" do
@@ -84,7 +99,7 @@ class ConfigurationTest < Test::Unit::TestCase
84
99
  :http_read_timeout, :ignore, :ignore_by_filters, :ignore_user_agent,
85
100
  :notifier_name, :notifier_url, :notifier_version, :params_filters,
86
101
  :project_root, :port, :protocol, :proxy_host, :proxy_pass, :proxy_port,
87
- :proxy_user, :secure, :development_lookup].each do |option|
102
+ :proxy_user, :secure, :development_lookup, :async].each do |option|
88
103
  assert_equal config[option], hash[option], "Wrong value for #{option}"
89
104
  end
90
105
  end
@@ -7,8 +7,6 @@ $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), "..", "lib"))
7
7
 
8
8
  require 'thread'
9
9
 
10
- require "bundler/setup"
11
-
12
10
  require 'shoulda'
13
11
  require 'mocha'
14
12
 
@@ -134,16 +132,6 @@ class Test::Unit::TestCase
134
132
  BacktracedException.new(opts)
135
133
  end
136
134
 
137
- class BacktracedException < Exception
138
- attr_accessor :backtrace
139
- def initialize(opts)
140
- @backtrace = opts[:backtrace]
141
- end
142
- def set_backtrace(bt)
143
- @backtrace = bt
144
- end
145
- end
146
-
147
135
  def build_notice_data(exception = nil)
148
136
  exception ||= build_exception
149
137
  {
@@ -187,7 +175,7 @@ class Test::Unit::TestCase
187
175
  "but found #{nodes.map { |n| n.content }} in #{nodes.size} matching nodes." +
188
176
  "Document:\n#{document.to_s}"
189
177
  end
190
-
178
+
191
179
  def assert_logged(expected)
192
180
  assert_received(Airbrake, :write_verbose_log) do |expect|
193
181
  expect.with {|actual| actual =~ expected }
@@ -200,7 +188,7 @@ class Test::Unit::TestCase
200
188
  end
201
189
  end
202
190
 
203
-
191
+
204
192
  end
205
193
 
206
194
  module DefinesConstants
@@ -261,3 +249,15 @@ class FakeLogger
261
249
  def fatal(*args); end
262
250
  end
263
251
 
252
+ class BacktracedException < Exception
253
+ attr_accessor :backtrace
254
+ def initialize(opts)
255
+ @backtrace = opts[:backtrace]
256
+ end
257
+ def set_backtrace(bt)
258
+ @backtrace = bt
259
+ end
260
+ def message
261
+ "Something went wrong. Did you press the red button?"
262
+ end
263
+ end
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/helper'
1
+ require File.expand_path '../helper', __FILE__
2
2
  require 'airbrake/rails/javascript_notifier'
3
3
  require 'ostruct'
4
4
 
@@ -49,4 +49,3 @@ class JavascriptNotifierTest < Test::Unit::TestCase
49
49
  assert ! controller.send(:airbrake_javascript_notifier)['"bad_javascript"']
50
50
  end
51
51
  end
52
-
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/helper'
1
+ require File.expand_path '../helper', __FILE__
2
2
 
3
3
  class LoggerTest < Test::Unit::TestCase
4
4
  def stub_http(response, body = nil)
@@ -11,7 +11,7 @@ class LoggerTest < Test::Unit::TestCase
11
11
  end
12
12
 
13
13
  def send_notice
14
- Airbrake.sender.send_to_airbrake('data')
14
+ Airbrake.sender.send_to_airbrake({'foo' => "bar"})
15
15
  end
16
16
 
17
17
  def stub_verbose_log
@@ -70,4 +70,10 @@ class LoggerTest < Test::Unit::TestCase
70
70
  assert_logged /Response from Airbrake:/
71
71
  end
72
72
 
73
+ should "print information about the notice when Airbrake server fails" do
74
+ stub_verbose_log
75
+ stub_http(Net::HTTPError, "test")
76
+ send_notice
77
+ assert_logged /Notice details:/
78
+ end
73
79
  end
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/helper'
1
+ require File.expand_path '../helper', __FILE__
2
2
 
3
3
  class NoticeTest < Test::Unit::TestCase
4
4
 
@@ -24,6 +24,79 @@ class NoticeTest < Test::Unit::TestCase
24
24
  :env => { 'three' => 'four' } }.update(attrs))
25
25
  end
26
26
 
27
+ def assert_accepts_exception_attribute(attribute, args = {}, &block)
28
+ exception = build_exception
29
+ block ||= lambda { exception.send(attribute) }
30
+ value = block.call(exception)
31
+
32
+ notice_from_exception = build_notice(args.merge(:exception => exception))
33
+
34
+ assert_equal notice_from_exception.send(attribute),
35
+ value,
36
+ "#{attribute} was not correctly set from an exception"
37
+
38
+ notice_from_hash = build_notice(args.merge(attribute => value))
39
+ assert_equal notice_from_hash.send(attribute),
40
+ value,
41
+ "#{attribute} was not correctly set from a hash"
42
+ end
43
+
44
+ def assert_serializes_hash(attribute)
45
+ [File.open(__FILE__), Proc.new { puts "boo!" }, Module.new].each do |object|
46
+ hash = {
47
+ :strange_object => object,
48
+ :sub_hash => {
49
+ :sub_object => object
50
+ },
51
+ :array => [object]
52
+ }
53
+ notice = build_notice(attribute => hash)
54
+ hash = notice.send(attribute)
55
+ assert_equal object.to_s, hash[:strange_object], "objects should be serialized"
56
+ assert_kind_of Hash, hash[:sub_hash], "subhashes should be kept"
57
+ assert_equal object.to_s, hash[:sub_hash][:sub_object], "subhash members should be serialized"
58
+ assert_kind_of Array, hash[:array], "arrays should be kept"
59
+ assert_equal object.to_s, hash[:array].first, "array members should be serialized"
60
+ end
61
+ end
62
+
63
+ def assert_valid_notice_document(document)
64
+ xsd_path = File.join(File.dirname(__FILE__), "airbrake_2_3.xsd")
65
+ schema = Nokogiri::XML::Schema.new(IO.read(xsd_path))
66
+ errors = schema.validate(document)
67
+ assert errors.empty?, errors.collect{|e| e.message }.join
68
+ end
69
+
70
+ def assert_filters_hash(attribute)
71
+ filters = ["abc", :def]
72
+ original = { 'abc' => "123", 'def' => "456", 'ghi' => "789", 'nested' => { 'abc' => '100' },
73
+ 'something_with_abc' => 'match the entire string'}
74
+ filtered = { 'abc' => "[FILTERED]",
75
+ 'def' => "[FILTERED]",
76
+ 'something_with_abc' => "match the entire string",
77
+ 'ghi' => "789",
78
+ 'nested' => { 'abc' => '[FILTERED]' } }
79
+
80
+ notice = build_notice(:params_filters => filters, attribute => original)
81
+
82
+ assert_equal(filtered,
83
+ notice.send(attribute))
84
+ end
85
+
86
+ def build_backtrace_array
87
+ ["app/models/user.rb:13:in `magic'",
88
+ "app/controllers/users_controller.rb:8:in `index'"]
89
+ end
90
+
91
+ def hostname
92
+ `hostname`.chomp
93
+ end
94
+
95
+ def user
96
+ Struct.new(:email,:id,:name).
97
+ new("darth@vader.com",1,"Anakin Skywalker")
98
+ end
99
+
27
100
  should "set the api key" do
28
101
  api_key = 'key'
29
102
  notice = build_notice(:api_key => api_key)
@@ -78,6 +151,12 @@ class NoticeTest < Test::Unit::TestCase
78
151
  "backtrace was not correctly set from a hash"
79
152
  end
80
153
 
154
+ should "accept user" do
155
+ assert_equal user.id, build_notice(:user => user).user.id
156
+ assert_equal user.email, build_notice(:user => user).user.email
157
+ assert_equal user.name, build_notice(:user => user).user.name
158
+ end
159
+
81
160
  should "pass its backtrace filters for parsing" do
82
161
  backtrace_array = ['my/file/backtrace:3']
83
162
  exception = build_exception
@@ -316,14 +395,7 @@ class NoticeTest < Test::Unit::TestCase
316
395
  end
317
396
  end
318
397
 
319
- ignored_error_classes = %w(
320
- ActiveRecord::RecordNotFound
321
- AbstractController::ActionNotFound
322
- ActionController::RoutingError
323
- ActionController::InvalidAuthenticityToken
324
- CGI::Session::CookieStore::TamperedWithCookie
325
- ActionController::UnknownAction
326
- )
398
+ ignored_error_classes = Airbrake::Configuration::IGNORE_DEFAULT
327
399
 
328
400
  ignored_error_classes.each do |ignored_error_class|
329
401
  should "ignore #{ignored_error_class} error by default" do
@@ -349,7 +421,6 @@ class NoticeTest < Test::Unit::TestCase
349
421
  end
350
422
  end
351
423
 
352
-
353
424
  should "ensure #to_ary is called on objects that support it" do
354
425
  assert_nothing_raised do
355
426
  build_notice(:session => { :object => stub(:to_ary => {}) })
@@ -368,6 +439,20 @@ class NoticeTest < Test::Unit::TestCase
368
439
  assert_equal 'GET', notice.cgi_data['REQUEST_METHOD']
369
440
  end
370
441
 
442
+ should "show a nice warning when rack environment exceeds rack keyspace" do
443
+ # simulate exception for too big query
444
+ Rack::Request.any_instance.expects(:params).raises(RangeError.new("exceeded available parameter key space"))
445
+
446
+ url = "https://subdomain.happylane.com:100/test/file.rb?var=x"
447
+ env = Rack::MockRequest.env_for(url)
448
+
449
+ notice = build_notice(:rack_env => env)
450
+
451
+ assert_equal url, notice.url
452
+ assert_equal({:message => "failed to call params on Rack::Request -- exceeded available parameter key space"}, notice.parameters)
453
+ assert_equal 'GET', notice.cgi_data['REQUEST_METHOD']
454
+ end
455
+
371
456
  should "extract data from a rack environment hash with action_dispatch info" do
372
457
  params = { 'controller' => 'users', 'action' => 'index', 'id' => '7' }
373
458
  env = Rack::MockRequest.env_for('/', { 'action_dispatch.request.parameters' => params })
@@ -397,72 +482,9 @@ class NoticeTest < Test::Unit::TestCase
397
482
  assert_equal session_data, notice.session_data
398
483
  end
399
484
 
400
- def assert_accepts_exception_attribute(attribute, args = {}, &block)
485
+ should "prefer passed error_message to exception message" do
401
486
  exception = build_exception
402
- block ||= lambda { exception.send(attribute) }
403
- value = block.call(exception)
404
-
405
- notice_from_exception = build_notice(args.merge(:exception => exception))
406
-
407
- assert_equal notice_from_exception.send(attribute),
408
- value,
409
- "#{attribute} was not correctly set from an exception"
410
-
411
- notice_from_hash = build_notice(args.merge(attribute => value))
412
- assert_equal notice_from_hash.send(attribute),
413
- value,
414
- "#{attribute} was not correctly set from a hash"
487
+ notice = build_notice(:exception => exception,:error_message => "Random ponies")
488
+ assert_equal "BacktracedException: Random ponies", notice.error_message
415
489
  end
416
-
417
- def assert_serializes_hash(attribute)
418
- [File.open(__FILE__), Proc.new { puts "boo!" }, Module.new].each do |object|
419
- hash = {
420
- :strange_object => object,
421
- :sub_hash => {
422
- :sub_object => object
423
- },
424
- :array => [object]
425
- }
426
- notice = build_notice(attribute => hash)
427
- hash = notice.send(attribute)
428
- assert_equal object.to_s, hash[:strange_object], "objects should be serialized"
429
- assert_kind_of Hash, hash[:sub_hash], "subhashes should be kept"
430
- assert_equal object.to_s, hash[:sub_hash][:sub_object], "subhash members should be serialized"
431
- assert_kind_of Array, hash[:array], "arrays should be kept"
432
- assert_equal object.to_s, hash[:array].first, "array members should be serialized"
433
- end
434
- end
435
-
436
- def assert_valid_notice_document(document)
437
- xsd_path = File.join(File.dirname(__FILE__), "airbrake_2_2.xsd")
438
- schema = Nokogiri::XML::Schema.new(IO.read(xsd_path))
439
- errors = schema.validate(document)
440
- assert errors.empty?, errors.collect{|e| e.message }.join
441
- end
442
-
443
- def assert_filters_hash(attribute)
444
- filters = ["abc", :def]
445
- original = { 'abc' => "123", 'def' => "456", 'ghi' => "789", 'nested' => { 'abc' => '100' },
446
- 'something_with_abc' => 'match the entire string'}
447
- filtered = { 'abc' => "[FILTERED]",
448
- 'def' => "[FILTERED]",
449
- 'something_with_abc' => "match the entire string",
450
- 'ghi' => "789",
451
- 'nested' => { 'abc' => '[FILTERED]' } }
452
-
453
- notice = build_notice(:params_filters => filters, attribute => original)
454
-
455
- assert_equal(filtered,
456
- notice.send(attribute))
457
- end
458
-
459
- def build_backtrace_array
460
- ["app/models/user.rb:13:in `magic'",
461
- "app/controllers/users_controller.rb:8:in `index'"]
462
- end
463
-
464
- def hostname
465
- `hostname`.chomp
466
- end
467
-
468
490
  end