airbrake 3.1.12 → 3.1.13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/CHANGELOG +61 -0
  2. data/README.md +36 -17
  3. data/Rakefile +14 -8
  4. data/TESTED_AGAINST +1 -0
  5. data/airbrake.gemspec +3 -3
  6. data/features/metal.feature +1 -1
  7. data/features/rails.feature +1 -0
  8. data/features/rake.feature +4 -0
  9. data/features/sinatra.feature +72 -1
  10. data/features/support/rake/Rakefile +14 -4
  11. data/generators/airbrake/airbrake_generator.rb +5 -5
  12. data/lib/airbrake.rb +2 -9
  13. data/lib/airbrake/backtrace.rb +8 -13
  14. data/lib/airbrake/capistrano.rb +58 -2
  15. data/lib/airbrake/cli/runner.rb +1 -0
  16. data/lib/airbrake/configuration.rb +31 -11
  17. data/lib/airbrake/notice.rb +52 -61
  18. data/lib/airbrake/rails/controller_methods.rb +7 -3
  19. data/lib/airbrake/rails/javascript_notifier.rb +56 -31
  20. data/lib/airbrake/rake_handler.rb +3 -2
  21. data/lib/airbrake/response.rb +6 -8
  22. data/lib/airbrake/sender.rb +1 -1
  23. data/lib/airbrake/sinatra.rb +9 -2
  24. data/lib/airbrake/user_informer.rb +1 -1
  25. data/lib/airbrake/version.rb +1 -1
  26. data/lib/airbrake_tasks.rb +2 -2
  27. data/lib/rails/generators/airbrake/airbrake_generator.rb +5 -5
  28. data/lib/templates/javascript_notifier_configuration +12 -0
  29. data/lib/templates/javascript_notifier_loader +6 -0
  30. data/resources/notice.xml +2 -1
  31. data/test/airbrake_tasks_test.rb +2 -2
  32. data/test/backtrace_test.rb +19 -0
  33. data/test/capistrano_test.rb +9 -1
  34. data/test/configuration_test.rb +49 -2
  35. data/test/controller_methods_test.rb +22 -0
  36. data/test/helper.rb +29 -155
  37. data/test/integration.rb +15 -0
  38. data/test/{catcher_test.rb → integration/catcher_test.rb} +130 -19
  39. data/test/{javascript_notifier_test.rb → integration/javascript_notifier_test.rb} +0 -1
  40. data/test/logger_test.rb +11 -11
  41. data/test/notice_test.rb +8 -2
  42. data/test/notifier_test.rb +6 -6
  43. data/test/sender_test.rb +1 -1
  44. metadata +34 -31
  45. data/lib/templates/javascript_notifier +0 -20
@@ -26,9 +26,9 @@ module Airbrake
26
26
  end
27
27
 
28
28
  def initialize(file, number, method_name)
29
- self.file = file
30
- self.number = number
31
- self.method_name = method_name
29
+ @file = file
30
+ @number = number
31
+ @method_name = method_name
32
32
  end
33
33
 
34
34
  # Reconstructs the line in a readable fashion
@@ -43,10 +43,6 @@ module Airbrake
43
43
  def inspect
44
44
  "<Line:#{to_s}>"
45
45
  end
46
-
47
- private
48
-
49
- attr_writer :file, :number, :method_name
50
46
  end
51
47
 
52
48
  # holder for an Array of Backtrace::Line instances
@@ -57,8 +53,8 @@ module Airbrake
57
53
 
58
54
  filters = opts[:filters] || []
59
55
  filtered_lines = ruby_lines.to_a.map do |line|
60
- filters.inject(line) do |line, proc|
61
- proc.call(line)
56
+ filters.inject(line) do |l, proc|
57
+ proc.call(l)
62
58
  end
63
59
  end.compact
64
60
 
@@ -66,11 +62,11 @@ module Airbrake
66
62
  Line.parse(unparsed_line)
67
63
  end
68
64
 
