rollbar 2.10.0 → 2.11.0

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 (84) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +7 -2
  3. data/CHANGELOG.md +20 -0
  4. data/README.md +73 -16
  5. data/docs/configuration.md +10 -0
  6. data/gemfiles/rails30.gemfile +2 -0
  7. data/gemfiles/rails31.gemfile +2 -0
  8. data/gemfiles/rails32.gemfile +2 -0
  9. data/gemfiles/rails40.gemfile +2 -0
  10. data/gemfiles/rails41.gemfile +2 -0
  11. data/gemfiles/rails42.gemfile +2 -0
  12. data/gemfiles/rails50.gemfile +2 -0
  13. data/gemfiles/ruby_1_8_and_1_9_2.gemfile +43 -0
  14. data/lib/rollbar.rb +139 -353
  15. data/lib/rollbar/configuration.rb +4 -0
  16. data/lib/rollbar/item.rb +225 -0
  17. data/lib/rollbar/item/backtrace.rb +97 -0
  18. data/lib/rollbar/js.rb +0 -28
  19. data/lib/rollbar/language_support.rb +10 -0
  20. data/lib/rollbar/{js/middleware.rb → middleware/js.rb} +3 -4
  21. data/lib/rollbar/plugin.rb +63 -0
  22. data/lib/rollbar/plugins.rb +41 -0
  23. data/lib/rollbar/{active_job.rb → plugins/active_job.rb} +0 -0
  24. data/lib/rollbar/plugins/basic_socket.rb +16 -0
  25. data/lib/rollbar/plugins/delayed_job.rb +12 -0
  26. data/lib/rollbar/plugins/delayed_job/job_data.rb +16 -0
  27. data/lib/rollbar/{delayed_job.rb → plugins/delayed_job/plugin.rb} +1 -17
  28. data/lib/rollbar/plugins/goalie.rb +46 -0
  29. data/lib/rollbar/plugins/rack.rb +16 -0
  30. data/lib/rollbar/plugins/rails.rb +77 -0
  31. data/lib/rollbar/{rails → plugins/rails}/controller_methods.rb +0 -0
  32. data/lib/rollbar/plugins/rails/railtie30.rb +17 -0
  33. data/lib/rollbar/plugins/rails/railtie32.rb +18 -0
  34. data/lib/rollbar/plugins/rails/railtie_mixin.rb +33 -0
  35. data/lib/rollbar/plugins/rake.rb +45 -0
  36. data/lib/rollbar/plugins/sidekiq.rb +35 -0
  37. data/lib/rollbar/{sidekiq.rb → plugins/sidekiq/plugin.rb} +0 -18
  38. data/lib/rollbar/plugins/thread.rb +13 -0
  39. data/lib/rollbar/plugins/validations.rb +33 -0
  40. data/lib/rollbar/request_data_extractor.rb +30 -18
  41. data/lib/rollbar/scrubbers/params.rb +4 -2
  42. data/lib/rollbar/scrubbers/url.rb +30 -28
  43. data/lib/rollbar/util.rb +10 -0
  44. data/lib/rollbar/version.rb +1 -1
  45. data/spec/controllers/home_controller_spec.rb +4 -3
  46. data/spec/dummyapp/app/models/post.rb +9 -0
  47. data/spec/dummyapp/app/models/user.rb +2 -0
  48. data/spec/dummyapp/config/initializers/rollbar.rb +1 -0
  49. data/spec/fixtures/plugins/dummy1.rb +5 -0
  50. data/spec/fixtures/plugins/dummy2.rb +5 -0
  51. data/spec/rollbar/item_spec.rb +635 -0
  52. data/spec/rollbar/logger_proxy_spec.rb +4 -0
  53. data/spec/rollbar/{js/middleware_spec.rb → middleware/js_spec.rb} +32 -3
  54. data/spec/rollbar/plugin_spec.rb +147 -0
  55. data/spec/rollbar/{active_job_spec.rb → plugins/active_job_spec.rb} +0 -1
  56. data/spec/rollbar/{delayed_job → plugins/delayed_job}/job_data.rb +0 -0
  57. data/spec/rollbar/{delayed_job_spec.rb → plugins/delayed_job_spec.rb} +3 -6
  58. data/spec/rollbar/{middleware/rack/builder_spec.rb → plugins/rack_spec.rb} +2 -1
  59. data/spec/rollbar/{js/frameworks/rails_spec.rb → plugins/rails_js_spec.rb} +1 -1
  60. data/spec/rollbar/{rake_spec.rb → plugins/rake_spec.rb} +2 -1
  61. data/spec/rollbar/{sidekiq_spec.rb → plugins/sidekiq_spec.rb} +2 -1
  62. data/spec/rollbar/plugins/validations_spec.rb +43 -0
  63. data/spec/rollbar/plugins_spec.rb +68 -0
  64. data/spec/rollbar/request_data_extractor_spec.rb +56 -10
  65. data/spec/rollbar/scrubbers/params_spec.rb +13 -10
  66. data/spec/rollbar/scrubbers/url_spec.rb +17 -12
  67. data/spec/rollbar/sidekig/clear_scope_spec.rb +2 -1
  68. data/spec/rollbar/util_spec.rb +61 -0
  69. data/spec/rollbar_bc_spec.rb +10 -10
  70. data/spec/rollbar_spec.rb +57 -706
  71. data/spec/spec_helper.rb +8 -0
  72. data/spec/support/notifier_helpers.rb +1 -0
  73. data/spec/support/rollbar_api.rb +57 -0
  74. metadata +57 -33
  75. data/lib/rollbar/active_record_extension.rb +0 -14
  76. data/lib/rollbar/core_ext/basic_socket.rb +0 -7
  77. data/lib/rollbar/core_ext/thread.rb +0 -9
  78. data/lib/rollbar/goalie.rb +0 -33
  79. data/lib/rollbar/js/frameworks.rb +0 -6
  80. data/lib/rollbar/js/frameworks/rails.rb +0 -49
  81. data/lib/rollbar/js/version.rb +0 -5
  82. data/lib/rollbar/rack.rb +0 -9
  83. data/lib/rollbar/railtie.rb +0 -46
  84. data/lib/rollbar/rake.rb +0 -40
