airbrake 3.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. data/.gitignore +18 -0
  2. data/.yardopts +3 -0
  3. data/CHANGELOG +441 -0
  4. data/Gemfile +3 -0
  5. data/INSTALL +25 -0
  6. data/MIT-LICENSE +22 -0
  7. data/README.md +431 -0
  8. data/README_FOR_HEROKU_ADDON.md +93 -0
  9. data/Rakefile +188 -0
  10. data/SUPPORTED_RAILS_VERSIONS +14 -0
  11. data/TESTING.md +26 -0
  12. data/airbrake.gemspec +33 -0
  13. data/features/metal.feature +23 -0
  14. data/features/rack.feature +27 -0
  15. data/features/rails.feature +254 -0
  16. data/features/rails_with_js_notifier.feature +78 -0
  17. data/features/rake.feature +23 -0
  18. data/features/sinatra.feature +33 -0
  19. data/features/step_definitions/airbrake_shim.rb.template +15 -0
  20. data/features/step_definitions/file_steps.rb +10 -0
  21. data/features/step_definitions/metal_steps.rb +23 -0
  22. data/features/step_definitions/rack_steps.rb +20 -0
  23. data/features/step_definitions/rails_application_steps.rb +401 -0
  24. data/features/step_definitions/rake_steps.rb +17 -0
  25. data/features/support/airbrake_shim.rb.template +15 -0
  26. data/features/support/env.rb +18 -0
  27. data/features/support/matchers.rb +35 -0
  28. data/features/support/rails.rb +181 -0
  29. data/features/support/rake/Rakefile +57 -0
  30. data/features/support/terminal.rb +103 -0
  31. data/features/user_informer.feature +63 -0
  32. data/generators/airbrake/airbrake_generator.rb +90 -0
  33. data/generators/airbrake/lib/insert_commands.rb +34 -0
  34. data/generators/airbrake/lib/rake_commands.rb +24 -0
  35. data/generators/airbrake/templates/airbrake_tasks.rake +25 -0
  36. data/generators/airbrake/templates/capistrano_hook.rb +6 -0
  37. data/generators/airbrake/templates/initializer.rb +6 -0
  38. data/install.rb +1 -0
  39. data/lib/airbrake.rb +150 -0
  40. data/lib/airbrake/backtrace.rb +100 -0
  41. data/lib/airbrake/capistrano.rb +21 -0
  42. data/lib/airbrake/configuration.rb +247 -0
  43. data/lib/airbrake/notice.rb +348 -0
  44. data/lib/airbrake/rack.rb +42 -0
  45. data/lib/airbrake/rails.rb +41 -0
  46. data/lib/airbrake/rails/action_controller_catcher.rb +30 -0
  47. data/lib/airbrake/rails/controller_methods.rb +68 -0
  48. data/lib/airbrake/rails/error_lookup.rb +33 -0
  49. data/lib/airbrake/rails/javascript_notifier.rb +42 -0
  50. data/lib/airbrake/rails3_tasks.rb +82 -0
  51. data/lib/airbrake/railtie.rb +33 -0
  52. data/lib/airbrake/rake_handler.rb +65 -0
  53. data/lib/airbrake/sender.rb +83 -0
  54. data/lib/airbrake/shared_tasks.rb +30 -0
  55. data/lib/airbrake/tasks.rb +83 -0
  56. data/lib/airbrake/user_informer.rb +25 -0
  57. data/lib/airbrake/version.rb +3 -0
  58. data/lib/airbrake_tasks.rb +50 -0
  59. data/lib/rails/generators/airbrake/airbrake_generator.rb +96 -0
  60. data/lib/templates/javascript_notifier.erb +13 -0
  61. data/lib/templates/rescue.erb +91 -0
  62. data/rails/init.rb +1 -0
  63. data/script/integration_test.rb +38 -0
  64. data/test/airbrake_2_2.xsd +78 -0
  65. data/test/airbrake_tasks_test.rb +163 -0
  66. data/test/backtrace_test.rb +163 -0
  67. data/test/catcher_test.rb +333 -0
  68. data/test/configuration_test.rb +216 -0
  69. data/test/helper.rb +251 -0
  70. data/test/javascript_notifier_test.rb +52 -0
  71. data/test/logger_test.rb +85 -0
  72. data/test/notice_test.rb +459 -0
  73. data/test/notifier_test.rb +235 -0
  74. data/test/rack_test.rb +58 -0
  75. data/test/rails_initializer_test.rb +36 -0
  76. data/test/recursion_test.rb +10 -0
  77. data/test/sender_test.rb +193 -0
  78. data/test/user_informer_test.rb +29 -0
  79. metadata +365 -0