69
- instance = new(lines)
65
+ new(lines)
70
66
  end
71
67
 
72
68
  def initialize(lines)
73
- self.lines = lines
69
+ @lines = lines
74
70
  end
75
71
 
76
72
  def inspect
@@ -95,9 +91,8 @@ module Airbrake
95
91
 
96
92
  private
97
93
 
98
- attr_writer :lines
99
-
100
94
  def self.split_multiline_backtrace(backtrace)
95
+ backtrace = [backtrace] unless backtrace.respond_to?(:to_a)
101
96
  if backtrace.to_a.size == 1
102
97
  backtrace.to_a.first.split(/\n\s*/)
103
98
  else
@@ -3,6 +3,62 @@ require 'capistrano'
3
3
 
4
4
  module Airbrake
5
5
  module Capistrano
6
+ # What follows is a copy-paste backport of the shellescape method
7
+ # included in Ruby 1.9 and greater. The FSF's guidance on a snippet
8
+ # of this size indicates that such a small function is not subject
9
+ # to copyright and as such there is no risk of a license conflict:
10
+ # See www.gnu.org/prep/maintain/maintain.html#Legally-Significant
11
+ #
12
+ # Escapes a string so that it can be safely used in a Bourne shell
13
+ # command line. +str+ can be a non-string object that responds to
14
+ # +to_s+.
15
+ #
16
+ # Note that a resulted string should be used unquoted and is not
17
+ # intended for use in double quotes nor in single quotes.
18
+ #
19
+ # argv = Shellwords.escape("It's better to give than to receive")
20
+ # argv #=> "It\\'s\\ better\\ to\\ give\\ than\\ to\\ receive"
21
+ #
22
+ # String#shellescape is a shorthand for this function.
23
+ #
24
+ # argv = "It's better to give than to receive".shellescape
25
+ # argv #=> "It\\'s\\ better\\ to\\ give\\ than\\ to\\ receive"
26
+ #
27
+ # # Search files in lib for method definitions
28
+ # pattern = "^[ \t]*def "
29
+ # open("| grep -Ern #{pattern.shellescape} lib") { |grep|
30
+ # grep.each_line { |line|
31
+ # file, lineno, matched_line = line.split(':', 3)
32
+ # # ...
33
+ # }
34
+ # }
35
+ #
36
+ # It is the caller's responsibility to encode the string in the right
37
+ # encoding for the shell environment where this string is used.
38
+ #
39
+ # Multibyte characters are treated as multibyte characters, not bytes.
40
+ #
41
+ # Returns an empty quoted String if +str+ has a length of zero.
42
+ def self.shellescape(str)
43
+ str = str.to_s
44
+
45
+ # An empty argument will be skipped, so return empty quotes.
46
+ return "''" if str.empty?
47
+
48
+ str = str.dup
49
+
50
+ # Treat multibyte characters as is. It is caller's responsibility
51
+ # to encode the string in the right encoding for the shell
52
+ # environment.
53
+ str.gsub!(/([^A-Za-z0-9_\-.,:\/@\n])/, "\\\\\\1")
54
+
55
+ # A LF cannot be escaped with a backslash because a backslash + LF
56
+ # combo is regarded as line continuation and simply ignored.
57
+ str.gsub!(/\n/, "'\n'")
58
+
59
+ return str
60
+ end
61
+
6
62
  def self.load_into(configuration)
7
63
  configuration.load do
8
64
  after "deploy", "airbrake:deploy"
@@ -18,9 +74,9 @@ module Airbrake
18
74
  rails_env = fetch(:rails_env, "production")
19
75
  airbrake_env = fetch(:airbrake_env, fetch(:rails_env, "production"))
20
76
  local_user = ENV['USER'] || ENV['USERNAME']
21
- executable = RUBY_PLATFORM.downcase.include?('mswin') ? fetch(:rake, 'rake.bat') : fetch(:rake, 'rake')
77
+ executable = RUBY_PLATFORM.downcase.include?('mswin') ? fetch(:rake, 'rake.bat') : fetch(:rake, 'bundle exec rake ')
22
78
  directory = configuration.release_path
