cloudtrapper 0.0.2.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. data/CHANGELOG +823 -0
  2. data/Gemfile +12 -0
  3. data/Guardfile +6 -0
  4. data/INSTALL +20 -0
  5. data/MIT-LICENSE +22 -0
  6. data/README.md +465 -0
  7. data/README_FOR_HEROKU_ADDON.md +94 -0
  8. data/Rakefile +223 -0
  9. data/SUPPORTED_RAILS_VERSIONS +23 -0
  10. data/TESTING.md +33 -0
  11. data/cloudtrapper.gemspec +35 -0
  12. data/features/metal.feature +18 -0
  13. data/features/rack.feature +56 -0
  14. data/features/rails.feature +211 -0
  15. data/features/rails_with_js_notifier.feature +97 -0
  16. data/features/rake.feature +27 -0
  17. data/features/sinatra.feature +29 -0
  18. data/features/step_definitions/file_steps.rb +10 -0
  19. data/features/step_definitions/metal_steps.rb +23 -0
  20. data/features/step_definitions/rack_steps.rb +23 -0
  21. data/features/step_definitions/rails_application_steps.rb +433 -0
  22. data/features/step_definitions/rake_steps.rb +17 -0
  23. data/features/support/airbrake_shim.rb.template +11 -0
  24. data/features/support/env.rb +18 -0
  25. data/features/support/matchers.rb +35 -0
  26. data/features/support/rails.rb +201 -0
  27. data/features/support/rake/Rakefile +68 -0
  28. data/features/support/terminal.rb +107 -0
  29. data/features/user_informer.feature +63 -0
  30. data/generators/cloudtrapper/airbrake_generator.rb +94 -0
  31. data/generators/cloudtrapper/lib/insert_commands.rb +34 -0
  32. data/generators/cloudtrapper/lib/rake_commands.rb +24 -0
  33. data/generators/cloudtrapper/templates/capistrano_hook.rb +6 -0
  34. data/generators/cloudtrapper/templates/cloudtrapper_tasks.rake +25 -0
  35. data/generators/cloudtrapper/templates/initializer.rb +6 -0
  36. data/install.rb +1 -0
  37. data/lib/cloudtrapper/backtrace.rb +100 -0
  38. data/lib/cloudtrapper/capistrano.rb +44 -0
  39. data/lib/cloudtrapper/configuration.rb +281 -0
  40. data/lib/cloudtrapper/notice.rb +348 -0
  41. data/lib/cloudtrapper/rack.rb +55 -0
  42. data/lib/cloudtrapper/rails/action_controller_catcher.rb +30 -0
  43. data/lib/cloudtrapper/rails/controller_methods.rb +74 -0
  44. data/lib/cloudtrapper/rails/error_lookup.rb +33 -0
  45. data/lib/cloudtrapper/rails/javascript_notifier.rb +48 -0
  46. data/lib/cloudtrapper/rails/middleware/exceptions_catcher.rb +29 -0
  47. data/lib/cloudtrapper/rails.rb +40 -0
  48. data/lib/cloudtrapper/rails3_tasks.rb +85 -0
  49. data/lib/cloudtrapper/railtie.rb +48 -0
  50. data/lib/cloudtrapper/rake_handler.rb +66 -0
  51. data/lib/cloudtrapper/sender.rb +116 -0
  52. data/lib/cloudtrapper/shared_tasks.rb +36 -0
  53. data/lib/cloudtrapper/tasks.rb +83 -0
  54. data/lib/cloudtrapper/user_informer.rb +27 -0
  55. data/lib/cloudtrapper/version.rb +3 -0
  56. data/lib/cloudtrapper.rb +155 -0
  57. data/lib/cloudtrapper_tasks.rb +65 -0
  58. data/lib/rails/generators/cloudtrapper/cloudtrapper_generator.rb +100 -0
  59. data/lib/templates/javascript_notifier.erb +15 -0
  60. data/lib/templates/rescue.erb +91 -0
  61. data/rails/init.rb +1 -0
  62. data/resources/README.md +34 -0
  63. data/resources/ca-bundle.crt +3376 -0
  64. data/script/integration_test.rb +38 -0
  65. data/test/backtrace_test.rb +162 -0
  66. data/test/capistrano_test.rb +34 -0
  67. data/test/catcher_test.rb +333 -0
  68. data/test/cloudtrapper_2_2.xsd +78 -0
  69. data/test/cloudtrapper_tasks_test.rb +170 -0
  70. data/test/configuration_test.rb +221 -0
  71. data/test/helper.rb +263 -0
  72. data/test/javascript_notifier_test.rb +52 -0
  73. data/test/logger_test.rb +73 -0
  74. data/test/notice_test.rb +468 -0
  75. data/test/notifier_test.rb +246 -0
  76. data/test/rack_test.rb +58 -0
  77. data/test/rails_initializer_test.rb +36 -0
  78. data/test/recursion_test.rb +10 -0
  79. data/test/sender_test.rb +261 -0
  80. data/test/user_informer_test.rb +29 -0
  81. metadata +301 -0
