cloudtrapper 0.0.2.pre

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