23
- notify_command = "cd #{directory}; #{executable} RAILS_ENV=#{rails_env} airbrake:deploy TO=#{airbrake_env} REVISION=#{current_revision} REPO=#{repository} USER=#{local_user}"
79
+ notify_command = "cd #{directory}; #{executable} RAILS_ENV=#{rails_env} airbrake:deploy TO=#{airbrake_env} REVISION=#{current_revision} REPO=#{repository} USER=#{Airbrake::Capistrano::shellescape(local_user)}"
24
80
  notify_command << " DRY_RUN=true" if dry_run
25
81
  notify_command << " API_KEY=#{ENV['API_KEY']}" if ENV['API_KEY']
26
82
  logger.info "Notifying Airbrake of Deploy (#{notify_command})"
@@ -22,6 +22,7 @@ module Runner
22
22
  c.api_key = options.api_key
23
23
  c.host = options.host if options.host
24
24
  c.port = options.port if options.port
25
+ c.secure = options.port.to_i == 443
25
26
  end
26
27
  exception_id = Airbrake.notify(:error_class => options.error,
27
28
  :error_message => "#{options.error}: #{options.message}",
@@ -25,7 +25,7 @@ module Airbrake
25
25
 
26
26
  # The port on which your Airbrake server runs (defaults to 443 for secure
27
27
  # connections, 80 for insecure connections).
28
- attr_accessor :port
28
+ attr_writer :port
29
29
 
30
30
  # +true+ for https connections, +false+ for http connections.
31
31
  attr_accessor :secure
@@ -65,9 +65,12 @@ module Airbrake
65
65
  # Empty by default and used only in rake handler
66
66
  attr_reader :rake_environment_filters
67
67
 
68
- # A list of exception classes to ignore. The array can be appended to.
68
+ # A list of exception classes to ignore during server requests. The array can be appended to.
69
69
  attr_reader :ignore
70
70
 
71
+ # A list of exception classes to ignore during Rake tasks. The array can be appended to.
72
+ attr_reader :ignore_rake
73
+
71
74
  # A list of user agents that are being ignored. The array can be appended to.
72
75
  attr_reader :ignore_user_agent
73
76
 
@@ -110,7 +113,7 @@ module Airbrake
110
113
  attr_reader :user_attributes
111
114
 
112
115
  # Only used for JSON API
113
- attr_accessor :project_id
116
+ attr_reader :project_id
114
117
 
115
118
  # Setting this to true will use the CollectingSender instead of
116
119
  # the default one which will cause storing the last notice locally
@@ -134,8 +137,8 @@ module Airbrake
134
137
  lambda { |line| line.gsub(/^\.\//, "") },
135
138
  lambda { |line|
136
139
  if defined?(Gem)
137
- Gem.path.inject(line) do |line, path|
138
- line.gsub(/#{path}/, "[GEM_ROOT]")
140
+ Gem.path.inject(line) do |l, path|
141
+ l.gsub(/#{path}/, "[GEM_ROOT]")
139
142
  end
140
143
  end
141
144
  },
@@ -155,15 +158,18 @@ module Airbrake
155
158
  alias_method :use_system_ssl_cert_chain?, :use_system_ssl_cert_chain
156
159
 
157
160
  def initialize
161
+ @js_api_key = nil
158
162
  @secure = false
159
163
  @use_system_ssl_cert_chain= false
160
164
  @host = 'api.airbrake.io'
165
+ @port = nil
161
166
  @http_open_timeout = 2
162
167
  @http_read_timeout = 5
163
168
  @params_filters = DEFAULT_PARAMS_FILTERS.dup
164
169
  @backtrace_filters = DEFAULT_BACKTRACE_FILTERS.dup
165
- @ignore_by_filters = []
170
+ @ignore_by_filters = [] # These filters are applied to both server requests and Rake tasks
166
171
  @ignore = IGNORE_DEFAULT.dup
172
+ @ignore_rake = [] # Rake tasks don't ignore any exception classes by default
167
173
  @ignore_user_agent = []
168
174
  @development_environments = %w(development test cucumber)
169
175
  @development_lookup = true
@@ -175,6 +181,7 @@ module Airbrake
175
181
  @rescue_rake_exceptions = nil
176
182
  @user_attributes = DEFAULT_USER_ATTRIBUTES.dup
177
183
  @rake_environment_filters = []
184
+ @async = nil
178
185
  end
179
186
 
180
187
  # Takes a block and adds it to the list of backtrace filters. When the filters
@@ -213,6 +220,13 @@ module Airbrake
213
220
  @ignore = [names].flatten
214
221
  end
215
222
 
223
+ # Overrides the list of default ignored errors during Rake tasks.
224
+ #
225
+ # @param [Array<Exception>] names A list of rake exceptions to ignore.
226
+ def ignore_rake_only=(names)
227
+ @ignore_rake = [names].flatten
228
+ end
229
+
216
230
  # Overrides the list of default ignored user agents
217
231
  #
218
232
  # @param [Array<String>] A list of user agents to ignore
@@ -304,6 +318,10 @@ module Airbrake
304
318
  File.expand_path(File.join("..", "..", "..", "resources", "ca-bundle.crt"), __FILE__)
305
319
  end
306
320
 
321
+ def project_id=(project_id)
322
+ @project_id = "#{project_id}"
323
+ end
324
+
307
325
  private
308
326
  # Determines what port should we use for sending notices.
309
327
  # @return [Fixnum] Returns 443 if you've set secure to true in your
@@ -328,11 +346,13 @@ module Airbrake
328
346
  end
329
347
 
330
348
  def validate_user_attributes(user_attributes)
331
- user_attributes.each do |attribute|
332
- next if VALID_USER_ATTRIBUTES.include? attribute
333
- warn "[AIRBRAKE] Unsupported user attribute: '#{attribute}'. "\
334
- "This attribute will not be shown in the Airbrake UI. "\
335
- "Check http://git.io/h6YRpA for more info."
349
+ user_attributes.reject do |attribute|
350
+ unless VALID_USER_ATTRIBUTES.include? attribute.to_s
351
+ warn "[AIRBRAKE] Unsupported user attribute: '#{attribute}'. "\
352
+ "This attribute will not be shown in the Airbrake UI. "\
353
+ "Check http://git.io/h6YRpA for more info."
354
+ true
355
+ end
336
356
  end
337
357
  end
338
358
  end
@@ -87,52 +87,40 @@ module Airbrake
87
87
  # Details about the user who experienced the error
88
88
  attr_reader :user
89
89
 
90
- private
91
-
92
- # Private writers for all the attributes
93
- attr_writer :exception, :api_key, :backtrace, :error_class, :error_message,
94
- :backtrace_filters, :parameters, :params_filters,
95
- :session_data, :project_root, :url, :ignore,
96
- :ignore_by_filters, :notifier_name, :notifier_url, :notifier_version,
97
- :component, :action, :cgi_data, :environment_name, :hostname, :user
98
-
99
- # Arguments given in the initializer
100
- attr_accessor :args
101
-
102
90
  public
103
91
 
104
92
  def initialize(args)
105
- self.args = args
106
- self.exception = args[:exception]
107
- self.api_key = args[:api_key]
108
- self.project_root = args[:project_root]
109
- self.url = args[:url] || rack_env(:url)
110
-
111
- self.notifier_name = args[:notifier_name]
112
- self.notifier_version = args[:notifier_version]
113
- self.notifier_url = args[:notifier_url]
114
-
115
- self.ignore = args[:ignore] || []
116
- self.ignore_by_filters = args[:ignore_by_filters] || []
117
- self.backtrace_filters = args[:backtrace_filters] || []
118
- self.params_filters = args[:params_filters] || []
119
- self.parameters = args[:parameters] ||
93
+ @args = args
94
+ @exception = args[:exception]
95
+ @api_key = args[:api_key]
96
+ @project_root = args[:project_root]
97
+ @url = args[:url] || rack_env(:url)
98
+
99
+ @notifier_name = args[:notifier_name]
100
+ @notifier_version = args[:notifier_version]
101
+ @notifier_url = args[:notifier_url]
102
+
103
+ @ignore = args[:ignore] || []
104
+ @ignore_by_filters = args[:ignore_by_filters] || []
105
+ @backtrace_filters = args[:backtrace_filters] || []
106
+ @params_filters = args[:params_filters] || []
107
+ @parameters = args[:parameters] ||
120
108
  action_dispatch_params ||
121
109
  rack_env(:params) ||
122
110
  {}
123
- self.component = args[:component] || args[:controller] || parameters['controller']
124
- self.action = args[:action] || parameters['action']
125
-
126
- self.environment_name = args[:environment_name]
127
- self.cgi_data = args[:cgi_data] || args[:rack_env]
128
- self.backtrace = Backtrace.parse(exception_attribute(:backtrace, caller), :filters => self.backtrace_filters)
129
- self.error_class = exception_attribute(:error_class) {|exception| exception.class.name }
130
- self.error_message = exception_attribute(:error_message, 'Notification') do |exception|
111
+ @component = args[:component] || args[:controller] || parameters['controller']
112
+ @action = args[:action] || parameters['action']
113
+
114
+ @environment_name = args[:environment_name]
115
+ @cgi_data = (args[:cgi_data] && args[:cgi_data].dup) || args[:rack_env] || {}
116
+ @backtrace = Backtrace.parse(exception_attribute(:backtrace, caller), :filters => @backtrace_filters)
117
+ @error_class = exception_attribute(:error_class) {|exception| exception.class.name }
118
+ @error_message = exception_attribute(:error_message, 'Notification') do |exception|
131
119
  "#{exception.class.name}: #{args[:error_message] || exception.message}"
132
120
  end
133
121
 
134
- self.hostname = local_hostname
135
- self.user = args[:user]
122
+ @hostname = local_hostname
123
+ @user = args[:user] || {}
136
124
 
137
125
  also_use_rack_params_filters
138
126
  find_session_data
@@ -164,27 +152,22 @@ module Airbrake
164
152
  end
165
153
  end
166
154
  end
167
- if url ||
168
- controller ||
169
- action ||
170
- !parameters.blank? ||
171
- !cgi_data.blank? ||
172
- !session_data.blank?
155
+ if request_present?
173
156
  notice.request do |request|
174
157
  request.url(url)
175
158
  request.component(controller)
176
159
  request.action(action)
177
- unless parameters.blank?
160
+ unless parameters.empty?
178
161
  request.params do |params|
179
162
  xml_vars_for(params, parameters)
180
163
  end
181
164
  end
182
- unless session_data.blank?
165
+ unless session_data.empty?
183
166
  request.session do |session|
184
167
  xml_vars_for(session, session_data)
185
168
  end
186
169
  end
187
- unless cgi_data.blank?
170
+ unless cgi_data.empty?
188
171
  request.tag!("cgi-data") do |cgi_datum|
189
172
  xml_vars_for(cgi_datum, cgi_data)
190
173
  end
@@ -196,14 +179,14 @@ module Airbrake
196
179
  env.tag!("environment-name", environment_name)
197
180
  env.tag!("hostname", hostname)
198
181
  end
199
- unless user.blank?
182
+ unless user.empty?
200
183
  notice.tag!("current-user") do |u|
201
184
  user.each do |attr, value|
202
185
  u.tag!(attr.to_s, value)
203
186
  end
204
187
  end
205
188
  end
206
- unless framework.blank?
189
+ if framework =~ /\S/
207
190
  notice.tag!("framework", framework)
208
191
  end
209
192
  end
@@ -229,7 +212,7 @@ module Airbrake
229
212
  end
230
213
  }],
231
214
  'context' => {}.tap do |hash|
232
- if url || controller || action || !parameters.blank? || !cgi_data.blank? || !session_data.blank?
215
+ if request_present?
233
216
  hash['url'] = url
234
217
  hash['component'] = controller
235
218
  hash['action'] = action
@@ -237,7 +220,7 @@ module Airbrake
237
220
  hash['environment'] = environment_name
238
221
  end
239
222
  end.tap do |hash|
240
- next if user.blank?
223
+ next if user.empty?
241
224
 
242
225
  hash['userId'] = user[:id]
243
226
  hash['userName'] = user[:name]
@@ -245,9 +228,9 @@ module Airbrake
245
228
  end
246
229
 
247
230
  }.tap do |hash|
248
- hash['environment'] = cgi_data unless cgi_data.blank?
249
- hash['params'] = parameters unless parameters.blank?
250
- hash['session'] = session_data unless session_data.blank?
231
+ hash['environment'] = cgi_data unless cgi_data.empty?
232
+ hash['params'] = parameters unless parameters.empty?
233
+ hash['session'] = session_data unless session_data.empty?
251
234
  end.to_json
252
235
  end
253
236
 
@@ -274,6 +257,14 @@ module Airbrake
274
257
 
275
258
  private
276
259
 
260
+ def request_present?
261
+ url ||
262
+ controller ||
263
+ action ||
264
+ !parameters.empty? ||
265
+ !cgi_data.empty? ||
266
+ !session_data.empty?
267
+ end
277
268
 
278
269
  # Gets a property named +attribute+ of an exception, either from an actual
279
270
  # exception or a hash.
@@ -283,7 +274,7 @@ module Airbrake
283
274
  #
284
275
  # If no exception or hash key is available, +default+ will be used.
285
276
  def exception_attribute(attribute, default = nil, &block)
286
- (exception && from_exception(attribute, &block)) || args[attribute] || default
277
+ (exception && from_exception(attribute, &block)) || @args[attribute] || default
287
278
  end
288
279
 
289
280
  # Gets a property named +attribute+ from an exception.
@@ -305,7 +296,7 @@ module Airbrake
305
296
  # Removes non-serializable data from the given attribute.
306
297
  # See #clean_unserializable_data
307
298
  def clean_unserializable_data_from(attribute)
308
- self.send(:"#{attribute}=", clean_unserializable_data(send(attribute)))
299
+ self.instance_variable_set("@#{attribute}", clean_unserializable_data(send(attribute)))
309
300
  end
310
301
 
311
302
  # Removes non-serializable data. Allowed data types are strings, arrays,
@@ -368,8 +359,8 @@ module Airbrake
368
359
  end
369
360
 
370
361
  def find_session_data
371
- self.session_data = args[:session_data] || args[:session] || rack_session || {}
372
- self.session_data = session_data[:data] if session_data[:data]
362
+ @session_data = @args[:session_data] || @args[:session] || rack_session || {}
363
+ @session_data = session_data[:data] if session_data[:data]
373
364
  end
374
365
 
375
366
  # Converts the mixed class instances and class names into just names
@@ -401,17 +392,17 @@ module Airbrake
401
392
  end
402
393
 
403
394
  def rack_request
404
- @rack_request ||= if args[:rack_env]
405
- ::Rack::Request.new(args[:rack_env])
395
+ @rack_request ||= if @args[:rack_env]
396
+ ::Rack::Request.new(@args[:rack_env])
406
397
  end
407
398
  end
408
399
 
409
400
  def action_dispatch_params
410
- args[:rack_env]['action_dispatch.request.parameters'] if args[:rack_env]
401
+ @args[:rack_env]['action_dispatch.request.parameters'] if @args[:rack_env]
411
402
  end
412
403
 
413
404
  def rack_session
414
- args[:rack_env]['rack.session'] if args[:rack_env]
405
+ @args[:rack_env]['rack.session'] if @args[:rack_env]
415
406
  end
416
407
 
417
408
  def also_use_rack_params_filters