@@ -0,0 +1,17 @@
1
+ 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`
4
+ end
5
+
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
12
+ end
13
+
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
17
+ end
@@ -0,0 +1,15 @@
1
+ require 'sham_rack'
2
+
3
+ ShamRack.at("airbrakeapp.com") do |env|
4
+ xml = env['rack.input'].read
5
+ puts "Recieved the following exception:\n#{xml}"
6
+ response = <<-end_xml
7
+ <?xml version="1.0" encoding="UTF-8"?>
8
+ <notice>
9
+ <error-id type="integer">3799307</error-id>
10
+ <url>http://sample.airbrakeapp.com/errors/3799307/notices/643732254</url>
11
+ <id type="integer">643732254</id>
12
+ </notice>
13
+ end_xml
14
+ ["200 OK", { "Content-type" => "text/xml" }, response]
15
+ end
@@ -0,0 +1,18 @@
1
+ require 'active_support'
2
+ require 'nokogiri'
3
+ require 'rspec'
4
+
5
+ PROJECT_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..', '..')).freeze
6
+ TEMP_DIR = File.join(PROJECT_ROOT, 'tmp').freeze
7
+ 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
+ RACK_FILE = File.join(TEMP_DIR, 'rack_app.rb').freeze
11
+
12
+ Before do
13
+ FileUtils.mkdir_p(TEMP_DIR)
14
+ FileUtils.rm_rf(BUILT_GEM_ROOT)
15
+ FileUtils.rm_rf(LOCAL_RAILS_ROOT)
16
+ FileUtils.rm_f(RACK_FILE)
17
+ FileUtils.mkdir_p(BUILT_GEM_ROOT)
18
+ end
@@ -0,0 +1,35 @@
1
+ RSpec::Matchers.define :have_content do |xpath, content|
2
+ match do |document|
3
+ @elements = document.search(xpath)
4
+
5
+ if @elements.empty?
6
+ false
7
+ else
8
+ element_with_content = document.at("#{xpath}[contains(.,'#{content}')]")
9
+
10
+ if element_with_content.nil?
11
+ @found = @elements.collect { |element| element.content }
12
+
13
+ false
14
+ else
15
+ true
16
+ end
17
+ end
18
+ end
19
+
20
+ failure_message_for_should do |document|
21
+ if @elements.empty?
22
+ "In XML:\n#{document}\nNo element at #{xpath}"
23
+ else
24
+ "In XML:\n#{document}\nGot content #{@found.inspect} at #{xpath} instead of #{content.inspect}"
25
+ end
26
+ end
27
+
28
+ failure_message_for_should_not do |document|
29
+ unless @elements.empty?
30
+ "In XML:\n#{document}\nExpcted no content #{content.inspect} at #{xpath}"
31
+ end
32
+ end
33
+ end
34
+
35
+ World(RSpec::Matchers)
@@ -0,0 +1,181 @@
1
+ module RailsHelpers
2
+ def rails_root_exists?
3
+ File.exists?(environment_path)
4
+ end
5
+
6
+ def application_controller_filename
7
+ controller_filename = File.join(rails_root, 'app', 'controllers', "application_controller.rb")
8
+ end
9
+
10
+ def rails3?
11
+ rails_version =~ /^3/
12
+ end
13
+
14
+ def rails_root
15
+ LOCAL_RAILS_ROOT
16
+ end
17
+
18
+ def rails_uses_rack?
19
+ rails3? || rails_version =~ /^2\.3/
20
+ end
21
+
22
+ def rails_version
23
+ @rails_version ||= begin
24
+ if bundler_manages_gems?
25
+ rails_version = open(gemfile_path).read.match(/gem.*rails["'].*["'](.+)["']/)[1]
26
+ else
27
+ environment_file = File.join(rails_root, 'config', 'environment.rb')
28
+ rails_version = `grep RAILS_GEM_VERSION #{environment_file}`.match(/[\d.]+/)[0]
29
+ end
30
+ end
31
+ end
32
+
33
+ def bundler_manages_gems?
34
+ File.exists?(gemfile_path)
35
+ end
36
+
37
+ def gemfile_path
38
+ gemfile = File.join(rails_root, 'Gemfile')
39
+ end
40
+
41
+ def rails_manages_gems?
42
+ rails_version =~ /^2\.[123]/
43
+ end
44
+
45
+ def rails_supports_initializers?
46
+ rails3? || rails_version =~ /^2\./
47
+ end
48
+
49
+ def rails_finds_generators_in_gems?
50
+ rails3? || rails_version =~ /^2\./
51
+ end
52
+
53
+ def environment_path
54
+ File.join(rails_root, 'config', 'environment.rb')
55
+ end
56
+
57
+ def rakefile_path
58
+ File.join(rails_root, 'Rakefile')
59
+ end
60
+
61
+ def bundle_gem(gem_name, version = nil)
62
+ File.open(gemfile_path, 'a') do |file|
63
+ gem = "gem '#{gem_name}'"
64
+ gem += ", '#{version}'" if version
65
+ file.puts(gem)
66
+ end
67
+ end
68
+
69
+ def config_gem(gem_name, version = nil)
70
+ run = "Rails::Initializer.run do |config|"
71
+ insert = " config.gem '#{gem_name}'"
72
+ insert += ", :version => '#{version}'" if version
73
+ content = File.read(environment_path)
74
+ content = "require 'thread'\n#{content}"
75
+ if content.sub!(run, "#{run}\n#{insert}")
76
+ File.open(environment_path, 'wb') { |file| file.write(content) }
77
+ else
78
+ raise "Couldn't find #{run.inspect} in #{environment_path}"
79
+ end
80
+ end
81
+
82
+ def config_gem_dependencies
83
+ insert = <<-END
84
+ if Gem::VERSION >= "1.3.6"
85
+ module Rails
86
+ class GemDependency
87
+ def requirement
88
+ r = super
89
+ (r == Gem::Requirement.default) ? nil : r
90
+ end
91
+ end
92
+ end
93
+ end
94
+ END
95
+ run = "Rails::Initializer.run do |config|"
96
+ content = File.read(environment_path)
97
+ if content.sub!(run, "#{insert}\n#{run}")
98
+ File.open(environment_path, 'wb') { |file| file.write(content) }
99
+ else
100
+ raise "Couldn't find #{run.inspect} in #{environment_path}"
101
+ end
102
+ end
103
+
104
+ def require_thread
105
+ content = File.read(rakefile_path)
106
+ content = "require 'thread'\n#{content}"
107
+ File.open(rakefile_path, 'wb') { |file| file.write(content) }
108
+ end
109
+
110
+ def perform_request(uri, environment = 'production')
111
+ if rails3?
112
+ request_script = <<-SCRIPT
113
+ require 'config/environment'
114
+
115
+ env = Rack::MockRequest.env_for(#{uri.inspect})
116
+ response = RailsRoot::Application.call(env).last
117
+
118
+ if response.is_a?(Array)
119
+ puts response.join
120
+ else
121
+ puts response.body
122
+ end
123
+ SCRIPT
124
+ File.open(File.join(rails_root, 'request.rb'), 'w') { |file| file.write(request_script) }
125
+ @terminal.cd(rails_root)
126
+ @terminal.run("ruby -rthread ./script/rails runner -e #{environment} request.rb")
127
+ elsif rails_uses_rack?
128
+ request_script = <<-SCRIPT
129
+ require 'config/environment'
130
+
131
+ env = Rack::MockRequest.env_for(#{uri.inspect})
132
+ app = Rack::Lint.new(ActionController::Dispatcher.new)
133
+
134
+ status, headers, body = app.call(env)
135
+
136
+ response = ""
137
+ if body.respond_to?(:to_str)
138
+ response << body
139
+ else
140
+ body.each { |part| response << part }
141
+ end
142
+
143
+ puts response
144
+ SCRIPT
145
+ File.open(File.join(rails_root, 'request.rb'), 'w') { |file| file.write(request_script) }
146
+ @terminal.cd(rails_root)
147
+ @terminal.run("ruby -rthread ./script/runner -e #{environment} request.rb")
148
+ else
149
+ uri = URI.parse(uri)
150
+ request_script = <<-SCRIPT
151
+ require 'cgi'
152
+ class CGIWrapper < CGI
153
+ def initialize(*args)
154
+ @env_table = {}
155
+ @stdinput = $stdin
156
+ super(*args)
157
+ end
158
+ attr_reader :env_table
159
+ end
160
+ $stdin = StringIO.new("")
161
+ cgi = CGIWrapper.new
162
+ cgi.env_table.update({
163
+ 'HTTPS' => 'off',
164
+ 'REQUEST_METHOD' => "GET",
165
+ 'HTTP_HOST' => #{[uri.host, uri.port].join(':').inspect},
166
+ 'SERVER_PORT' => #{uri.port.inspect},
167
+ 'REQUEST_URI' => #{uri.request_uri.inspect},
168
+ 'PATH_INFO' => #{uri.path.inspect},
169
+ 'QUERY_STRING' => #{uri.query.inspect}
170
+ })
171
+ require 'dispatcher' unless defined?(ActionController::Dispatcher)
172
+ Dispatcher.dispatch(cgi)
173
+ SCRIPT
174
+ File.open(File.join(rails_root, 'request.rb'), 'w') { |file| file.write(request_script) }
175
+ @terminal.cd(rails_root)
176
+ @terminal.run("ruby -rthread ./script/runner -e #{environment} request.rb")
177
+ end
178
+ end
179
+ end
180
+
181
+ World(RailsHelpers)
@@ -0,0 +1,57 @@
1
+ # A test harness for RakeHandler
2
+ #
3
+ require 'rake'
4
+ require 'rubygems'
5
+ require 'airbrake'
6
+ require 'airbrake/rake_handler'
7
+
8
+ Airbrake.configure do |c|
9
+ end
10
+
11
+ # Should catch exception
12
+ task :airbrake do
13
+ Airbrake.configuration.rescue_rake_exceptions = true
14
+ stub_tty_output(true)
15
+ raise_exception
16
+ end
17
+
18
+ # Should not catch exception
19
+ task :airbrake_disabled do
20
+ Airbrake.configuration.rescue_rake_exceptions = false
21
+ stub_tty_output(true)
22
+ raise_exception
23
+ end
24
+
25
+ # Should not catch exception as tty_output is true
26
+ task :airbrake_autodetect_from_terminal do
27
+ Airbrake.configuration.rescue_rake_exceptions = nil
28
+ stub_tty_output(true)
29
+ raise_exception
30
+ end
31
+
32
+ # Should catch exception as tty_output is false
33
+ task :airbrake_autodetect_not_from_terminal do
34
+ Airbrake.configuration.rescue_rake_exceptions = nil
35
+ stub_tty_output(false)
36
+ raise_exception
37
+ end
38
+
39
+ module Airbrake
40
+ def self.notify(*args)
41
+ # TODO if you need to check more params, you'll have to use json.dump or something
42
+ $stderr.puts "airbrake #{args[1][:component]}"
43
+ end
44
+ end
45
+
46
+ def stub_tty_output(value)
47
+ Rake.application.instance_eval do
48
+ @tty_output_stub = value
49
+ def tty_output?
50
+ @tty_output_stub
51
+ end
52
+ end
53
+ end
54
+
55
+ def raise_exception
56
+ raise 'TEST'
57
+ end
@@ -0,0 +1,103 @@
1
+ require 'fileutils'
2
+
3
+ Before do
4
+ @terminal = Terminal.new
5
+ end
6
+
7
+ After do |story|
8
+ if story.failed?
9
+ # puts @terminal.output
10
+ end
11
+ end
12
+
13
+ class Terminal
14
+ attr_reader :output, :status
15
+ attr_accessor :environment_variables, :invoke_heroku_rake_tasks_locally
16
+
17
+ def initialize
18
+ @cwd = FileUtils.pwd
19
+ @output = ""
20
+ @status = 0
21
+ @logger = Logger.new(File.join(TEMP_DIR, 'terminal.log'))
22
+
23
+ @invoke_heroku_rake_tasks_locally = false
24
+
25
+ @environment_variables = {
26
+ "GEM_HOME" => LOCAL_GEM_ROOT,
27
+ "GEM_PATH" => "#{LOCAL_GEM_ROOT}:#{BUILT_GEM_ROOT}",
28
+ "PATH" => "#{gem_bin_path}:#{ENV['PATH']}"
29
+ }
30
+ end
31
+
32
+ def cd(directory)
33
+ @cwd = directory
34
+ end
35
+
36
+ def run(command)
37
+ command = optionally_invoke_heroku_rake_tasks_locally(command)
38
+
39
+ output << "#{command}\n"
40
+ FileUtils.cd(@cwd) do
41
+ # The ; forces ruby to shell out so the env settings work right
42
+ cmdline = "#{environment_settings} #{command} 2>&1 ; "
43
+ logger.debug(cmdline)
44
+ result = `#{cmdline}`
45
+ logger.debug(result)
46
+ output << result
47
+ end
48
+ @status = $?
49
+ end
50
+
51
+ def optionally_invoke_heroku_rake_tasks_locally(command)
52
+ if invoke_heroku_rake_tasks_locally
53
+ command.sub(/^heroku /, '')
54
+ else
55
+ command
56
+ end
57
+ end
58
+
59
+ def echo(string)
60
+ logger.debug(string)
61
+ end
62
+
63
+ def build_and_install_gem(gemspec)
64
+ pkg_dir = File.join(TEMP_DIR, 'pkg')
65
+ FileUtils.mkdir_p(pkg_dir)
66
+ output = `gem build #{gemspec} 2>&1`
67
+ gem_file = Dir.glob("*.gem").first
68
+ unless gem_file
69
+ raise "Gem didn't build:\n#{output}"
70
+ end
71
+ target = File.join(pkg_dir, gem_file)
72
+ FileUtils.mv(gem_file, target)
73
+ install_gem_to(LOCAL_GEM_ROOT, target)
74
+ end
75
+
76
+ def install_gem(gem)
77
+ install_gem_to(LOCAL_GEM_ROOT, gem)
78
+ end
79
+
80
+ def uninstall_gem(gem)
81
+ `gem uninstall -i #{LOCAL_GEM_ROOT} #{gem}`
82
+ end
83
+
84
+ def prepend_path(path)
85
+ @environment_variables['PATH'] = path + ":" + @environment_variables['PATH']
86
+ end
87
+
88
+ private
89
+
90
+ def install_gem_to(root, gem)
91
+ `gem install -i #{root} --no-ri --no-rdoc #{gem}`
92
+ end
93
+
94
+ def environment_settings
95
+ @environment_variables.map { |key, value| "#{key}=#{value}" }.join(' ')
96
+ end
97
+
98
+ def gem_bin_path
99
+ File.join(LOCAL_GEM_ROOT, "bin")
100
+ end
101
+
102
+ attr_reader :logger
103
+ end
@@ -0,0 +1,63 @@
1
+ Feature: Inform the user of the airbrake notice that was just created
2
+
3
+ Background:
4
+ Given I have built and installed the "airbrake" gem
5
+
6
+ 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"
11
+ And I define a response for "TestController#index":
12
+ """
13
+ raise RuntimeError, "some message"
14
+ """
15
+ And the response page for a "500" error is
16
+ """
17
+ <!-- AIRBRAKE ERROR -->
18
+ """
19
+ And I route "/test/index" to "test#index"
20
+ And I perform a request to "http://example.com:123/test/index?param=value"
21
+ Then I should see "Airbrake Error 3799307"
22
+
23
+ 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:
28
+ """
29
+ config.user_information = 'Error #{{ error_id }}'
30
+ """
31
+ And I run the airbrake generator with "-k myapikey"
32
+ And I define a response for "TestController#index":
33
+ """
34
+ raise RuntimeError, "some message"
35
+ """
36
+ And the response page for a "500" error is
37
+ """
38
+ <!-- AIRBRAKE ERROR -->
39
+ """
40
+ And I route "/test/index" to "test#index"
41
+ And I perform a request to "http://example.com:123/test/index?param=value"
42
+ Then I should see "Error #3799307"
43
+
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:
49
+ """
50
+ config.user_information = false
51
+ """
52
+ And I run the airbrake generator with "-k myapikey"
53
+ And I define a response for "TestController#index":
54
+ """
55
+ raise RuntimeError, "some message"
56
+ """
57
+ And the response page for a "500" error is
58
+ """
59
+ <!-- AIRBRAKE ERROR -->
60
+ """
61
+ And I route "/test/index" to "test#index"
62
+ And I perform a request to "http://example.com:123/test/index?param=value"
63
+ Then I should not see "Airbrake Error 3799307"