@@ -0,0 +1,100 @@
1
+ module Cloudtrapper
2
+ # Front end to parsing the backtrace for each notice
3
+ class Backtrace
4
+
5
+ # Handles backtrace parsing line by line
6
+ class Line
7
+
8
+ # regexp (optionnally allowing leading X: for windows support)
9
+ INPUT_FORMAT = %r{^((?:[a-zA-Z]:)?[^:]+):(\d+)(?::in `([^']+)')?$}.freeze
10
+
11
+ # The file portion of the line (such as app/models/user.rb)
12
+ attr_reader :file
13
+
14
+ # The line number portion of the line
15
+ attr_reader :number
16
+
17
+ # The method of the line (such as index)
18
+ attr_reader :method
19
+
20
+ # Parses a single line of a given backtrace
21
+ # @param [String] unparsed_line The raw line from +caller+ or some backtrace
22
+ # @return [Line] The parsed backtrace line
23
+ def self.parse(unparsed_line)
24
+ _, file, number, method = unparsed_line.match(INPUT_FORMAT).to_a
25
+ new(file, number, method)
26
+ end
27
+
28
+ def initialize(file, number, method)
29
+ self.file = file
30
+ self.number = number
31
+ self.method = method
32
+ end
33
+
34
+ # Reconstructs the line in a readable fashion
35
+ def to_s
36
+ "#{file}:#{number}:in `#{method}'"
37
+ end
38
+
39
+ def ==(other)
40
+ to_s == other.to_s
41
+ end
42
+
43
+ def inspect
44
+ "<Line:#{to_s}>"
45
+ end
46
+
47
+ private
48
+
49
+ attr_writer :file, :number, :method
50
+ end
51
+
52
+ # holder for an Array of Backtrace::Line instances
53
+ attr_reader :lines
54
+
55
+ def self.parse(ruby_backtrace, opts = {})
56
+ ruby_lines = split_multiline_backtrace(ruby_backtrace)
57
+
58
+ filters = opts[:filters] || []
59
+ filtered_lines = ruby_lines.to_a.map do |line|
60
+ filters.inject(line) do |line, proc|
61
+ proc.call(line)
62
+ end
63
+ end.compact
64
+
65
+ lines = filtered_lines.collect do |unparsed_line|
66
+ Line.parse(unparsed_line)
67
+ end
68
+
69
+ instance = new(lines)
70
+ end
71
+
72
+ def initialize(lines)
73
+ self.lines = lines
74
+ end
75
+
76
+ def inspect
77
+ "<Backtrace: " + lines.collect { |line| line.inspect }.join(", ") + ">"
78
+ end
79
+
80
+ def ==(other)
81
+ if other.respond_to?(:lines)
82
+ lines == other.lines
83
+ else
84
+ false
85
+ end
86
+ end
87
+
88
+ private
89
+
90
+ attr_writer :lines
91
+
92
+ def self.split_multiline_backtrace(backtrace)
93
+ if backtrace.to_a.size == 1
94
+ backtrace.to_a.first.split(/\n\s*/)
95
+ else
96
+ backtrace
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,44 @@
1
+ # Defines deploy:notify_cloudtrapper which will send information about the deploy to Cloudtrapper.
2
+ require 'capistrano'
3
+
4
+ module Cloudtrapper
5
+ module Capistrano
6
+ def self.load_into(configuration)
7
+ configuration.load do
8
+ after "deploy", "cloudtrapper:deploy"
9
+ after "deploy:migrations", "cloudtrapper:deploy"
10
+
11
+ namespace :cloudtrapper do
12
+ desc <<-DESC
13
+ Notify Cloudtrapper of the deployment by running the notification on the REMOTE machine.
14
+ - Run remotely so we use remote API keys, environment, etc.
15
+ DESC
16
+ task :deploy, :except => { :no_release => true } do
17
+ rails_env = fetch(:rails_env, "production")
18
+ cloudtrapper_env = fetch(:cloudtrapper_env, fetch(:rails_env, "production"))
19
+ local_user = ENV['USER'] || ENV['USERNAME']
20
+ executable = RUBY_PLATFORM.downcase.include?('mswin') ? fetch(:rake, 'rake.bat') : fetch(:rake, 'rake')
21
+ directory = configuration.current_release
22
+ notify_command = "cd #{directory}; #{executable} RAILS_ENV=#{rails_env} cloudtrapper:deploy TO=#{cloudtrapper_env} REVISION=#{current_revision} REPO=#{repository} USER=#{local_user}"
23
+ notify_command << " DRY_RUN=true" if dry_run
24
+ notify_command << " API_KEY=#{ENV['API_KEY']}" if ENV['API_KEY']
25
+ logger.info "Notifying Cloudtrapper of Deploy (#{notify_command})"
26
+ if configuration.dry_run
27
+ logger.info "DRY RUN: Notification not actually run."
28
+ else
29
+ result = ""
30
+ run(notify_command, :once => true) { |ch, stream, data| result << data }
31
+ # TODO: Check if SSL is active on account via result content.
32
+ end
33
+ logger.info "Cloudtrapper Notification Complete."
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ if Capistrano::Configuration.instance
42
+ Cloudtrapper::Capistrano.load_into(Capistrano::Configuration.instance)
43
+ end
44
+
@@ -0,0 +1,281 @@
1
+ module Cloudtrapper
2
+ # Used to set up and modify settings for the notifier.
3
+ class Configuration
4
+
5
+ OPTIONS = [:api_key, :js_api_key, :backtrace_filters, :development_environments,
6
+ :development_lookup, :environment_name, :host,
7
+ :http_open_timeout, :http_read_timeout, :ignore, :ignore_by_filters,
8
+ :ignore_user_agent, :notifier_name, :notifier_url, :notifier_version,
9
+ :params_filters, :project_root, :port, :protocol, :proxy_host,
10
+ :proxy_pass, :proxy_port, :proxy_user, :secure, :use_system_ssl_cert_chain,
11
+ :framework, :user_information, :rescue_rake_exceptions].freeze
12
+
13
+ # The API key for your project, found on the project edit form.
14
+ attr_accessor :api_key
15
+
16
+ # If you're using the Javascript notifier and would want to separate
17
+ # Javascript notifications into another Cloudtrapper project, specify
18
+ # its APi key here.
19
+ # Defaults to #api_key (of the base project)
20
+ attr_writer :js_api_key
21
+
22
+ # The host to connect to (defaults to cloudtrapper.io).
23
+ attr_accessor :host
24
+
25
+ # The port on which your Cloudtrapper server runs (defaults to 443 for secure
26
+ # connections, 80 for insecure connections).
27
+ attr_accessor :port
28
+
29
+ # +true+ for https connections, +false+ for http connections.
30
+ attr_accessor :secure
31
+
32
+ # +true+ to use whatever CAs OpenSSL has installed on your system. +false+ to use the ca-bundle.crt file included in Cloudtrapper itself (reccomended and default)
33
+ attr_accessor :use_system_ssl_cert_chain
34
+
35
+ # The HTTP open timeout in seconds (defaults to 2).
36
+ attr_accessor :http_open_timeout
37
+
38
+ # The HTTP read timeout in seconds (defaults to 5).
39
+ attr_accessor :http_read_timeout
40
+
41
+ # The hostname of your proxy server (if using a proxy)
42
+ attr_accessor :proxy_host
43
+
44
+ # The port of your proxy server (if using a proxy)
45
+ attr_accessor :proxy_port
46
+
47
+ # The username to use when logging into your proxy server (if using a proxy)
48
+ attr_accessor :proxy_user
49
+
50
+ # The password to use when logging into your proxy server (if using a proxy)
51
+ attr_accessor :proxy_pass
52
+
53
+ # A list of parameters that should be filtered out of what is sent to Cloudtrapper.
54
+ # By default, all "password" attributes will have their contents replaced.
55
+ attr_reader :params_filters
56
+
57
+ # A list of filters for cleaning and pruning the backtrace. See #filter_backtrace.
58
+ attr_reader :backtrace_filters
59
+
60
+ # A list of filters for ignoring exceptions. See #ignore_by_filter.
61
+ attr_reader :ignore_by_filters
62
+
63
+ # A list of exception classes to ignore. The array can be appended to.
64
+ attr_reader :ignore
65
+
66
+ # A list of user agents that are being ignored. The array can be appended to.
67
+ attr_reader :ignore_user_agent
68
+
69
+ # A list of environments in which notifications should not be sent.
70
+ attr_accessor :development_environments
71
+
72
+ # +true+ if you want to check for production errors matching development errors, +false+ otherwise.
73
+ attr_accessor :development_lookup
74
+
75
+ # The name of the environment the application is running in
76
+ attr_accessor :environment_name
77
+
78
+ # The path to the project in which the error occurred, such as the Rails.root
79
+ attr_accessor :project_root
80
+
81
+ # The name of the notifier library being used to send notifications (such as "Cloudtrapper Notifier")
82
+ attr_accessor :notifier_name
83
+
84
+ # The version of the notifier library being used to send notifications (such as "1.0.2")
85
+ attr_accessor :notifier_version
86
+
87
+ # The url of the notifier library being used to send notifications
88
+ attr_accessor :notifier_url
89
+
90
+ # The logger used by Cloudtrapper
91
+ attr_accessor :logger
92
+
93
+ # The text that the placeholder is replaced with. {{error_id}} is the actual error number.
94
+ attr_accessor :user_information
95
+
96
+ # The framework Cloudtrapper is configured to use
97
+ attr_accessor :framework
98
+
99
+ # Should Cloudtrapper catch exceptions from Rake tasks?
100
+ # (boolean or nil; set to nil to catch exceptions when rake isn't running from a terminal; default is nil)
101
+ attr_accessor :rescue_rake_exceptions
102
+
103
+ DEFAULT_PARAMS_FILTERS = %w(password password_confirmation).freeze
104
+
105
+ DEFAULT_BACKTRACE_FILTERS = [
106
+ lambda { |line|
107
+ if defined?(Cloudtrapper.configuration.project_root) && Cloudtrapper.configuration.project_root.to_s != ''
108
+ line.sub(/#{Cloudtrapper.configuration.project_root}/, "[PROJECT_ROOT]")
109
+ else
110
+ line
111
+ end
112
+ },
113
+ lambda { |line| line.gsub(/^\.\//, "") },
114
+ lambda { |line|
115
+ if defined?(Gem)
116
+ Gem.path.inject(line) do |line, path|
117
+ line.gsub(/#{path}/, "[GEM_ROOT]")
118
+ end
119
+ end
120
+ },
121
+ lambda { |line| line if line !~ %r{lib/cloudtrapper} }
122
+ ].freeze
123
+
124
+ IGNORE_DEFAULT = ['ActiveRecord::RecordNotFound',
125
+ 'ActionController::RoutingError',
126
+ 'ActionController::InvalidAuthenticityToken',
127
+ 'CGI::Session::CookieStore::TamperedWithCookie',
128
+ 'ActionController::UnknownAction',
129
+ 'AbstractController::ActionNotFound',
130
+ 'Mongoid::Errors::DocumentNotFound']
131
+
132
+ alias_method :secure?, :secure
133
+ alias_method :use_system_ssl_cert_chain?, :use_system_ssl_cert_chain
134
+
135
+ def initialize
136
+ @secure = false
137
+ @use_system_ssl_cert_chain= false
138
+ @host = '127.0.0.1:3000/api'
139
+ @http_open_timeout = 2
140
+ @http_read_timeout = 5
141
+ @params_filters = DEFAULT_PARAMS_FILTERS.dup
142
+ @backtrace_filters = DEFAULT_BACKTRACE_FILTERS.dup
143
+ @ignore_by_filters = []
144
+ @ignore = IGNORE_DEFAULT.dup
145
+ @ignore_user_agent = []
146
+ @development_environments = %w(test cucumber)
147
+ @development_lookup = true
148
+ @notifier_name = 'Cloudtrapper Notifier'
149
+ @notifier_version = VERSION
150
+ @notifier_url = 'https://github.com/cloudtrapper/cloudtrapper'
151
+ @framework = 'Standalone'
152
+ @user_information = 'Cloudtrapper Error {{error_id}}'
153
+ @rescue_rake_exceptions = nil
154
+ end
155
+
156
+ # Takes a block and adds it to the list of backtrace filters. When the filters
157
+ # run, the block will be handed each line of the backtrace and can modify
158
+ # it as necessary.
159
+ #
160
+ # @example
161
+ # config.filter_bracktrace do |line|
162
+ # line.gsub(/^#{Rails.root}/, "[Rails.root]")
163
+ # end
164
+ #
165
+ # @param [Proc] block The new backtrace filter.
166
+ # @yieldparam [String] line A line in the backtrace.
167
+ def filter_backtrace(&block)
168
+ self.backtrace_filters << block
169
+ end
170
+
171
+ # Takes a block and adds it to the list of ignore filters.
172
+ # When the filters run, the block will be handed the exception.
173
+ # @example
174
+ # config.ignore_by_filter do |exception_data|
175
+ # true if exception_data[:error_class] == "RuntimeError"
176
+ # end
177
+ #
178
+ # @param [Proc] block The new ignore filter
179
+ # @yieldparam [Hash] data The exception data given to +Cloudtrapper.notify+
180
+ # @yieldreturn [Boolean] If the block returns true the exception will be ignored, otherwise it will be processed by cloudtrapper.
181
+ def ignore_by_filter(&block)
182
+ self.ignore_by_filters << block
183
+ end
184
+
185
+ # Overrides the list of default ignored errors.
186
+ #
187
+ # @param [Array<Exception>] names A list of exceptions to ignore.
188
+ def ignore_only=(names)
189
+ @ignore = [names].flatten
190
+ end
191
+
192
+ # Overrides the list of default ignored user agents
193
+ #
194
+ # @param [Array<String>] A list of user agents to ignore
195
+ def ignore_user_agent_only=(names)
196
+ @ignore_user_agent = [names].flatten
197
+ end
198
+
199
+ # Allows config options to be read like a hash
200
+ #
201
+ # @param [Symbol] option Key for a given attribute
202
+ def [](option)
203
+ send(option)
204
+ end
205
+
206
+ # Returns a hash of all configurable options
207
+ def to_hash
208
+ OPTIONS.inject({}) do |hash, option|
209
+ hash[option.to_sym] = self.send(option)
210
+ hash
211
+ end
212
+ end
213
+
214
+ # Returns a hash of all configurable options merged with +hash+
215
+ #
216
+ # @param [Hash] hash A set of configuration options that will take precedence over the defaults
217
+ def merge(hash)
218
+ to_hash.merge(hash)
219
+ end
220
+
221
+ # Determines if the notifier will send notices.
222
+ # @return [Boolean] Returns +false+ if in a development environment, +true+ otherwise.
223
+ def public?
224
+ !development_environments.include?(environment_name)
225
+ end
226
+
227
+ def port
228
+ @port || default_port
229
+ end
230
+
231
+ # Determines whether protocol should be "http" or "https".
232
+ # @return [String] Returns +"http"+ if you've set secure to +false+ in
233
+ # configuration, and +"https"+ otherwise.
234
+ def protocol
235
+ if secure?
236
+ 'https'
237
+ else
238
+ 'http'
239
+ end
240
+ end
241
+
242
+ def js_api_key
243
+ @js_api_key || self.api_key
244
+ end
245
+
246
+ def js_notifier=(*args)
247
+ warn '[AIRBRAKE] config.js_notifier has been deprecated and has no effect. You should use <%= cloudtrapper_javascript_notifier %> directly at the top of your layouts. Be sure to place it before all other javascript.'
248
+ end
249
+
250
+ def environment_filters
251
+ warn 'config.environment_filters has been deprecated and has no effect.'
252
+ []
253
+ end
254
+
255
+ def ca_bundle_path
256
+ if use_system_ssl_cert_chain? && File.exist?(OpenSSL::X509::DEFAULT_CERT_FILE)
257
+ OpenSSL::X509::DEFAULT_CERT_FILE
258
+ else
259
+ local_cert_path # ca-bundle.crt built from source, see resources/README.md
260
+ end
261
+ end
262
+
263
+ def local_cert_path
264
+ File.expand_path(File.join("..", "..", "..", "resources", "ca-bundle.crt"), __FILE__)
265
+ end
266
+
267
+ private
268
+ # Determines what port should we use for sending notices.
269
+ # @return [Fixnum] Returns 443 if you've set secure to true in your
270
+ # configuration, and 80 otherwise.
271
+ def default_port
272
+ if secure?
273
+ 443
274
+ else
275
+ 80
276
+ end
277
+ end
278
+
279
+ end
280
+
281
+ end