@@ -0,0 +1,13 @@
1
+ Rollbar.plugins.define('thread') do
2
+ execute do
3
+ Thread.class_eval do
4
+ def initialize_with_rollbar(*args, &block)
5
+ self[:_rollbar_notifier] ||= Rollbar.notifier.scope
6
+ initialize_without_rollbar(*args, &block)
7
+ end
8
+
9
+ alias_method :initialize_without_rollbar, :initialize
10
+ alias_method :initialize, :initialize_with_rollbar
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,33 @@
1
+ Rollbar.plugins.define('active_model') do
2
+ dependency { !configuration.disable_monkey_patch }
3
+ dependency { defined?(ActiveModel::Validations) }
4
+ dependency do
5
+ require 'active_record/version'
6
+
7
+ ActiveModel::VERSION::MAJOR >= 3
8
+ end
9
+
10
+ execute do
11
+ module Rollbar
12
+ # Module that defines methods to be used by instances using
13
+ # ActiveModel::Validations
14
+ # The name is ActiveRecordExtension in order to not break backwards
15
+ # compatibility, although probably it should be named
16
+ # Rollbar::ValidationsExtension or similar
17
+ module ActiveRecordExtension
18
+ def report_validation_errors_to_rollbar
19
+ errors.full_messages.each do |error|
20
+ Rollbar.log_info "[Rollbar] Reporting form validation error: #{error} for #{self}"
21
+ Rollbar.warning("Form Validation Error: #{error} for #{self}")
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ execute do
29
+ ActiveModel::Validations.module_eval do
30
+ include Rollbar::ActiveRecordExtension
31
+ end
32
+ end
33
+ end
@@ -23,20 +23,15 @@ module Rollbar
23
23
  rack_req = ::Rack::Request.new(env)
24
24
 
25
25
  sensitive_params = sensitive_params_list(env)
26
- request_params = rollbar_filtered_params(sensitive_params, rollbar_request_params(env))
27
- get_params = rollbar_filtered_params(sensitive_params, rollbar_get_params(rack_req))
28
- post_params = rollbar_filtered_params(sensitive_params, rollbar_post_params(rack_req))
29
- raw_body_params = rollbar_filtered_params(sensitive_params, mergeable_raw_body_params(rack_req))
30
- cookies = rollbar_filtered_params(sensitive_params, rollbar_request_cookies(rack_req))
31
- session = rollbar_filtered_params(sensitive_params, rollbar_request_session(rack_req))
32
- route_params = rollbar_filtered_params(sensitive_params, rollbar_route_params(env))
33
-
34
- url_scrubber = Rollbar::Scrubbers::URL.new(:scrub_fields => sensitive_params,
35
- :scrub_user => Rollbar.configuration.scrub_user,
36
- :scrub_password => Rollbar.configuration.scrub_password,
37
- :randomize_scrub_length => Rollbar.configuration.randomize_scrub_length)
38
- url = url_scrubber.call(rollbar_url(env))
39
-
26
+ request_params = scrub_params(rollbar_request_params(env), sensitive_params)
27
+ get_params = scrub_params(rollbar_get_params(rack_req), sensitive_params)
28
+ post_params = scrub_params(rollbar_post_params(rack_req), sensitive_params)
29
+ raw_body_params = scrub_params(mergeable_raw_body_params(rack_req), sensitive_params)
30
+ cookies = scrub_params(rollbar_request_cookies(rack_req), sensitive_params)
31
+ session = scrub_params(rollbar_request_session(rack_req), sensitive_params)
32
+ route_params = scrub_params(rollbar_route_params(env), sensitive_params)
33
+
34
+ url = scrub_url(rollbar_url(env), sensitive_params)
40
35
  params = request_params.merge(get_params).merge(post_params).merge(raw_body_params)
41
36
 
42
37
  data = {
@@ -57,6 +52,27 @@ module Rollbar
57
52
  data
58
53
  end
59
54
 
55
+ def scrub_url(url, sensitive_params)
56
+ options = {
57
+ :url => url,
58
+ :scrub_fields => Array(Rollbar.configuration.scrub_fields) + sensitive_params,
59
+ :scrub_user => Rollbar.configuration.scrub_user,
60
+ :scrub_password => Rollbar.configuration.scrub_password,
61
+ :randomize_scrub_length => Rollbar.configuration.randomize_scrub_length
62
+ }
63
+
64
+ Rollbar::Scrubbers::URL.call(options)
65
+ end
66
+
67
+ def scrub_params(params, sensitive_params)
68
+ options = {
69
+ :params => params,
70
+ :config => Rollbar.configuration.scrub_fields,
71
+ :extra_fields => sensitive_params
72
+ }
73
+ Rollbar::Scrubbers::Params.call(options)
74
+ end
75
+
60
76
  private
61
77
 
62
78
  def mergeable_raw_body_params(rack_req)
@@ -180,10 +196,6 @@ module Rollbar
180
196
  {}
181
197
  end
182
198
 
183
- def rollbar_filtered_params(sensitive_params, params)
184
- Rollbar::Scrubbers::Params.call(params, sensitive_params)
185
- end
186
-
187
199
  def sensitive_params_list(env)
188
200
  Array(env['action_dispatch.parameter_filter'])
189
201
  end
@@ -15,10 +15,12 @@ module Rollbar
15
15
  new.call(*args)
16
16
  end
17
17
 
18
- def call(params, extra_fields = [])
18
+ def call(options = {})
19
+ params = options[:params]
19
20
  return {} unless params
20
21
 
21
- config = Rollbar.configuration.scrub_fields
22
+ config = options[:config]
23
+ extra_fields = options[:extra_fields]
22
24
 
23
25
  scrub(params, build_scrub_options(config, extra_fields))
24
26
  end
@@ -6,34 +6,36 @@ require 'rollbar/language_support'
6
6
  module Rollbar
7
7
  module Scrubbers
8
8
  class URL
9
- attr_reader :regex
10
- attr_reader :scrub_user
11
- attr_reader :scrub_password
12
- attr_reader :randomize_scrub_length
13
-
14
- def initialize(options = {})
15
- @regex = build_regex(options[:scrub_fields])
16
- @scrub_user = options[:scrub_user]
17
- @scrub_password = options[:scrub_password]
18
- @randomize_scrub_length = options.fetch(:randomize_scrub_length, true)
9
+ def self.call(*args)
10
+ new.call(*args)
19
11
  end
20
12
 
21
- def call(url)
13
+ def call(options = {})
14
+ url = options[:url]
22
15
  return url unless Rollbar::LanguageSupport.can_scrub_url?
23
16
 
17
+ filter(url,
18
+ build_regex(options[:scrub_fields]),
19
+ options[:scrub_user],
20
+ options[:scrub_password],
21
+ options.fetch(:randomize_scrub_length, true))
22
+ rescue => e
23
+ Rollbar.logger.error("[Rollbar] There was an error scrubbing the url: #{e}, options: #{options.inspect}")
24
+ url
25
+ end
26
+
27
+ private
28
+
29
+ def filter(url, regex, scrub_user, scrub_password, randomize_scrub_length)
24
30
  uri = URI.parse(url)
25
31
 
26
- uri.user = filter_user(uri.user)
27
- uri.password = filter_password(uri.password)
28
- uri.query = filter_query(uri.query)
32
+ uri.user = filter_user(uri.user, scrub_user, randomize_scrub_length)
33
+ uri.password = filter_password(uri.password, scrub_password, randomize_scrub_length)
34
+ uri.query = filter_query(uri.query, regex, randomize_scrub_length)
29
35
 
30
36
  uri.to_s
31
- rescue
32
- url
33
37
  end
34
38
 
35
- private
36
-
37
39
  # Builds a regex to match with any of the received fields.
38
40
  # The built regex will also match array params like 'user_ids[]'.
39
41
  def build_regex(fields)
@@ -42,20 +44,20 @@ module Rollbar
42
44
  Regexp.new("^#{fields_or}$")
43
45
  end
44
46
 
45
- def filter_user(user)
46
- scrub_user && user ? filtered_value(user) : user
47
+ def filter_user(user, scrub_user, randomize_scrub_length)
48
+ scrub_user && user ? filtered_value(user, randomize_scrub_length) : user
47
49
  end
48
50
 
49
- def filter_password(password)
50
- scrub_password && password ? filtered_value(password) : password
51
+ def filter_password(password, scrub_password, randomize_scrub_length)
52
+ scrub_password && password ? filtered_value(password, randomize_scrub_length) : password
51
53
  end
52
54
 
53
- def filter_query(query)
55
+ def filter_query(query, regex, randomize_scrub_length)
54
56
  return query unless query
55
57
 
56
58
  params = decode_www_form(query)
57
59
 
58
- encoded_query = encode_www_form(filter_query_params(params))
60
+ encoded_query = encode_www_form(filter_query_params(params, regex, randomize_scrub_length))
59
61
 
60
62
  # We want this to rebuild array params like foo[]=1&foo[]=2
61
63
  CGI.unescape(encoded_query)
@@ -69,17 +71,17 @@ module Rollbar
69
71
  URI.encode_www_form(params)
70
72
  end
71
73
 
72
- def filter_query_params(params)
74
+ def filter_query_params(params, regex, randomize_scrub_length)
73
75
  params.map do |key, value|
74
- [key, filter_key?(key) ? filtered_value(value) : value]
76
+ [key, filter_key?(key, regex) ? filtered_value(value, randomize_scrub_length) : value]
75
77
  end
76
78
  end
77
79
 
78
- def filter_key?(key)
80
+ def filter_key?(key, regex)
79
81
  !!(key =~ regex)
80
82
  end
81
83
 
82
- def filtered_value(value)
84
+ def filtered_value(value, randomize_scrub_length)
83
85
  if randomize_scrub_length
84
86
  random_filtered_value
85
87
  else
data/lib/rollbar/util.rb CHANGED
@@ -91,5 +91,15 @@ module Rollbar
91
91
 
92
92
  str.unpack("U*").slice(0, length - ellipsis.length).pack("U*") + ellipsis
93
93
  end
94
+
95
+ def self.uuid_rollbar_url(data, configuration)
96
+ "#{configuration.web_base}/instance/uuid?uuid=#{data[:uuid]}"
97
+ end
98
+
99
+ def self.enforce_valid_utf8(payload)
100
+ normalizer = lambda { |object| Encoding.encode(object) }
101
+
102
+ Util.iterate_and_update(payload, normalizer)
103
+ end
94
104
  end
95
105
  end
@@ -1,3 +1,3 @@
1
1
  module Rollbar
2
- VERSION = '2.10.0'
2
+ VERSION = '2.11.0'
3
3
  end
@@ -12,13 +12,14 @@ describe HomeController do
12
12
  Rollbar.configure do |config|
13
13
  config.access_token = test_access_token
14
14
  config.logger = logger_mock
15
+ config.open_timeout = 60
15
16
  config.request_timeout = 60
16
17
  end
17
18
  end
18
19
 
19
20
  context "rollbar base_data" do
20
21
  it 'should have the Rails environment' do
21
- data = Rollbar.notifier.send(:build_payload, 'error', 'message', nil, nil)
22
+ data = Rollbar.notifier.send(:build_item, 'error', 'message', nil, nil)
22
23
  data['data'][:environment].should == ::Rails.env
23
24
  end
24
25
 
@@ -27,7 +28,7 @@ describe HomeController do
27
28
  config.environment = 'dev'
28
29
  end
29
30
 
30
- data = Rollbar.notifier.send(:build_payload, 'error', 'message', nil, nil)
31
+ data = Rollbar.notifier.send(:build_item, 'error', 'message', nil, nil)
31
32
  data['data'][:environment].should == 'dev'
32
33
  end
33
34
 
@@ -35,7 +36,7 @@ describe HomeController do
35
36
  old_env, ::Rails.env = ::Rails.env, ''
36
37
  preconfigure_rails_notifier
37
38
 
38
- data = Rollbar.notifier.send(:build_payload, 'error', 'message', nil, nil)
39
+ data = Rollbar.notifier.send(:build_item, 'error', 'message', nil, nil)
39
40
  data['data'][:environment].should == 'unspecified'
40
41
 
41
42
  ::Rails.env = old_env
@@ -0,0 +1,9 @@
1
+ class Post
2
+ include ActiveModel::Validations
3
+ include ActiveModel::Validations::Callbacks
4
+
5
+ attr_accessor :title
6
+
7
+ validates_presence_of :title
8
+ after_validation :report_validation_errors_to_rollbar
9
+ end
@@ -4,4 +4,6 @@ class User < ActiveRecord::Base
4
4
  attr_accessible :username, :email, :password, :password_confirmation, :remember_me
5
5
  end
6
6
 
7
+ validates_presence_of :email
8
+ after_validation :report_validation_errors_to_rollbar
7
9
  end
@@ -1,5 +1,6 @@
1
1
  Rollbar.configure do |config|
2
2
  config.access_token = 'aaaabbbbccccddddeeeeffff00001111'
3
+ config.open_timeout = 60
3
4
  config.request_timeout = 60
4
5
  config.js_enabled = true
5
6
  config.js_options = {
@@ -0,0 +1,5 @@
1
+ require 'rollbar/plugins'
2
+
3
+ Rollbar.plugins.define(:dummy1) do
4
+ dependency { true }
5
+ end
@@ -0,0 +1,5 @@
1
+ require 'rollbar/plugins'
2
+
3
+ Rollbar.plugins.define(:dummy2) do
4
+ dependency { true }
5
+ end
@@ -0,0 +1,635 @@
1
+ require 'spec_helper'
2
+ require 'rollbar/configuration'
3
+ require 'rollbar/item'
4
+ require 'rollbar/lazy_store'
5
+
6
+ describe Rollbar::Item do
7
+ let(:notifier) { double('notifier', :safely => safely_notifier) }
8
+ let(:safely_notifier) { double('safely_notifier') }
9
+ let(:logger) { double }
10
+ let(:configuration) do
11
+ c = Rollbar::Configuration.new
12
+ c.enabled = true
13
+ c.access_token = 'footoken'
14
+ c.root = '/foo/'
15
+ c.framework = 'Rails'
16
+ c
17
+ end
18
+ let(:level) { 'info' }
19
+ let(:message) { 'message' }
20
+ let(:exception) {}
21
+ let(:extra) {}
22
+ let(:scope) {}
23
+
24
+ let(:options) do
25
+ {
26
+ :level => level,
27
+ :message => message,
28
+ :exception => exception,
29
+ :extra => extra,
30
+ :configuration => configuration,
31
+ :logger => logger,
32
+ :scope => scope,
33
+ :notifier => notifier
34
+ }
35
+ end
36
+
37
+ subject { described_class.new(options) }
38
+
39
+ describe '#build' do
40
+ let(:payload) { subject.build }
41
+
42
+ context 'a basic payload' do
43
+ let(:extra) { {:key => 'value', :hash => {:inner_key => 'inner_value'}} }
44
+
45
+ it 'calls Rollbar::Util.enforce_valid_utf8' do
46
+ expect(Rollbar::Util).to receive(:enforce_valid_utf8).with(kind_of(Hash))
47
+
48
+ subject.build
49
+ end
50
+
51
+ it 'should have the correct root-level keys' do
52
+ payload.keys.should match_array(['access_token', 'data'])
53
+ end
54
+
55
+ it 'should have the correct data keys' do
56
+ payload['data'].keys.should include(:timestamp, :environment, :level, :language, :framework, :server,
57
+ :notifier, :body)
58
+ end
59
+
60
+ it 'should have the correct notifier name and version' do
61
+ payload['data'][:notifier][:name].should == 'rollbar-gem'
62
+ payload['data'][:notifier][:version].should == Rollbar::VERSION
63
+ end
64
+
65
+ it 'should have the correct language and framework' do
66
+ payload['data'][:language].should == 'ruby'
67
+ payload['data'][:framework].should == configuration.framework
68
+ payload['data'][:framework].should match(/^Rails/)
69
+ end
70
+
71
+ it 'should have the correct server keys' do
72
+ payload['data'][:server].keys.should match_array([:host, :root, :pid])
73
+ end
74
+
75
+ it 'should have the correct level and message body' do
76
+ payload['data'][:level].should == 'info'
77
+ payload['data'][:body][:message][:body].should == 'message'
78
+ end
79
+ end
80
+
81
+ it 'should merge in a new key from payload_options' do
82
+ configuration.payload_options = { :some_new_key => 'some new value' }
83
+
84
+ payload['data'][:some_new_key].should == 'some new value'
85
+ end
86
+
87
+ it 'should overwrite existing keys from payload_options' do
88
+ payload_options = {
89
+ :notifier => 'bad notifier',
90
+ :server => { :host => 'new host', :new_server_key => 'value' }
91
+ }
92
+ configuration.payload_options = payload_options
93
+
94
+ payload['data'][:notifier].should == 'bad notifier'
95
+ payload['data'][:server][:host].should == 'new host'
96
+ payload['data'][:server][:root].should_not be_nil
97
+ payload['data'][:server][:new_server_key].should == 'value'
98
+ end
99
+
100
+ it 'should have default environment "unspecified"' do
101
+ configuration.environment = nil
102
+
103
+ payload['data'][:environment].should == 'unspecified'
104
+ end
105
+
106
+ it 'should have an overridden environment' do
107
+ configuration.environment = 'overridden'
108
+
109
+ payload['data'][:environment].should == 'overridden'
110
+ end
111
+
112
+ it 'should not have custom data under default configuration' do
113
+ payload['data'][:body][:message][:extra].should be_nil
114
+ end
115
+
116
+ it 'should have custom message data when custom_data_method is configured' do
117
+ configuration.custom_data_method = lambda { {:a => 1, :b => [2, 3, 4]} }
118
+
119
+ payload['data'][:body][:message][:extra].should_not be_nil
120
+ payload['data'][:body][:message][:extra][:a].should == 1
121
+ payload['data'][:body][:message][:extra][:b][2].should == 4
122
+ end
123
+
124
+ context do
125
+ let(:extra) do
126
+ { :c => {:e => 'g' }, :f => 'f' }
127
+ end
128
+
129
+ it 'should merge extra data into custom message data' do
130
+ custom_method = lambda do
131
+ { :a => 1,
132
+ :b => [2, 3, 4],
133
+ :c => { :d => 'd', :e => 'e' },
134
+ :f => ['1', '2']
135
+ }
136
+ end
137
+ configuration.custom_data_method = custom_method
138
+
139
+ payload['data'][:body][:message][:extra].should_not be_nil
140
+ payload['data'][:body][:message][:extra][:a].should == 1
141
+ payload['data'][:body][:message][:extra][:b][2].should == 4
142
+ payload['data'][:body][:message][:extra][:c][:d].should == 'd'
143
+ payload['data'][:body][:message][:extra][:c][:e].should == 'g'
144
+ payload['data'][:body][:message][:extra][:f].should == 'f'
145
+ end
146
+ end
147
+
148
+ context 'with custom_data_method crashing' do
149
+ next unless defined?(SecureRandom) && SecureRandom.respond_to?(:uuid)
150
+
151
+ let(:crashing_exception) { StandardError.new }
152
+ let(:custom_method) { proc { raise crashing_exception } }
153
+ let(:extra) { { :foo => :bar } }
154
+ let(:custom_data_report) do
155
+ { :_error_in_custom_data_method => SecureRandom.uuid }
156
+ end
157
+ let(:expected_extra) { extra.merge(custom_data_report) }
158
+
159
+ before do
160
+ configuration.custom_data_method = custom_method
161
+ end
162
+
163
+ it 'doesnt crash the report' do
164
+ expect(subject).to receive(:report_custom_data_error).once.and_return(custom_data_report)
165
+
166
+ expect(payload['data'][:body][:message][:extra]).to be_eql(expected_extra)
167
+ end
168
+
169
+ context 'and for some reason the safely.error returns a String' do
170
+ it 'returns an empty Hash' do
171
+ allow(safely_notifier).to receive(:error).and_return('ignored')
172
+
173
+ expect(payload['data'][:body][:message][:extra]).to be_eql(extra)
174
+ end
175
+ end
176
+ end
177
+
178
+ it 'should include project_gem_paths' do
179
+ gems = Gem::Specification.map(&:name)
180
+ project_gems = ['rails']
181
+ project_gems << 'rspec' if gems.include?('rspec')
182
+ project_gems << 'rspec-core' if gems.include?('rspec-core')
183
+
184
+ configuration.project_gems = project_gems
185
+
186
+ expect(payload['data'][:project_package_paths].count).to eq(project_gems.size)
187
+ end
188
+
189
+ it 'should include a code_version' do
190
+ configuration.code_version = 'abcdef'
191
+
192
+ payload['data'][:code_version].should == 'abcdef'
193
+ end
194
+
195
+ it 'should have the right hostname' do
196
+ payload['data'][:server][:host].should == Socket.gethostname
197
+ end
198
+
199
+ it 'should have root and branch set when configured' do
200
+ configuration.root = '/path/to/root'
201
+ configuration.branch = 'master'
202
+
203
+ payload['data'][:server][:root].should == '/path/to/root'
204
+ payload['data'][:server][:branch].should == 'master'
205
+ end
206
+
207
+ context 'build_payload_body' do
208
+ let(:exception) do
209
+ begin
210
+ foo = bar
211
+ rescue => e
212
+ e
213
+ end
214
+ end
215
+
216
+ context 'with no exception' do
217
+ let(:exception) { nil }
218
+
219
+ it 'should build a message body when no exception is passed in' do
220
+ payload['data'][:body][:message][:body].should == 'message'
221
+ payload['data'][:body][:message][:extra].should be_nil
222
+ payload['data'][:body][:trace].should be_nil
223
+ end
224
+
225
+ context 'and extra data' do
226
+ let(:extra) do
227
+ {:a => 'b'}
228
+ end
229
+
230
+ it 'should build a message body when no exception and extra data is passed in' do
231
+ payload['data'][:body][:message][:body].should == 'message'
232
+ payload['data'][:body][:message][:extra].should == {:a => 'b'}
233
+ payload['data'][:body][:trace].should be_nil
234
+ end
235
+ end
236
+ end
237
+
238
+ it 'should build an exception body when one is passed in' do
239
+ body = payload['data'][:body]
240
+ body[:message].should be_nil
241
+
242
+ trace = body[:trace]
243
+ trace.should_not be_nil
244
+ trace[:extra].should be_nil
245
+
246
+ trace[:exception][:class].should_not be_nil
247
+ trace[:exception][:message].should_not be_nil
248
+ end
249
+
250
+ context 'with extra data' do
251
+ let(:extra) do
252
+ {:a => 'b'}
253
+ end
254
+
255
+ it 'should build an exception body when one is passed in along with extra data' do
256
+ body = payload['data'][:body]
257
+ body[:message].should be_nil
258
+
259
+ trace = body[:trace]
260
+ trace.should_not be_nil
261
+
262
+ trace[:exception][:class].should_not be_nil
263
+ trace[:exception][:message].should_not be_nil
264
+ trace[:extra].should == {:a => 'b'}
265
+ end
266
+ end
267
+ end
268
+
269
+ context 'build_payload_body_exception' do
270
+ let(:exception) do
271
+ begin
272
+ foo = bar
273
+ rescue => e
274
+ e
275
+ end
276
+ end
277
+
278
+ it 'should build valid exception data' do
279
+ body = payload['data'][:body]
280
+ body[:message].should be_nil
281
+
282
+ trace = body[:trace]
283
+
284
+ frames = trace[:frames]
285
+ frames.should be_a_kind_of(Array)
286
+ frames.each do |frame|
287
+ frame[:filename].should be_a_kind_of(String)
288
+ frame[:lineno].should be_a_kind_of(Fixnum)
289
+ if frame[:method]
290
+ frame[:method].should be_a_kind_of(String)
291
+ end
292
+ end
293
+
294
+ # should be NameError, but can be NoMethodError sometimes on rubinius 1.8
295
+ # http://yehudakatz.com/2010/01/02/the-craziest-fing-bug-ive-ever-seen/
296
+ trace[:exception][:class].should match(/^(NameError|NoMethodError)$/)
297
+ trace[:exception][:message].should match(/^(undefined local variable or method `bar'|undefined method `bar' on an instance of)/)
298
+ end
299
+
300
+ context 'with description message' do
301
+ let(:message) { 'exception description' }
302
+
303
+ it 'should build exception data with a description' do
304
+ body = payload['data'][:body]
305
+
306
+ trace = body[:trace]
307
+
308
+ trace[:exception][:message].should match(/^(undefined local variable or method `bar'|undefined method `bar' on an instance of)/)
309
+ trace[:exception][:description].should == 'exception description'
310
+ end
311
+
312
+ context 'and extra data' do
313
+ let(:extra) do
314
+ {:key => 'value', :hash => {:inner_key => 'inner_value'}}
315
+ end
316
+
317
+ it 'should build exception data with a description and extra data' do
318
+ body = payload['data'][:body]
319
+ trace = body[:trace]
320
+
321
+ trace[:exception][:message].should match(/^(undefined local variable or method `bar'|undefined method `bar' on an instance of)/)
322
+ trace[:exception][:description].should == 'exception description'
323
+ trace[:extra][:key].should == 'value'
324
+ trace[:extra][:hash].should == {:inner_key => 'inner_value'}
325
+ end
326
+ end
327
+ end
328
+
329
+ context 'with extra data' do
330
+ let(:extra) do
331
+ {:key => 'value', :hash => {:inner_key => 'inner_value'}}
332
+ end
333
+ it 'should build exception data with a extra data' do
334
+ body = payload['data'][:body]
335
+ trace = body[:trace]
336
+
337
+ trace[:exception][:message].should match(/^(undefined local variable or method `bar'|undefined method `bar' on an instance of)/)
338
+ trace[:extra][:key].should == 'value'
339
+ trace[:extra][:hash].should == {:inner_key => 'inner_value'}
340
+ end
341
+ end
342
+
343
+ context 'with nested exceptions' do
344
+ let(:crashing_code) do
345
+ proc do
346
+ begin
347
+ begin
348
+ fail CauseException.new('the cause')
349
+ rescue
350
+ fail StandardError.new('the error')
351
+ end
352
+ rescue => e
353
+ e
354
+ end
355
+ end
356
+ end
357
+
358
+ let(:exception) { crashing_code.call }
359
+ let(:message) { 'message' }
360
+ let(:extra) { {} }
361
+
362
+ context 'using ruby >= 2.1' do
363
+ next unless Exception.instance_methods.include?(:cause)
364
+
365
+ it 'sends the two exceptions in the trace_chain attribute' do
366
+ body = payload['data'][:body]
367
+
368
+ body[:trace].should be_nil
369
+ body[:trace_chain].should be_kind_of(Array)
370
+
371
+ chain = body[:trace_chain]
372
+ chain[0][:exception][:class].should match(/StandardError/)
373
+ chain[0][:exception][:message].should match(/the error/)
374
+
375
+ chain[1][:exception][:class].should match(/CauseException/)
376
+ chain[1][:exception][:message].should match(/the cause/)
377
+ end
378
+
379
+ context 'when cause is not an Exception' do
380
+ let(:exception) { Exception.new('custom cause') }
381
+
382
+ it 'ignores the cause when it is not an Exception' do
383
+ allow(exception).to receive(:cause) { "Foo" }
384
+
385
+ payload['data'][:body][:trace].should_not be_nil
386
+ end
387
+ end
388
+
389
+ context 'with cyclic nested exceptions' do
390
+ let(:exception1) { Exception.new('exception1') }
391
+ let(:exception2) { Exception.new('exception2') }
392
+ let(:exception) { exception1 }
393
+
394
+ before do
395
+ allow(exception1).to receive(:cause).and_return(exception2)
396
+ allow(exception2).to receive(:cause).and_return(exception1)
397
+ end
398
+
399
+ it 'doesnt loop for ever' do
400
+ chain = payload['data'][:body][:trace_chain]
401
+
402
+ expect(chain[0][:exception][:message]).to be_eql('exception1')
403
+ expect(chain[1][:exception][:message]).to be_eql('exception2')
404
+ end
405
+ end
406
+ end
407
+
408
+ context 'using ruby <= 2.1' do
409
+ next if Exception.instance_methods.include?(:cause)
410
+
411
+ it 'sends only the last exception in the trace attribute' do
412
+ body = payload['data'][:body]
413
+
414
+ body[:trace].should be_kind_of(Hash)
415
+ body[:trace_chain].should be_nil
416
+
417
+ body[:trace][:exception][:class].should match(/StandardError/)
418
+ body[:trace][:exception][:message].should match(/the error/)
419
+ end
420
+ end
421
+ end
422
+ end
423
+
424
+ context 'build_payload_body_message' do
425
+ it 'should build a message' do
426
+ payload['data'][:body][:message][:body].should == 'message'
427
+ payload['data'][:body][:trace].should be_nil
428
+ end
429
+
430
+ context 'with extra data' do
431
+ let(:extra) do
432
+ {:key => 'value', :hash => {:inner_key => 'inner_value'}}
433
+ end
434
+
435
+ it 'should build a message with extra data' do
436
+ payload['data'][:body][:message][:body].should == 'message'
437
+ payload['data'][:body][:message][:extra][:key].should == 'value'
438
+ payload['data'][:body][:message][:extra][:hash].should == {:inner_key => 'inner_value'}
439
+ end
440
+ end
441
+
442
+ context 'with empty message and extra data' do
443
+ let(:message) { nil }
444
+ let(:extra) do
445
+ {:key => 'value', :hash => {:inner_key => 'inner_value'}}
446
+ end
447
+
448
+ it 'should build an empty message with extra data' do
449
+ payload['data'][:body][:message][:body].should == 'Empty message'
450
+ payload['data'][:body][:message][:extra][:key].should == 'value'
451
+ payload['data'][:body][:message][:extra][:hash].should == {:inner_key => 'inner_value'}
452
+ end
453
+ end
454
+ end
455
+
456
+ context 'with transform handlers in configuration' do
457
+ let(:scope) { Rollbar::LazyStore.new({ :bar => :foo }) }
458
+ let(:message) { 'message' }
459
+ let(:exception) { Exception.new }
460
+ let(:extra) { { :foo => :bar } }
461
+ let(:level) { 'error' }
462
+
463
+ context 'without mutation in payload' do
464
+ let(:handler) do
465
+ proc do |options|
466
+
467
+ end
468
+ end
469
+
470
+ before do
471
+ configuration.transform = handler
472
+ end
473
+
474
+ it 'calls the handler with the correct options' do
475
+ options = {
476
+ :level => subject.level,
477
+ :scope => subject.scope,
478
+ :exception => subject.exception,
479
+ :message => subject.message,
480
+ :extra => subject.extra,
481
+ :payload => kind_of(Hash)
482
+ }
483
+
484
+ expect(handler).to receive(:call).with(options).and_call_original
485
+
486
+ subject.build
487
+ end
488
+ end
489
+
490
+ context 'with mutation in payload' do
491
+ let(:new_payload) do
492
+ {
493
+ 'access_token' => configuration.access_token,
494
+ 'data' => {
495
+ }
496
+ }
497
+ end
498
+ let(:handler) do
499
+ proc do |options|
500
+ payload = options[:payload]
501
+
502
+ payload.replace(new_payload)
503
+ end
504
+ end
505
+
506
+ before do
507
+ configuration.transform = handler
508
+ end
509
+
510
+ it 'calls the handler with the correct options' do
511
+ options = {
512
+ :level => level,
513
+ :scope => Rollbar::LazyStore.new(scope),
514
+ :exception => exception,
515
+ :message => message,
516
+ :extra => extra,
517
+ :payload => kind_of(Hash)
518
+ }
519
+ expect(handler).to receive(:call).with(options).and_call_original
520
+ expect(payload).to be_eql(new_payload)
521
+ end
522
+ end
523
+
524
+ context 'with two handlers' do
525
+ let(:handler1) { proc { |options|} }
526
+ let(:handler2) { proc { |options|} }
527
+
528
+ before do
529
+ configuration.transform << handler1
530
+ configuration.transform << handler2
531
+ end
532
+
533
+ context 'and the first one fails' do
534
+ let(:exception) { StandardError.new('foo') }
535
+ let(:handler1) do
536
+ proc { |options| raise exception }
537
+ end
538
+
539
+ it 'doesnt call the second handler and logs the error' do
540
+ expect(handler2).not_to receive(:call)
541
+ expect(logger).to receive(:error).with("[Rollbar] Error calling the `transform` hook: #{exception}")
542
+
543
+ subject.build
544
+ end
545
+ end
546
+ end
547
+ end
548
+
549
+ describe '#custom_data' do
550
+ before do
551
+ configuration.custom_data_method = proc { raise 'this-will-raise' }
552
+
553
+ expect(safely_notifier).to receive(:error).and_return(report_data)
554
+ end
555
+
556
+ context 'with uuid in reported data' do
557
+ next unless defined?(SecureRandom) and SecureRandom.respond_to?(:uuid)
558
+
559
+ let(:report_data) { { :uuid => SecureRandom.uuid } }
560
+ let(:expected_url) { "https://rollbar.com/instance/uuid?uuid=#{report_data[:uuid]}" }
561
+
562
+ it 'returns the uuid in :_error_in_custom_data_method' do
563
+ expect(payload['data'][:body][:message][:extra]).to be_eql(:_error_in_custom_data_method => expected_url)
564
+ end
565
+ end
566
+
567
+ context 'without uuid in reported data' do
568
+ let(:report_data) { { :some => 'other-data' } }
569
+
570
+ it 'returns empty data' do
571
+ expect(payload['data'][:body][:message][:extra]).to be_eql({})
572
+ end
573
+ end
574
+ end
575
+
576
+ context 'server_data' do
577
+ it 'should have the right hostname' do
578
+ payload['data'][:server][:host] == Socket.gethostname
579
+ end
580
+
581
+ it 'should have root and branch set when configured' do
582
+ configuration.root = '/path/to/root'
583
+ configuration.branch = 'master'
584
+
585
+ payload['data'][:server][:root].should == '/path/to/root'
586
+ payload['data'][:server][:branch].should == 'master'
587
+ end
588
+ end
589
+
590
+ context 'with ignored person ids' do
591
+ let(:ignored_ids) { [1,2,4] }
592
+ let(:person_data) do
593
+ { :person => {
594
+ :id => 2,
595
+ :username => 'foo'
596
+ }
597
+ }
598
+ end
599
+ let(:scope) { Rollbar::LazyStore.new(person_data) }
600
+
601
+ before do
602
+ configuration.person_id_method = :id
603
+ configuration.ignored_person_ids = ignored_ids
604
+ end
605
+
606
+ it 'sets ignored property to true' do
607
+ subject.build
608
+
609
+ expect(subject).to be_ignored
610
+ end
611
+ end
612
+
613
+ end # end #build
614
+
615
+ describe '#dump' do
616
+ context 'with Redis instance in payload and ActiveSupport is enabled' do
617
+ let(:redis) { ::Redis.new }
618
+ let(:payload) do
619
+ {
620
+ :key => {
621
+ :value => redis
622
+ }
623
+ }
624
+ end
625
+ let(:item) { Rollbar::Item.build_with(payload) }
626
+
627
+ it 'dumps to JSON correctly' do
628
+ redis.set('foo', 'bar')
629
+ json = item.dump
630
+
631
+ expect(json).to be_kind_of(String)
632
+ end
633
+ end
634
+ end
635
+ end